diff --git a/.github/workflows/GLuaFixer.yml b/.github/workflows/GLuaFixer.yml index e58870bb9..e74d0564c 100644 --- a/.github/workflows/GLuaFixer.yml +++ b/.github/workflows/GLuaFixer.yml @@ -5,23 +5,21 @@ on: paths: - 'lua/**' - '!lua/entities/gmod_wire_expression2/**' - - '!lua/starfall/**' pull_request: paths: - 'lua/**' - '!lua/entities/gmod_wire_expression2/**' - - '!lua/starfall/**' jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - - name: Download GLuaFixer 1.12.0 - run: curl -o glualint.zip -L https://github.com/FPtje/GLuaFixer/releases/download/1.12.0/glualint-1.12.0-linux.zip + - name: Download GLuaFixer 1.17.0 + run: curl -o glualint.zip -L https://github.com/FPtje/GLuaFixer/releases/download/1.17.0/glualint-1.17.0-linux.zip - name: Extract glualint.zip run: unzip glualint.zip - name: Remove blacklisted folders - run: rm -r lua/entities/gmod_wire_expression2/ lua/starfall/ + run: rm -r lua/entities/gmod_wire_expression2/ - name: Initiate linting run: ./glualint lua diff --git a/.glualint.json b/.glualint.json index 3229b3f22..08dd758ef 100644 --- a/.glualint.json +++ b/.glualint.json @@ -3,24 +3,31 @@ "lint_syntaxErrors": true, "lint_syntaxInconsistencies": true, "lint_deprecated": true, + "lint_trailingWhitespace": true, "lint_whitespaceStyle": true, "lint_beginnerMistakes": true, "lint_emptyBlocks": true, "lint_shadowing": true, "lint_gotos": true, "lint_doubleNegations": true, + "lint_redundantIfStatements": true, + "lint_redundantParentheses": true, "lint_duplicateTableKeys": true, "lint_profanity": true, "lint_unusedVars": true, "lint_unusedParameters": true, "lint_unusedLoopVars": true, + "lint_ignoreFiles": [], "prettyprint_spaceAfterParens": false, "prettyprint_spaceAfterBrackets": false, "prettyprint_spaceAfterBraces": false, + "prettyprint_spaceAfterLabel": false, "prettyprint_spaceBeforeComma": false, "prettyprint_spaceAfterComma": true, "prettyprint_semicolons": false, "prettyprint_cStyle": false, - "prettyprint_indentation": "\t" + "prettyprint_rejectInvalidCode": false, + "prettyprint_indentation": "\t", + "log_format": "auto" } \ No newline at end of file diff --git a/lua/acf/base/acf_globals.lua b/lua/acf/base/acf_globals.lua index a5f5541f9..71dbe5eaf 100644 --- a/lua/acf/base/acf_globals.lua +++ b/lua/acf/base/acf_globals.lua @@ -1,34 +1,47 @@ do -- ACF global vars - ACF.AmmoTypes = ACF.AmmoTypes or {} - ACF.AmmoCrates = ACF.AmmoCrates or {} - ACF.FuelTanks = ACF.FuelTanks or {} - ACF.MenuFunc = ACF.MenuFunc or {} - ACF.AmmoBlacklist = ACF.AmmoBlacklist or {} - ACF.Repositories = ACF.Repositories or {} - - -- Misc - ACF.Year = 1945 - ACF.IllegalDisableTime = 30 -- Time in seconds for an entity to be disabled when it fails ACF_IsLegal - ACF.GunfireEnabled = true - ACF.SmokeWind = 5 + math.random() * 35 --affects the ability of smoke to be used for screening effect - ACF.EnableKillicons = true -- Enable killicons overwriting. - ACF.SoundVolume = 1 --Scales sound volume + ACF.AmmoCrates = ACF.AmmoCrates or {} + ACF.Classes = ACF.Classes or {} + ACF.FuelTanks = ACF.FuelTanks or {} + ACF.Repositories = ACF.Repositories or {} + ACF.ClientData = ACF.ClientData or {} + ACF.ServerData = ACF.ServerData or {} + + -- General Settings + ACF.Gamemode = 2 -- Gamemode of the server. 1 = Sandbox, 2 = Classic, 3 = Competitive + ACF.Year = 1945 + ACF.IllegalDisableTime = 30 -- Time in seconds for an entity to be disabled when it fails ACF_IsLegal + ACF.RestrictInfo = true -- If enabled, players will be only allowed to get info from entities they're allowed to mess with. + ACF.GunfireEnabled = true + ACF.AllowAdminData = false -- Allows admins to mess with a few server settings and data variables + ACF.HEPush = true -- Whether or not HE pushes on entities + ACF.KEPush = true -- Whether or not kinetic force pushes on entities + ACF.RecoilPush = true -- Whether or not ACF guns apply recoil + ACF.Volume = 1 -- Global volume for ACF sounds + ACF.AllowFunEnts = true -- Allows entities listed under the Fun Stuff option to be used + ACF.WorkshopContent = true -- Enable workshop content download for clients + ACF.WorkshopExtras = false -- Enable extra workshop content download for clients + ACF.SmokeWind = 5 + math.random() * 35 --affects the ability of smoke to be used for screening effect + -- Fuzes - ACF.MinFuzeCaliber = 2 -- Minimum caliber that can be fuzed (centimeters) + ACF.MinFuzeCaliber = 20 -- Minimum caliber in millimeters that can be fuzed + -- Reload Mechanics - ACF.BaseReload = 1 -- Minimum reload time. Time it takes to move around a weightless projectile - ACF.MassToTime = 0.2 -- Conversion of projectile mass to time be moved around - ACF.LengthToTime = 0.1 -- Conversion of projectile length to time -- Emulating the added difficulty of manipulating a longer projectile + ACF.BaseReload = 1 -- Minimum reload time. Time it takes to move around a weightless projectile + ACF.MassToTime = 0.2 -- Conversion of projectile mass to time be moved around + ACF.LengthToTime = 0.1 -- Conversion of projectile length to time -- Emulating the added difficulty of manipulating a longer projectile + -- External and Terminal Ballistics - ACF.DragDiv = 80 --Drag fudge factor - ACF.Scale = 1 --Scale factor for ACF in the game world - ACF.Threshold = 264.7 --Health Divisor (don"t forget to update cvar function down below) - ACF.PenAreaMod = 0.85 - ACF.KinFudgeFactor = 2.1 --True kinetic would be 2, over that it's speed biased, below it's mass biased - ACF.KEtoRHA = 0.25 --Empirical conversion from (kinetic energy in KJ)/(Area in Cm2) to RHA penetration - ACF.GroundtoRHA = 0.15 --How much mm of steel is a mm of ground worth (Real soil is about 0.15) - ACF.ArmorMod = 1 - ACF.SlopeEffectFactor = 1.1 -- Sloped armor effectiveness: armor / cos(angle)^factor + ACF.DragDiv = 80 --Drag fudge factor + ACF.Scale = 1 --Scale factor for ACF in the game world + ACF.HealthFactor = 1 + ACF.Threshold = 264.7 -- Health Divisor, directly tied to ACF.HealthFactor + ACF.PenAreaMod = 0.85 + ACF.KinFudgeFactor = 2.1 --True kinetic would be 2, over that it's speed biased, below it's mass biased + ACF.KEtoRHA = 0.25 --Empirical conversion from (kinetic energy in KJ)/(Area in Cm2) to RHA penetration + ACF.GroundtoRHA = 0.15 --How much mm of steel is a mm of ground worth (Real soil is about 0.15) + ACF.ArmorMod = 1 + ACF.ArmorFactor = 1 -- Multiplier for ACF.ArmorMod + ACF.SlopeEffectFactor = 1.1 -- Sloped armor effectiveness: armor / cos(angle)^factor ACF.GlobalFilter = { -- Global ACF filter gmod_ghost = true, acf_debris = true, @@ -40,34 +53,36 @@ do -- ACF global vars npc_strider = true, npc_dog = true } + -- Ammo - ACF.AmmoArmor = 5 -- How many millimeters of armor ammo crates have - ACF.AmmoPadding = 2 -- Millimeters of wasted space between rounds - ACF.AmmoMod = 1.05 -- Ammo modifier. 1 is 1x the amount of ammo. 0.6 default - ACF.AmmoCaseScale = 1.4 -- How much larger the diameter of the case is versus the projectile (necked cartridges, M829 is 1.4, .50 BMG is 1.6) - ACF.PBase = 875 --1KG of propellant produces this much KE at the muzzle, in kj - ACF.PScale = 1 --Gun Propellant power expotential - ACF.MVScale = 0.5 --Propellant to MV convertion expotential - ACF.PDensity = 0.95 -- Propellant loading density (Density of propellant + volume lost due to packing density) + ACF.AmmoArmor = 5 -- How many millimeters of armor ammo crates have + ACF.AmmoPadding = 2 -- Millimeters of wasted space between rounds + ACF.AmmoMod = 1.05 -- DEPRECATED. Ammo modifier. 1 is 1x the amount of ammo. 0.6 default + ACF.AmmoCaseScale = 1.4 -- How much larger the diameter of the case is versus the projectile (necked cartridges, M829 is 1.4, .50 BMG is 1.6) + ACF.PBase = 875 --1KG of propellant produces this much KE at the muzzle, in kj + ACF.PScale = 1 --Gun Propellant power expotential + ACF.MVScale = 0.5 --Propellant to MV convertion expotential + ACF.PDensity = 0.95 -- Propellant loading density (Density of propellant + volume lost due to packing density) + -- HE - ACF.HEPower = 8000 --HE Filler power per KG in KJ - ACF.HEDensity = 1.65 --HE Filler density (That's TNT density) - ACF.HEFrag = 1000 --Mean fragment number for equal weight TNT and casing - ACF.HEBlastPen = 0.4 --Blast penetration exponent based of HE power - ACF.HEFeatherExp = 0.5 --exponent applied to HE dist/maxdist feathering, <1 will increasingly bias toward max damage until sharp falloff at outer edge of range - ACF.HEATMVScale = 0.75 --Filler KE to HEAT slug KE conversion expotential - ACF.HEATMulAmmo = 30 --HEAT slug damage multiplier; 13.2x roughly equal to AP damage - ACF.HEATMulFuel = 4 --needs less multiplier, much less health than ammo - ACF.HEATMulEngine = 10 --likewise - ACF.HEATPenLayerMul = 0.75 --HEAT base energy multiplier - ACF.HEATBoomConvert = 1 / 3 -- percentage of filler that creates HE damage at detonation - ACF.HEATMinCrush = 800 -- vel where crush starts, progressively converting round to raw HE - ACF.HEATMaxCrush = 1200 -- vel where fully crushed + ACF.HEPower = 8000 --HE Filler power per KG in KJ + ACF.HEDensity = 1.65 --HE Filler density (That's TNT density) + ACF.HEFrag = 1000 --Mean fragment number for equal weight TNT and casing + ACF.HEBlastPen = 0.4 --Blast penetration exponent based of HE power + ACF.HEFeatherExp = 0.5 --exponent applied to HE dist/maxdist feathering, <1 will increasingly bias toward max damage until sharp falloff at outer edge of range + ACF.HEATMVScale = 0.75 --Filler KE to HEAT slug KE conversion expotential + ACF.HEATMulAmmo = 30 --HEAT slug damage multiplier; 13.2x roughly equal to AP damage + ACF.HEATMulFuel = 4 --needs less multiplier, much less health than ammo + ACF.HEATMulEngine = 10 --likewise + ACF.HEATPenLayerMul = 0.75 --HEAT base energy multiplier + ACF.HEATBoomConvert = 1 / 3 -- percentage of filler that creates HE damage at detonation + ACF.HEATMinCrush = 800 -- vel where crush starts, progressively converting round to raw HE + ACF.HEATMaxCrush = 1200 -- vel where fully crushed + -- Debris - ACF.ChildDebris = 50 -- higher is more debris props; Chance = ACF.ChildDebris / num_children; Only applies to children of acf-killed parent props - ACF.DebrisIgniteChance = 0.25 - ACF.DebrisScale = 999999 -- Ignore debris that is less than this bounding radius. - ACF.ValidDebris = { -- Whitelist for things that can be turned into debris + ACF.ChildDebris = 50 -- higher is more debris props; Chance = ACF.ChildDebris / num_children; Only applies to children of acf-killed parent props + ACF.DebrisIgniteChance = 0.25 + ACF.ValidDebris = { -- Whitelist for things that can be turned into debris acf_ammo = true, acf_gun = true, acf_gearbox = true, @@ -76,111 +91,60 @@ do -- ACF global vars prop_physics = true, prop_vehicle_prisoner_pod = true } - -- Weapon Accuracy - ACF.SpreadScale = 4 -- The maximum amount that damage can decrease a gun"s accuracy. Default 4x - ACF.GunInaccuracyScale = 1 -- A multiplier for gun accuracy. Must be between 0.5 and 4 - ACF.GunInaccuracyBias = 2 -- Higher numbers make shots more likely to be inaccurate. Choose between 0.5 to 4. Default is 2 (unbiased). - -- Fuel - ACF.FuelRate = 1 --multiplier for fuel usage, 1.0 is approx real world - ACF.CompFuelRate = 27.8 --Extra multiplier for fuel consumption on servers with acf_gamemode set to 2 (Competitive) - ACF.TankVolumeMul = 1 -- multiplier for fuel tank capacity, 1.0 is approx real world - ACF.LiIonED = 0.458 -- li-ion energy density: kw hours / liter - ACF.CuIToLiter = 0.0163871 -- cubic inches to liters - ACF.RefillDistance = 300 --Distance in which ammo crate starts refilling. - ACF.RefillSpeed = 700 -- (ACF.RefillSpeed / RoundMass) / Distance - - --kg/liter - ACF.FuelDensity = { - Diesel = 0.832, - Petrol = 0.745, - Electric = 3.89 -- li-ion - } - - --how efficient various engine types are, higher is worse - ACF.Efficiency = { - GenericPetrol = 0.304, -- kg per kw hr - GenericDiesel = 0.243, - Turbine = 0.375, - Wankel = 0.335, - Radial = 0.4, -- 0.38 to 0.53 - Electric = 0.2125 --percent efficiency converting chemical kw into mechanical kw - } - --how fast damage drops torque, lower loses more % torque - ACF.TorqueScale = { - GenericPetrol = 0.25, - GenericDiesel = 0.35, - Turbine = 0.2, - Wankel = 0.2, - Radial = 0.3, - Electric = 0.5 - } + -- Weapon Accuracy + ACF.SpreadScale = 4 -- The maximum amount that damage can decrease a gun"s accuracy. Default 4x + ACF.GunInaccuracyScale = 0.5 -- A multiplier for gun accuracy. Must be between 0.5 and 4 + ACF.GunInaccuracyBias = 2 -- Higher numbers make shots more likely to be inaccurate. Choose between 0.5 to 4. Default is 2 (unbiased). - --health multiplier for engines - ACF.EngineHPMult = { - GenericPetrol = 0.2, - GenericDiesel = 0.5, - Turbine = 0.125, - Wankel = 0.125, - Radial = 0.3, - Electric = 0.75 - } + -- Fuel + ACF.FuelRate = 1 --multiplier for fuel usage, 1.0 is approx real world + ACF.FuelFactor = 1 -- Multiplier for ACF.FuelRate + ACF.CompFuelRate = 27.8 -- Extra multiplier for fuel consumption on servers with ACF Gamemode set to Competitive + ACF.CompFuelFactor = 1 -- Multiplier for ACF.CompFuelRate + ACF.TankVolumeMul = 1 -- multiplier for fuel tank capacity, 1.0 is approx real world + ACF.LiIonED = 0.458 -- li-ion energy density: kw hours / liter + ACF.CuIToLiter = 0.0163871 -- cubic inches to liters + ACF.RefillDistance = 300 --Distance in which ammo crate starts refilling. + ACF.RefillSpeed = 700 -- (ACF.RefillSpeed / RoundMass) / Distance + ACF.RefuelSpeed = 20 -- Liters per second * ACF.FuelRate end -do -- ACF Convars/Callbacks ------------------------ - CreateConVar("sbox_max_acf_gun", 16) - CreateConVar("sbox_max_acf_smokelauncher", 10) - CreateConVar("sbox_max_acf_ammo", 32) - CreateConVar("sbox_max_acf_misc", 32) - CreateConVar("acf_meshvalue", 1) - CreateConVar("sbox_acf_restrictinfo", 1) -- 0=any, 1=owned - -- Cvars for recoil/he push - CreateConVar("acf_hepush", 1) - CreateConVar("acf_recoilpush", 1) - -- New healthmod/armormod/ammomod cvars - CreateConVar("acf_healthmod", 1) - CreateConVar("acf_armormod", 1) - CreateConVar("acf_ammomod", 1) - CreateConVar("acf_fuelrate", 1) - CreateConVar("acf_spalling", 0) - CreateConVar("acf_gunfire", 1) - - cvars.AddChangeCallback("acf_healthmod", ACF_CVarChangeCallback) - cvars.AddChangeCallback("acf_armormod", ACF_CVarChangeCallback) - cvars.AddChangeCallback("acf_ammomod", ACF_CVarChangeCallback) - cvars.AddChangeCallback("acf_fuelrate", ACF_CVarChangeCallback) - cvars.AddChangeCallback("acf_spalling", ACF_CVarChangeCallback) - cvars.AddChangeCallback("acf_gunfire", ACF_CVarChangeCallback) +do -- ACF Convars & Particles + CreateConVar("sbox_max_acf_ammo", 32, FCVAR_ARCHIVE + FCVAR_NOTIFY, "Maximum amount of ACF ammo crates a player can create.") game.AddParticles("particles/acf_muzzleflashes.pcf") game.AddParticles("particles/explosion1.pcf") game.AddParticles("particles/rocket_motor.pcf") - game.AddDecal("GunShot1", "decals/METAL/shot5") end if SERVER then - util.AddNetworkString("ACF_KilledByACF") - util.AddNetworkString("ACF_RenderDamage") - util.AddNetworkString("ACF_Notify") + util.AddNetworkString("ACF_UpdateEntity") - CreateConVar("acf_enable_workshop_extras", 1, FCVAR_ARCHIVE, "Enable extra workshop content download for clients. Requires server restart on change.", 0, 1) - CreateConVar("acf_gamemode", 1, FCVAR_ARCHIVE + FCVAR_NOTIFY, "Sets the ACF gamemode of the server. 0 = Sandbox, 1 = Classic, 2 = Competitive", 0, 2) - CreateConVar("acf_hepush", 1, FCVAR_ARCHIVE, "Whether or not HE pushes on entities", 0, 1) - CreateConVar("acf_kepush", 1, FCVAR_ARCHIVE, "Whether or not kinetic force pushes on entities", 0, 1) + hook.Add("PlayerConnect", "ACF Workshop Content", function() + if ACF.WorkshopContent then + resource.AddWorkshop("2183798463") -- Playermodel seats + end - -- Extra content ---------------------------- - local Extras = GetConVar("acf_enable_workshop_extras") + if ACF.WorkshopExtras then + resource.AddWorkshop("439526795") -- Hide Errors addon + resource.AddWorkshop("2099387099") -- ACF-3 Removed Extra Sounds + end - if Extras:GetBool() then - resource.AddWorkshop("439526795") -- Hide Errors addon - resource.AddWorkshop("2099387099") -- ACF-3 Removed Extra Sounds - end - --------------------------------------------- + hook.Add("PlayerConnect", "ACF Workshop Content") + end) elseif CLIENT then CreateClientConVar("acf_show_entity_info", 1, true, false, "Defines under what conditions the info bubble on ACF entities will be shown. 0 = Never, 1 = When not seated, 2 = Always", 0, 2) CreateClientConVar("acf_cl_particlemul", 1, true, true, "Multiplier for the density of ACF effects.", 0.1, 1) CreateClientConVar("acf_mobilityropelinks", 1, true, true) CreateClientConVar("acf_maxroundsdisplay", 16, true, false, "Maximum rounds to display before using bulk display (0 to only display bulk)", 0, 5000) + CreateClientConVar("acf_drawboxes", 1, true, false, "Whether or not to draw hitboxes on ACF entities", 0, 1) + CreateClientConVar("acf_legalhints", 1, true, true, "If enabled, ACF will throw a warning hint whenever an entity gets disabled.", 0, 1) + CreateClientConVar("acf_debris", 1, true, false, "Toggles ACF Debris.", 0, 1) + CreateClientConVar("acf_debris_collision", 0, true, false, "Toggles debris collisions with other entities.", 0, 1) + CreateClientConVar("acf_debris_gibmultiplier", 1, true, false, "The amount of gibs spawned when created by ACF debris.", 0, 1) + CreateClientConVar("acf_debris_giblifetime", 60, true, false, "Defines lifetime in seconds of each debris gib.", 1, 300) + CreateClientConVar("acf_debris_lifetime", 60, true, false, "Defines lifetime in seconds of each debris entity.", 1, 300) -- Display Info Bubble ---------------------- local ShowInfo = GetConVar("acf_show_entity_info") @@ -196,7 +160,7 @@ elseif CLIENT then --------------------------------------------- -- Custom Tool Category --------------------- - ACF.CustomToolCategory = CreateClientConVar("acf_tool_category", 0, true, false) + ACF.CustomToolCategory = CreateClientConVar("acf_tool_category", 0, true, false, "If enabled, ACF tools will be put inside their own category.", 0, 1) if ACF.CustomToolCategory:GetBool() then language.Add("spawnmenu.tools.acf", "ACF") @@ -207,125 +171,44 @@ elseif CLIENT then end) end --------------------------------------------- -end - -timer.Simple(0, function() - for _, Table in pairs(ACF.Classes.GunClass) do - PrecacheParticleSystem(Table["muzzleflash"]) - end -end) - -function switch(cases,arg) local Var = (cases[arg] or cases["default"]) return Var end - --- changes here will be automatically reflected in the armor properties tool -function ACF_CalcArmor(Area, Ductility, Mass) - return (Mass * 1000 / Area / 0.78) / (1 + Ductility) ^ 0.5 * ACF.ArmorMod -end - -function ACF_MuzzleVelocity(Propellant, Mass) - local PEnergy = ACF.PBase * ((1 + Propellant) ^ ACF.PScale - 1) - local Speed = ((PEnergy * 2000 / Mass) ^ ACF.MVScale) - local Final = Speed -- - Speed * math.Clamp(Speed/2000,0,0.5) - - return Final -end - -function ACF_Kinetic(Speed, Mass, LimitVel) - LimitVel = LimitVel or 99999 - Speed = Speed / 39.37 - - local Energy = { - Kinetic = (Mass * (Speed ^ 2)) / 2000, --Energy in KiloJoules - Momentum = Speed * Mass, - } - local KE = (Mass * (Speed ^ ACF.KinFudgeFactor)) / 2000 + Energy.Momentum - - Energy.Penetration = math.max(KE - (math.max(Speed - LimitVel, 0) ^ 2) / (LimitVel * 5) * (KE / 200) ^ 0.95, KE * 0.1) - --Energy.Penetration = math.max( KE - (math.max(Speed-LimitVel,0)^2)/(LimitVel*5) * (KE/200)^0.95 , KE*0.1 ) - --Energy.Penetration = math.max(Energy.Momentum^ACF.KinFudgeFactor - math.max(Speed-LimitVel,0)/(LimitVel*5) * Energy.Momentum , Energy.Momentum*0.1) - return Energy -end + -- Clientside Updating -------------------------- + net.Receive("ACF_UpdateEntity", function() + local Entity = net.ReadEntity() -function ACF_CVarChangeCallback(CVar, _, New) - if CVar == "acf_healthmod" then - ACF.Threshold = 264.7 / math.max(New, 0.01) - print("Health Mod changed to a factor of " .. New) - elseif CVar == "acf_armormod" then - ACF.ArmorMod = 1 * math.max(New, 0) - print("Armor Mod changed to a factor of " .. New) - elseif CVar == "acf_ammomod" then - ACF.AmmoMod = 1 * math.max(New, 0.01) - print("Ammo Mod changed to a factor of " .. New) - elseif CVar == "acf_fuelrate" then - local Value = tonumber(New) or 1 - - ACF.FuelRate = math.max(Value, 0.01) - - print("Fuel Rate changed to a factor of " .. Value) - elseif CVar == "acf_gunfire" then - ACF.GunfireEnabled = tobool(New) - local text = "disabled" - - if ACF.GunfireEnabled then - text = "enabled" - end + timer.Simple(0.5, function() + if not IsValid(Entity) then return end + if not isfunction(Entity.Update) then return end - print("ACF Gunfire has been " .. text) - end + Entity:Update() + end) + end) + --------------------------------------------- end -do -- ACF Notify ----------------------------------- +do -- Player loaded hook + -- PlayerInitialSpawn isn't reliable when it comes to network messages + -- So we'll ask the clientside to tell us when it's actually ready to send and receive net messages + -- For more info, see: https://wiki.facepunch.com/gmod/GM:PlayerInitialSpawn if SERVER then - function ACF_SendNotify(ply, success, msg) - net.Start("ACF_Notify") - net.WriteBit(success) - net.WriteString(msg or "") - net.Send(ply) - end - else - local function ACF_Notify() - local Type = NOTIFY_ERROR - - if tobool(net.ReadBit()) then - Type = NOTIFY_GENERIC - else - surface.PlaySound("buttons/button10.wav") - end + util.AddNetworkString("ACF_PlayerLoaded") - GAMEMODE:AddNotify(net.ReadString(), Type, 7) - end + net.Receive("ACF_PlayerLoaded", function(_, Player) + hook.Run("ACF_OnPlayerLoaded", Player) + end) + else + hook.Add("InitPostEntity", "ACF Player Loaded", function() + net.Start("ACF_PlayerLoaded") + net.SendToServer() - net.Receive("ACF_Notify", ACF_Notify) + hook.Remove("InitPostEntity", "ACF Player Loaded") + end) end -end ------------------------------------------------ - -do -- Render Damage -------------------------------- - hook.Add("PlayerInitialSpawn", "renderdamage", function(ply) - local Table = {} - - for _, v in pairs(ents.GetAll()) do - if v.ACF and v.ACF.PrHealth then - table.insert(Table, { - ID = v:EntIndex(), - Health = v.ACF.Health, - MaxHealth = v.ACF.MaxHealth - }) - end - end - - if next(Table) then - net.Start("ACF_RenderDamage") - net.WriteTable(Table) - net.Send(ply) - end - end) -end ------------------------------------------------ +end --Stupid workaround red added to precache timescaling. hook.Add("Think", "Update ACF Internal Clock", function() ACF.CurTime = CurTime() - ACF.SysTime = SysTime() end) do -- Smoke/Wind ----------------------------------- @@ -372,18 +255,14 @@ do -- Smoke/Wind ----------------------------------- end end) - local function sendSmokeWind(ply) + hook.Add("ACF_OnPlayerLoaded", "ACF Send Smoke Wind", function(Player) net.Start("acf_smokewind") net.WriteFloat(ACF.SmokeWind) - net.Send(ply) - end - - hook.Add("PlayerInitialSpawn", "ACF_SendSmokeWind", sendSmokeWind) + net.Send(Player) + end) else - local function recvSmokeWind() + net.Receive("acf_smokewind", function() ACF.SmokeWind = net.ReadFloat() - end - - net.Receive("acf_smokewind", recvSmokeWind) + end) end end ------------------------------------------------ diff --git a/lua/acf/base/data_vars/cl_data_vars.lua b/lua/acf/base/data_vars/cl_data_vars.lua new file mode 100644 index 000000000..8c716c94b --- /dev/null +++ b/lua/acf/base/data_vars/cl_data_vars.lua @@ -0,0 +1,166 @@ +local ACF = ACF +local Client = ACF.ClientData +local Server = ACF.ServerData +local Queued = { Client = {}, Server = {} } +local LastSent = { Client = {}, Server = {} } + +local function PrepareQueue(Type, Values, Result) + local Queue = Queued[Type] + + if not next(Queue) then return end + + local Sent = LastSent[Type] + local Data = {} + + for K in pairs(Queue) do + local Value = Values[K] + + if Value ~= Sent[K] then + Data[K] = Value + end + + Queue[K] = nil + end + + Result[Type] = Data +end + +local function SendQueued() + local Result = {} + + PrepareQueue("Client", Client, Result) + PrepareQueue("Server", Server, Result) + + if next(Result) then + local JSON = util.TableToJSON(Result) + + net.Start("ACF_DataVarNetwork") + net.WriteString(JSON) + net.SendToServer() + end +end + +local function NetworkData(Key, IsServer) + local Type = IsServer and "Server" or "Client" + local Destiny = Queued[Type] + + if Destiny[Key] then return end -- Already queued + + Destiny[Key] = true + + -- Avoiding net message spam by sending all the events of a tick at once + if timer.Exists("ACF Network Data Vars") then return end + + timer.Create("ACF Network Data Vars", 0, 1, SendQueued) +end + +do -- Server data var syncronization + local function ProcessData(Values, Received) + if not Received then return end + + for K, V in pairs(Received) do + if Values[K] ~= V then + Values[K] = V + + hook.Run("ACF_OnServerDataUpdate", nil, K, V) + end + + Received[K] = nil + end + end + + net.Receive("ACF_DataVarNetwork", function(_, Player) + local Received = util.JSONToTable(net.ReadString()) + + if IsValid(Player) then return end -- NOTE: Can this even happen? + + ProcessData(Server, Received) + end) + + -- We'll request the server data vars as soon as the player starts moving + hook.Add("InitPostEntity", "ACF Request Data Vars", function() + net.Start("ACF_RequestDataVars") + net.SendToServer() + + hook.Remove("InitPostEntity", "ACF Request Data Vars") + end) +end + +do -- Client data getter functions + local function GetData(Key, Default) + if Key == nil then return Default end + + local Value = Client[Key] + + if Value ~= nil then return Value end + + return Default + end + + function ACF.GetAllClientData(NoCopy) + if NoCopy then return Client end + + local Result = {} + + for K, V in pairs(Client) do + Result[K] = V + end + + return Result + end + + function ACF.GetClientBool(Key, Default) + return tobool(GetData(Key, Default)) + end + + function ACF.GetClientNumber(Key, Default) + local Value = GetData(Key, Default) + + return ACF.CheckNumber(Value, 0) + end + + function ACF.GetClientString(Key, Default) + local Value = GetData(Key, Default) + + return ACF.CheckString(Value, "") + end + + ACF.GetClientData = GetData + ACF.GetClientRaw = GetData +end + +do -- Client data setter function + function ACF.SetClientData(Key, Value, Forced) + if not isstring(Key) then return end + + Value = Value or false + + if Forced or Client[Key] ~= Value then + Client[Key] = Value + + hook.Run("ACF_OnClientDataUpdate", LocalPlayer(), Key, Value) + + NetworkData(Key) + end + end +end + +do -- Server data setter function + function ACF.SetServerData(Key, Value, Forced) + if not isstring(Key) then return end + + local Player = LocalPlayer() + + if not ACF.CanSetServerData(Player) then return end + + Value = Value or false + + if Forced or Server[Key] ~= Value then + Server[Key] = Value + + hook.Run("ACF_OnServerDataUpdate", Player, Key, Value) + + NetworkData(Key, true) + end + end +end diff --git a/lua/acf/base/data_vars/cl_panel_functions.lua b/lua/acf/base/data_vars/cl_panel_functions.lua new file mode 100644 index 000000000..fb72bf2f4 --- /dev/null +++ b/lua/acf/base/data_vars/cl_panel_functions.lua @@ -0,0 +1,231 @@ +local ACF = ACF +local PanelMeta = FindMetaTable("Panel") + +function PanelMeta:DefineSetter(Function) + if not isfunction(Function) then return end + + self.SetCustomValue = Function +end + +do -- Tracker and Setter panel functions + ACF.DataPanels = ACF.DataPanels or { + Server = {}, + Client = {}, + Panels = {}, + } + + --------------------------------------------------------------------------- + + local DataPanels = ACF.DataPanels + local Queued = { Client = {}, Server = {} } + local IsQueued + + local function RefreshValues() + for Realm, Data in pairs(Queued) do + local SetFunction = ACF["Set" .. Realm .. "Data"] + local Variables = ACF[Realm .. "Data"] + + for Key in pairs(Data) do + SetFunction(Key, Variables[Key], true) + + Data[Key] = nil + end + end + + IsQueued = nil + end + + local function QueueRefresh(Realm, Key) + local Data = Queued[Realm] + + if Data[Key] then return end + + Data[Key] = true + + if not IsQueued then + timer.Create("ACF Refresh Data Vars", 0, 1, RefreshValues) + + IsQueued = true + end + end + + --------------------------------------------------------------------------- + + local function GetSubtable(Table, Key) + local Result = Table[Key] + + if not Result then + Result = {} + Table[Key] = Result + end + + return Result + end + + local function StoreData(Destiny, Key, Realm, Value, Store) + local BaseData = GetSubtable(DataPanels[Destiny], Key) + local TypeData = GetSubtable(BaseData, Realm) + + TypeData[Value] = Store or true + end + + local function ClearFromType(Data, Realm, Panel) + local Saved = Data[Realm] + + if not Saved then return end + + for Name, Mode in pairs(Saved) do + DataPanels[Realm][Name][Mode][Panel] = nil -- Weed lmao + end + end + + local function ClearData(Panel) + local Panels = DataPanels.Panels + local Data = Panels[Panel] + + -- Apparently this can be called twice + if Data then + ClearFromType(Data, "Server", Panel) + ClearFromType(Data, "Client", Panel) + end + + Panels[Panel] = nil + end + + local function DefaultSetter(Panel, Value, Forced) + local ServerVar = Panel.ServerVar + local ClientVar = Panel.ClientVar + + if not (ServerVar or ClientVar) then + if Panel.SetCustomValue then + Value = Panel:SetCustomValue(nil, nil, Value) or Value + end + + return Panel:OldSet(Value) + end + + if ServerVar then + ACF.SetServerData(ServerVar, Value, Forced) + end + + if ClientVar then + ACF.SetClientData(ClientVar, Value, Forced) + end + end + + local function HijackThink(Panel) + local OldThink = Panel.Think + local Player = LocalPlayer() + + Panel.Enabled = Panel:IsEnabled() + + function Panel:Think(...) + if self.ServerVar then + local Enabled = ACF.CanSetServerData(Player) + + if self.Enabled ~= Enabled then + self.Enabled = Enabled + + self:SetEnabled(Enabled) + end + end + + if OldThink then + return OldThink(self, ...) + end + end + end + + local function HijackRemove(Panel) + local OldRemove = Panel.Remove + + function Panel:Remove(...) + ClearData(self) + + return OldRemove(self, ...) + end + end + + local function HijackFunctions(Panel, SetFunction) + if Panel.Hijacked then return end + + local Setter = Panel[SetFunction] and SetFunction or "SetValue" + + Panel.OldSet = Panel[Setter] + Panel[Setter] = DefaultSetter + Panel.Setter = DefaultSetter + Panel.Hijacked = true + + HijackThink(Panel) + HijackRemove(Panel) + end + + local function UpdatePanels(Panels, Realm, Key, Value, IsTracked) + if not Panels then return end + + for Panel in pairs(Panels) do + if not IsValid(Panel) then + ClearData(Panel) -- Somehow Panel:Remove is not being called + continue + end + + local Result = Value + + if Panel.SetCustomValue then + Result = Panel:SetCustomValue(Realm, Key, Value, IsTracked) + end + + if Result ~= nil then + Panel:OldSet(Result) + end + end + end + + --------------------------------------------------------------------------- + + --- Generates the following functions: + -- Panel:SetClientData(Key, Setter) + -- Panel:TrackClientData(Key, Setter) + -- Panel:SetServerData(Key, Setter) + -- Panel:TrackServerData(Key, Setter) + + for Realm in pairs(Queued) do + PanelMeta["Set" .. Realm .. "Data"] = function(Panel, Key, Setter) + if not isstring(Key) then return end + + local Variables = ACF[Realm .. "Data"] + local SetFunction = ACF["Set" .. Realm .. "Data"] + + StoreData("Panels", Panel, Realm, Key, "Setter") + StoreData(Realm, Key, "Setter", Panel) + + HijackFunctions(Panel, Setter or "SetValue") + + Panel[Realm .. "Var"] = Key + + if Variables[Key] == nil then + SetFunction(Key, Panel:GetValue()) + end + + QueueRefresh(Realm, Key) + end + + PanelMeta["Track" .. Realm .. "Data"] = function(Panel, Key, Setter) + if not isstring(Key) then return end + + StoreData("Panels", Panel, Realm, Key, "Tracker") + StoreData(Realm, Key, "Tracker", Panel) + + HijackFunctions(Panel, Setter or "SetValue") + end + + hook.Add("ACF_On" .. Realm .. "DataUpdate", "ACF Update Panel Values", function(_, Key, Value) + local Data = DataPanels[Realm][Key] + + if not Data then return end -- This variable is not being set or tracked by panels + + UpdatePanels(Data.Setter, Realm, Key, Value, IsTracked) + UpdatePanels(Data.Tracker, Realm, Key, Value, IsTracked) + end) + end +end diff --git a/lua/acf/base/data_vars/sh_data_vars.lua b/lua/acf/base/data_vars/sh_data_vars.lua new file mode 100644 index 000000000..08bb92ba8 --- /dev/null +++ b/lua/acf/base/data_vars/sh_data_vars.lua @@ -0,0 +1,221 @@ +local ACF = ACF + +function ACF.CanSetServerData(Player) + if not IsValid(Player) then return true end -- No player, probably the server + if Player:IsSuperAdmin() then return true end + + local AllowAdmin = ACF.GetServerBool("ServerDataAllowAdmin") + + return AllowAdmin and Player:IsAdmin() +end + +do -- Server data getter functions + local Server = ACF.ServerData + + local function GetData(Key, Default) + if Key == nil then return Default end + + local Value = Server[Key] + + if Value ~= nil then return Value end + + return Default + end + + function ACF.GetAllServerData(NoCopy) + if NoCopy then return Server end + + local Result = {} + + for K, V in pairs(Server) do + Result[K] = V + end + + return Result + end + + function ACF.GetServerBool(Key, Default) + return tobool(GetData(Key, Default)) + end + + function ACF.GetServerNumber(Key, Default) + local Value = GetData(Key, Default) + + return ACF.CheckNumber(Value, 0) + end + + function ACF.GetServerString(Key, Default) + local Value = GetData(Key, Default) + + return ACF.CheckString(Value, "") + end + + ACF.GetServerData = GetData + ACF.GetServerRaw = GetData +end + +do -- Data persisting + ACF.PersistedKeys = ACF.PersistedKeys or {} + ACF.PersistedData = ACF.PersistedData or {} + + local Persist = ACF.PersistedData + local Keys = ACF.PersistedKeys + local Realm = SERVER and "Server" or "Client" + local Values = ACF[Realm .. "Data"] + local Folder = "acf/data_vars" + local File = "stored.json" + local Storing + + local function StoreData() + local Result = {} + + for Key, Default in pairs(Keys) do + local Value = Persist[Key] + + Result[Key] = { + Value = Value, + Default = Default, + } + end + + Storing = nil + + ACF.SaveToJSON(Folder, File, Result, true) + end + + local function UpdateData(Key) + if Keys[Key] == nil then return end + if Values[Key] == nil then return end + + local Value = Values[Key] + + if Persist[Key] ~= Value then + Persist[Key] = Value + + if not Storing then + timer.Create("ACF Store Persisted", 1, 1, StoreData) + + Storing = true + end + end + end + + --- Generates the following functions: + -- ACF.PersistServerData(Key, Default) - Serverside only + -- ACF.PersistClientData(Key, Default) - Clientside only + + ACF["Persist" .. Realm .. "Data"] = function(Key, Default) + if not isstring(Key) then return end + if Default == nil then Default = "nil" end + + Keys[Key] = Default + end + + hook.Add("ACF_On" .. Realm .. "DataUpdate", "ACF Persisted Data", function(_, Key) + UpdateData(Key) + end) + + hook.Add("Initialize", "ACF Load Persisted Data", function() + local Saved = ACF.LoadFromFile(Folder, File) + local SetFunction = ACF["Set" .. Realm .. "Data"] + + if Saved then + for Key, Stored in pairs(Saved) do + if Keys[Key] == nil then continue end + + if Stored.Value ~= "nil" then + SetFunction(Key, Stored.Value) + end + end + end + + -- In case the file doesn't exist or it's missing one of the persisted variables + for Key, Default in pairs(Keys) do + if Persist[Key] ~= nil then continue end + + SetFunction(Key, Default) + end + + hook.Remove("Initialize", "ACF Load Persisted Data") + end) +end + +do -- Data callbacks + ACF.DataCallbacks = ACF.DataCallbacks or { + Server = {}, + Client = {}, + } + + local Callbacks = ACF.DataCallbacks + + --- Generates the following functions: + -- ACF.AddServerDataCallback(Key, Name, Function) + -- ACF.RemoveServerDataCallback(Key, Name) + -- ACF.AddClientDataCallback(Key, Name, Function) + -- ACF.RemoveClientDataCallback(Key, Name) + + for Realm, Callback in pairs(Callbacks) do + local Queue = {} + + local function ProcessQueue() + for Key, Data in pairs(Queue) do + local Store = Callback[Key] + local Player = Data.Player + local Value = Data.Value + + for _, Function in pairs(Store) do + Function(Player, Key, Value) + end + + Queue[Key] = nil + end + end + + ACF["Add" .. Realm .. "DataCallback"] = function(Key, Name, Function) + if not isstring(Key) then return end + if not isstring(Name) then return end + if not isfunction(Function) then return end + + local Store = Callback[Key] + + if not Store then + Callback[Key] = { + [Name] = Function + } + else + Store[Name] = Function + end + end + + ACF["Remove" .. Realm .. "DataCallback"] = function(Key, Name) + if not isstring(Key) then return end + if not isstring(Name) then return end + + local Store = Callback[Key] + + if not Store then return end + + Store[Name] = nil + end + + hook.Add("ACF_On" .. Realm .. "DataUpdate", "ACF Data Callbacks", function(Player, Key, Value) + if not Callback[Key] then return end + + local Data = Queue[Key] + + if not next(Queue) then + timer.Create("ACF Data Callback", 0, 1, ProcessQueue) + end + + if not Data then + Queue[Key] = { + Player = Player, + Value = Value, + } + else + Data.Player = Player + Data.Value = Value + end + end) + end +end diff --git a/lua/acf/base/data_vars/sv_data_vars.lua b/lua/acf/base/data_vars/sv_data_vars.lua new file mode 100644 index 000000000..ee21bdc52 --- /dev/null +++ b/lua/acf/base/data_vars/sv_data_vars.lua @@ -0,0 +1,187 @@ +local ACF = ACF +local Client = ACF.ClientData +local Server = ACF.ServerData +local Queued = {} + +local function PrepareQueue(Type, Values) + local Queue = Queued[Type] + + if not next(Queue) then return end + + local Data = {} + + for K in pairs(Queue) do + Data[K] = Values[K] + Queue[K] = nil + end + + return util.TableToJSON(Data) +end + +local function SendQueued() + local Broadcast = Queued.Broadcast + + if Broadcast then + net.Start("ACF_DataVarNetwork") + net.WriteString(PrepareQueue("Broadcast", Server)) + net.Broadcast() + + Queued.Broadcast = nil + end + + for Player in pairs(Queued) do + net.Start("ACF_DataVarNetwork") + net.WriteString(PrepareQueue(Player, Server)) + net.Send(Player) + + Queued[Player] = nil + end +end + +local function NetworkData(Key, Player) + local Type = IsValid(Player) and Player or "Broadcast" + local Destiny = Queued[Type] + + if Destiny and Destiny[Key] then return end -- Already queued + + if not Destiny then + Queued[Type] = { + [Key] = true + } + else + Destiny[Key] = true + end + + -- Avoiding net message spam by sending all the events of a tick at once + if timer.Exists("ACF Network Data Vars") then return end + + timer.Create("ACF Network Data Vars", 0, 1, SendQueued) +end + +do -- Data syncronization + util.AddNetworkString("ACF_DataVarNetwork") + util.AddNetworkString("ACF_RequestDataVars") + + local function ProcessData(Player, Type, Values, Received) + local Data = Received[Type] + + if not Data then return end + + local Hook = "ACF_On" .. Type .. "DataUpdate" + + for K, V in pairs(Data) do + if Values[K] ~= V then + Values[K] = V + + hook.Run(Hook, Player, K, V) + end + + Data[K] = nil + end + end + + net.Receive("ACF_DataVarNetwork", function(_, Player) + local Received = util.JSONToTable(net.ReadString()) + + if not IsValid(Player) then return end -- NOTE: Can this even happen? + + ProcessData(Player, "Client", Client[Player], Received) + + -- There's a check on the clientside for this, but we won't trust it + if ACF.CanSetServerData(Player) then + ProcessData(Player, "Server", Server, Received) + else + local Data = Received.Server + + if not Data then return end + + -- This player shouldn't be updating these values + -- So we'll just force him to update with the correct stuff + for Key in pairs(Data) do + NetworkData(Key, Player) + end + end + end) + + net.Receive("ACF_RequestDataVars", function(_, Player) + -- Server data var syncronization + for Key in pairs(Server) do + NetworkData(Key, Player) + end + end) + + hook.Add("PlayerInitialSpawn", "ACF Data Var Syncronization", function(Player) + Client[Player] = {} + end) + + hook.Add("PlayerDisconnected", "ACF Data Var Syncronization", function(Player) + Client[Player] = nil + Queued[Player] = nil + end) +end + +do -- Client data getter functions + local function GetData(Player, Key, Default) + if not IsValid(Player) then return Default end + if Key == nil then return Default end + + local Data = Client[Player] + local Value = Data and Data[Key] + + if Value ~= nil then return Value end + + return Default + end + + function ACF.GetAllClientData(Player, NoCopy) + if not IsValid(Player) then return {} end + + local Data = Client[Player] + + if not Data then return {} end + if NoCopy then return Data end + + local Result = {} + + for K, V in pairs(Data) do + Result[K] = V + end + + return Result + end + + function ACF.GetClientBool(Player, Key, Default) + return tobool(GetData(Player, Key, Default)) + end + + function ACF.GetClientNumber(Player, Key, Default) + local Value = GetData(Player, Key, Default) + + return ACF.CheckNumber(Value, 0) + end + + function ACF.GetClientString(Player, Key, Default) + local Value = GetData(Player, Key, Default) + + return ACF.CheckString(Value, "") + end + + ACF.GetClientData = GetData + ACF.GetClientRaw = GetData +end + +do -- Server data setter function + function ACF.SetServerData(Key, Value, Forced) + if not isstring(Key) then return end + + Value = Value or false + + if Forced or Server[Key] ~= Value then + Server[Key] = Value + + hook.Run("ACF_OnServerDataUpdate", nil, Key, Value) + + NetworkData(Key) + end + end +end diff --git a/lua/acf/base/permission/sv_permissions.lua b/lua/acf/base/permission/sv_permissions.lua index 7eca3ff99..f7f4d3f70 100644 --- a/lua/acf/base/permission/sv_permissions.lua +++ b/lua/acf/base/permission/sv_permissions.lua @@ -100,7 +100,7 @@ end hook.Add("Initialize", "ACF_LoadSafesForMap", function() if not getMapSZs() then - print("Safezone file " .. getMapFilename() .. " is missing, invalid or corrupt! Safezones will not be restored this time.") + print("Safezone file " .. getMapFilename() .. " is missing, invalid or corrupt! Safezones will not be restored this time.") end end) @@ -128,7 +128,7 @@ concommand.Add("ACF_AddSafeZone", function(ply, _, args) end or msgtoconsole if not args[1] then - printmsg(HUD_PRINTCONSOLE, " - Add a safezone as an AABB box." .. "\n Input a name and six numbers. First three numbers are minimum co-ords, last three are maxs." .. "\n Example; ACF_addsafezone airbase -500 -500 0 500 500 1000") + printmsg(HUD_PRINTCONSOLE, " - Add a safezone as an AABB box." .. "\n Input a name and six numbers. First three numbers are minimum co-ords, last three are maxs." .. "\n Example; ACF_addsafezone airbase -500 -500 0 500 500 1000") return false end @@ -189,7 +189,7 @@ concommand.Add("ACF_RemoveSafeZone", function(ply, _, args) end or msgtoconsole if not args[1] then - printmsg(HUD_PRINTCONSOLE, " - Delete a safezone using its name." .. "\n Input a safezone name. If it exists, it will be removed." .. "\n Deletion is not permanent until safezones are saved.") + printmsg(HUD_PRINTCONSOLE, " - Delete a safezone using its name." .. "\n Input a safezone name. If it exists, it will be removed." .. "\n Deletion is not permanent until safezones are saved.") return false end diff --git a/lua/acf/base/sh_classes.lua b/lua/acf/base/sh_classes.lua new file mode 100644 index 000000000..7d2403c75 --- /dev/null +++ b/lua/acf/base/sh_classes.lua @@ -0,0 +1,632 @@ +local ACF = ACF + +do -- Basic class registration functions + function ACF.AddSimpleClass(ID, Destiny, Data) + if not ID then return end + if not Data then return end + if not Destiny then return end + + local Class = Destiny[ID] + + if not Class then + Class = { + ID = ID, + } + + Destiny[ID] = Class + end + + for K, V in pairs(Data) do + Class[K] = V + end + + hook.Run("ACF_OnNewSimpleClass", ID, Class) + + return Class + end + + function ACF.AddClassGroup(ID, Destiny, Data) + if not ID then return end + if not Data then return end + if not Destiny then return end + + local Group = Destiny[ID] + + if not Group then + Group = { + ID = ID, + Lookup = {}, + Items = {}, + Count = 0, + } + + Destiny[ID] = Group + end + + for K, V in pairs(Data) do + Group[K] = V + end + + hook.Run("ACF_OnNewClassGroup", ID, Group) + + return Group + end + + local Groups = {} + + local function GetDestinyData(Destiny) + local Data = Groups[Destiny] + + if not Data then + Data = {} + + Groups[Destiny] = Data + end + + return Data + end + + function ACF.AddGroupedClass(ID, GroupID, Destiny, Data) + if not ID then return end + if not Data then return end + if not GroupID then return end + if not Destiny then return end + if not Destiny[GroupID] then return end + + local Group = Destiny[GroupID] + local Class = Group.Lookup[ID] + + if not Class then + Class = { + ID = ID, + Class = Group, + ClassID = GroupID, + } + + Group.Count = Group.Count + 1 + Group.Lookup[ID] = Class + Group.Items[Group.Count] = Class + + local DestinyData = GetDestinyData(Destiny) + DestinyData[ID] = Group + end + + for K, V in pairs(Data) do + Class[K] = V + end + + hook.Run("ACF_OnNewGroupedClass", ID, Group, Class) + + return Class + end + + function ACF.GetClassGroup(Destiny, Name) + if not istable(Destiny) then return end + if not Name then return end + + local Data = Groups[Destiny] + local Class = Data and Data[Name] + + if Class then return Class end + + local Group = Destiny[Name] + + if not Group then return end + + return Group.IsScalable and Group + end +end + +local AddSimpleClass = ACF.AddSimpleClass +local AddClassGroup = ACF.AddClassGroup +local AddGroupedClass = ACF.AddGroupedClass + +local function AddSboxLimit(Data) + if CLIENT then return end + if ConVarExists("sbox_max" .. Data.Name) then return end + + CreateConVar("sbox_max" .. Data.Name, + Data.Amount, + FCVAR_ARCHIVE + FCVAR_NOTIFY, + Data.Text or "") +end + +do -- Class registration function + local Classes = {} + local Queued = {} + + local function CreateInstance(Class) + local New = {} + + setmetatable(New, { __index = table.Copy(Class) }) + + if New.OnCalled then + New:OnCalled() + end + + return New + end + + local function QueueBaseClass(ID, Base) + if not Queued[Base] then + Queued[Base] = { [ID] = true } + else + Queued[Base][ID] = true + end + end + + local function AttachMetaTable(Class, Base) + local OldMeta = getmetatable(Class) or {} + + if Base then + local BaseClass = Classes[Base] + + if BaseClass then + Class.BaseClass = BaseClass + OldMeta.__index = BaseClass + else + QueueBaseClass(Class.ID, Base) + end + end + + OldMeta.__call = function() + return CreateInstance(Class) + end + + setmetatable(Class, OldMeta) + + timer.Simple(0, function() + if Class.OnLoaded then + Class:OnLoaded() + end + + hook.Run("ACF_OnClassLoaded", Class.ID, Class) + + Class.Loaded = true + end) + end + + function ACF.RegisterClass(ID, Base, Destiny) + if not Classes[ID] then + Classes[ID] = {} + end + + local Class = Classes[ID] + Class.ID = ID + + AttachMetaTable(Class, Base) + + if Queued[ID] then + for K in pairs(Queued[ID]) do + AttachMetaTable(Classes[K], ID) + end + + Queued[ID] = nil + end + + if Destiny then + Destiny[ID] = Class + end + + return Class + end +end + +do -- Weapon registration functions + ACF.Classes.Weapons = ACF.Classes.Weapons or {} + + local Weapons = ACF.Classes.Weapons + + function ACF.RegisterWeaponClass(ID, Data) + local Group = AddClassGroup(ID, Weapons, Data) + + if not Group.LimitConVar then + Group.LimitConVar = { + Name = "_acf_weapon", + Amount = 16, + Text = "Maximum amount of ACF weapons a player can create." + } + end + + if not Group.Cleanup then + Group.Cleanup = "acf_gun" + end + + AddSboxLimit(Group.LimitConVar) + + if Group.MuzzleFlash then + PrecacheParticleSystem(Group.MuzzleFlash) + end + + return Group + end + + function ACF.RegisterWeapon(ID, ClassID, Data) + local Class = AddGroupedClass(ID, ClassID, Weapons, Data) + + Class.Destiny = "Weapons" + + if Class.MuzzleFlash then + PrecacheParticleSystem(Class.MuzzleFlash) + end + + return Class + end +end + +do -- Ammo crate registration function + ACF.Classes.Crates = ACF.Classes.Crates or {} + + local Crates = ACF.Classes.Crates + + function ACF.RegisterCrate(ID, Data) + return AddSimpleClass(ID, Crates, Data) + end +end + +do -- Ammo type registration function + ACF.Classes.AmmoTypes = ACF.Classes.AmmoTypes or {} + + local RegisterClass = ACF.RegisterClass + local Types = ACF.Classes.AmmoTypes + + function ACF.RegisterAmmoType(ID, Base) + return RegisterClass(ID, Base, Types) + end +end + +do -- Engine registration functions + ACF.Classes.Engines = ACF.Classes.Engines or {} + + local Engines = ACF.Classes.Engines + + function ACF.RegisterEngineClass(ID, Data) + local Group = AddClassGroup(ID, Engines, Data) + + if not Group.LimitConVar then + Group.LimitConVar = { + Name = "_acf_engine", + Amount = 16, + Text = "Maximum amount of ACF engines a player can create." + } + end + + AddSboxLimit(Group.LimitConVar) + + return Group + end + + function ACF.RegisterEngine(ID, ClassID, Data) + local Class = AddGroupedClass(ID, ClassID, Engines, Data) + + if not Class.Sound then + Class.Sound = "vehicles/junker/jnk_fourth_cruise_loop2.wav" + end + + return Class + end +end + +do -- Engine type registration function + ACF.Classes.EngineTypes = ACF.Classes.EngineTypes or {} + + local Types = ACF.Classes.EngineTypes + + function ACF.RegisterEngineType(ID, Data) + return AddSimpleClass(ID, Types, Data) + end +end + +do -- Fuel tank registration functions + ACF.Classes.FuelTanks = ACF.Classes.FuelTanks or {} + + local FuelTanks = ACF.Classes.FuelTanks + + function ACF.RegisterFuelTankClass(ID, Data) + local Group = AddClassGroup(ID, FuelTanks, Data) + + if not Group.LimitConVar then + Group.LimitConVar = { + Name = "_acf_fueltank", + Amount = 32, + Text = "Maximum amount of ACF fuel tanks a player can create." + } + end + + AddSboxLimit(Group.LimitConVar) + + return Group + end + + function ACF.RegisterFuelTank(ID, ClassID, Data) + local Class = AddGroupedClass(ID, ClassID, FuelTanks, Data) + + if Class.IsExplosive == nil then + Class.IsExplosive = true + end + + return Class + end +end + +do -- Fuel type registration function + ACF.Classes.FuelTypes = ACF.Classes.FuelTypes or {} + + local Types = ACF.Classes.FuelTypes + + function ACF.RegisterFuelType(ID, Data) + return AddSimpleClass(ID, Types, Data) + end +end + +do -- Gearbox registration functions + ACF.Classes.Gearboxes = ACF.Classes.Gearboxes or {} + + local Gearboxes = ACF.Classes.Gearboxes + + function ACF.RegisterGearboxClass(ID, Data) + local Group = AddClassGroup(ID, Gearboxes, Data) + + if not Group.Sound then + Group.Sound = "buttons/lever7.wav" + end + + if not Group.LimitConVar then + Group.LimitConVar = { + Name = "_acf_gearbox", + Amount = 24, + Text = "Maximum amount of ACF gearboxes a player can create." + } + end + + AddSboxLimit(Group.LimitConVar) + + return Group + end + + function ACF.RegisterGearbox(ID, ClassID, Data) + return AddGroupedClass(ID, ClassID, Gearboxes, Data) + end +end + +do -- Component registration functions + ACF.Classes.Components = ACF.Classes.Components or {} + + local Components = ACF.Classes.Components + + function ACF.RegisterComponentClass(ID, Data) + local Group = AddClassGroup(ID, Components, Data) + + if not Group.LimitConVar then + Group.LimitConVar = { + Name = "_acf_misc", + Amount = 32, + Text = "Maximum amount of ACF components a player can create." + } + end + + AddSboxLimit(Group.LimitConVar) + + return Group + end + + function ACF.RegisterComponent(ID, ClassID, Data) + return AddGroupedClass(ID, ClassID, Components, Data) + end +end + +do -- Sensor registration functions + ACF.Classes.Sensors = ACF.Classes.Sensors or {} + + local Sensors = ACF.Classes.Sensors + + function ACF.RegisterSensorClass(ID, Data) + local Group = AddClassGroup(ID, Sensors, Data) + + if not Group.LimitConVar then + Group.LimitConVar = { + Name = "_acf_sensor", + Amount = 16, + Text = "Maximum amount of ACF sensors a player can create." + } + end + + AddSboxLimit(Group.LimitConVar) + + return Group + end + + function ACF.RegisterSensor(ID, ClassID, Data) + return AddGroupedClass(ID, ClassID, Sensors, Data) + end +end + +do -- Piledriver registration functions + ACF.Classes.Piledrivers = ACF.Classes.Piledrivers or {} + + local Piledrivers = ACF.Classes.Piledrivers + + function ACF.RegisterPiledriverClass(ID, Data) + local Group = AddClassGroup(ID, Piledrivers, Data) + + Group.Cyclic = math.min(120, Group.Cyclic or 60) + + if not Group.LimitConVar then + Group.LimitConVar = { + Name = "_acf_piledriver", + Amount = 4, + Text = "Maximum amount of ACF piledrivers a player can create." + } + end + + if not Group.Cleanup then + Group.Cleanup = "acf_piledriver" + end + + AddSboxLimit(Group.LimitConVar) + + return Group + end + + function ACF.RegisterPiledriver(ID, ClassID, Data) + return AddGroupedClass(ID, ClassID, Piledrivers, Data) + end +end + +do -- Entity class registration function + ACF.Classes.Entities = ACF.Classes.Entities or {} + + local Entities = ACF.Classes.Entities + + local function GetEntityTable(Class) + if Entities[Class] then return Entities[Class] end + + local Table = { + Lookup = {}, + Count = 0, + List = {}, + } + + Entities[Class] = Table + + return Table + end + + local function AddArguments(Entity, Arguments) + local Lookup = Entity.Lookup + local Count = Entity.Count + local List = Entity.List + + for _, V in ipairs(Arguments) do + if not Lookup[V] then + Count = Count + 1 + + Lookup[V] = true + List[Count] = V + end + end + + Entity.Count = Count + + return List + end + + function ACF.RegisterEntityClass(Class, Function, ...) + if not isstring(Class) then return end + if not isfunction(Function) then return end + + local Entity = GetEntityTable(Class) + local Arguments = istable(...) and ... or { ... } + local List = AddArguments(Entity, Arguments) + + Entity.Spawn = Function + + duplicator.RegisterEntityClass(Class, Function, "Pos", "Angle", "Data", unpack(List)) + end + + function ACF.AddEntityArguments(Class, ...) + if not isstring(Class) then return end + + local Entity = GetEntityTable(Class) + local Arguments = istable(...) and ... or { ... } + local List = AddArguments(Entity, Arguments) + + if Entity.Spawn then + duplicator.RegisterEntityClass(Class, Entity.Spawn, "Pos", "Angle", "Data", unpack(List)) + end + end + + function ACF.GetEntityClass(Class) + if not Class then return end + + return Entities[Class] + end + + function ACF.GetEntityArguments(Class) + if not isstring(Class) then return end + + local Entity = GetEntityTable(Class) + local List = {} + + for K, V in ipairs(Entity.List) do + List[K] = V + end + + return List + end + + function ACF.CreateEntity(Class, Player, Position, Angles, Data, NoUndo) + if not isstring(Class) then return false end + + local ClassData = ACF.GetEntityClass(Class) + + if not ClassData then return false, Class .. " is not a registered ACF entity class." end + if not ClassData.Spawn then return false, Class .. " doesn't have a spawn function assigned to it." end + + local HookResult, HookMessage = hook.Run("ACF_CanCreateEntity", Class, Player, Position, Angles, Data) + + if HookResult == false then return false, HookMessage end + + local Entity = ClassData.Spawn(Player, Position, Angles, Data) + + if not IsValid(Entity) then return false, "The spawn function for" .. Class .. " didn't return a value entity." end + + Entity:Activate() + + if CPPI then + Entity:CPPISetOwner(Player) + end + + if not NoUndo then + undo.Create(Entity.Name or Class) + undo.AddEntity(Entity) + undo.SetPlayer(Player) + undo.Finish() + end + + return true, Entity + end + + function ACF.UpdateEntity(Entity, Data) + if not IsValid(Entity) then return false, "Can't update invalid entities." end + if not isfunction(Entity.Update) then return false, "This entity does not support updating." end + + Data = istable(Data) and Data or {} + + local HookResult, HookMessage = hook.Run("ACF_CanUpdateEntity", Entity, Data) + + if HookResult == false then return false, "Couldn't update entity: " .. HookMessage end + + local Result, Message = Entity:Update(Data) + + if not Result then Message = "Couldn't update entity: " .. Message end + + return Result, Message + end +end + +do -- Discontinued functions + function ACF_defineGunClass(ID) + print("Attempted to register weapon class " .. ID .. " with a discontinued function. Use ACF.RegisterWeaponClass instead.") + end + + function ACF_defineGun(ID) + print("Attempted to register weapon " .. ID .. " with a discontinued function. Use ACF.RegisterWeapon instead.") + end + + function ACF_DefineEngine(ID) + print("Attempted to register engine " .. ID .. " with a discontinued function. Use ACF.RegisterEngine instead.") + end + + function ACF_DefineGearbox(ID) + print("Attempted to register gearbox " .. ID .. " with a discontinued function. Use ACF.RegisterGearbox instead.") + end + + function ACF_DefineFuelTank(ID) + print("Attempted to register fuel tank type " .. ID .. " with a discontinued function. Use ACF.RegisterFuelTankClass instead.") + end + + function ACF_DefineFuelTankSize(ID) + print("Attempted to register fuel tank " .. ID .. " with a discontinued function. Use ACF.RegisterFuelTank instead.") + end +end diff --git a/lua/acf/base/sh_round_functions.lua b/lua/acf/base/sh_round_functions.lua new file mode 100644 index 000000000..42d16c842 --- /dev/null +++ b/lua/acf/base/sh_round_functions.lua @@ -0,0 +1,326 @@ +local ACF = ACF +local Classes = ACF.Classes + +local function GetWeaponSpecs(ToolData) + local Source = Classes[ToolData.Destiny] + local Class = Source and ACF.GetClassGroup(Source, ToolData.Weapon) + + if not Class then return end + + if not Class.IsScalable then + local Weapon = Class.Lookup[ToolData.Weapon] + local Round = Weapon.Round + + return Weapon.Caliber, Round.MaxLength, Round.PropMass + end + + local Bounds = Class.Caliber + local Round = Class.Round + local Caliber = math.Clamp(ToolData.Caliber or Bounds.Base, Bounds.Min, Bounds.Max) + local Scale = Caliber / Bounds.Base + + return Caliber, Round.MaxLength * Scale, Round.PropMass * Scale +end + +function ACF.RoundBaseGunpowder(ToolData, Data) + local Caliber, MaxLength, PropMass = GetWeaponSpecs(ToolData) + local GUIData = {} + + if not Caliber then return Data, GUIData end + + Data.Caliber = Caliber * 0.1 -- Bullet caliber will have to stay in cm + Data.FrArea = 3.1416 * (Data.Caliber * 0.5) ^ 2 + + GUIData.MaxRoundLength = math.Round(MaxLength * (Data.LengthAdj or 1), 2) + GUIData.MinPropLength = 0.01 + GUIData.MinProjLength = math.Round(Data.Caliber * 1.5, 2) + + local DesiredProp = math.Round(PropMass * 1000 / ACF.PDensity / Data.FrArea, 2) + local AllowedProp = GUIData.MaxRoundLength - GUIData.MinProjLength + + GUIData.MaxPropLength = math.min(DesiredProp, AllowedProp) + GUIData.MaxProjLength = GUIData.MaxRoundLength - GUIData.MinPropLength + + ACF.UpdateRoundSpecs(ToolData, Data, GUIData) + + return Data, GUIData +end + +function ACF.UpdateRoundSpecs(ToolData, Data, GUIData) + GUIData = GUIData or Data + + Data.Priority = Data.Priority or "Projectile" + Data.Tracer = ToolData.Tracer and math.Round(Data.Caliber * 0.15, 2) or 0 + + local Projectile = math.Clamp(ToolData.Projectile + Data.Tracer, GUIData.MinProjLength, GUIData.MaxProjLength) + local Propellant = math.Clamp(ToolData.Propellant, GUIData.MinPropLength, GUIData.MaxPropLength) + + if Data.Priority == "Projectile" then + Propellant = math.min(Propellant, GUIData.MaxRoundLength - Projectile, GUIData.MaxPropLength) + elseif Data.Priority == "Propellant" then + Projectile = math.min(Projectile, GUIData.MaxRoundLength - Propellant, GUIData.MaxProjLength) + end + + Data.ProjLength = math.Round(Projectile, 2) - Data.Tracer + Data.PropLength = math.Round(Propellant, 2) + Data.PropMass = Data.FrArea * ACF.AmmoCaseScale ^ 2 * (Data.PropLength * ACF.PDensity * 0.001) --Volume of the case as a cylinder * Powder density converted from g to kg + Data.RoundVolume = Data.FrArea * ACF.AmmoCaseScale ^ 2 * (Data.ProjLength + Data.PropLength) + + GUIData.ProjVolume = Data.FrArea * Data.ProjLength +end + +local Weaponry = { + Piledrivers = Classes.Piledrivers, + Missiles = Classes.Missiles, + Weapons = Classes.Weapons, +} + +-- In case you might want to add more +function ACF.AddWeaponrySource(Class) + if not Class then return end + if not Classes[Class] then return end + + Weaponry[Class] = Classes[Class] +end + +function ACF.GetWeaponrySources() + local Result = {} + + for K, V in pairs(Weaponry) do + Result[K] = V + end + + return Result +end + +function ACF.FindWeaponrySource(ID) + if not ID then return end + + for Key, Source in pairs(Weaponry) do + if ACF.GetClassGroup(Source, ID) then + return Key, Source + end + end +end + +function ACF.GetWeaponBlacklist(Whitelist) + local Result = {} + + for _, Source in pairs(Weaponry) do + for ID in pairs(Source) do + if not Whitelist[ID] then + Result[ID] = true + end + end + end + + return Result +end + +function ACF.RoundShellCapacity(Momentum, FrArea, Caliber, ProjLength) + local MinWall = 0.2 + ((Momentum / FrArea) ^ 0.7) * 0.02 --The minimal shell wall thickness required to survive firing at the current energy level + local Length = math.max(ProjLength - MinWall, 0) + local Radius = math.max((Caliber * 0.5) - MinWall, 0) + local Volume = 3.1416 * Radius ^ 2 * Length + + return Volume, Length, Radius --Returning the cavity volume and the minimum wall thickness +end + +function ACF.RicoProbability(Rico, Speed) + local MinAngle = math.min(Rico - Speed * 0.066, 89) + + return { + Min = math.Round(math.max(MinAngle, 0.01), 2), + Mean = math.Round(math.max(MinAngle + (90 - MinAngle) / 2, 0.01), 2), + Max = 90 + } +end + +--Formula from https://mathscinotes.wordpress.com/2013/10/03/parameter-determination-for-pejsa-velocity-model/ +--not terribly accurate for acf, particularly small caliber (7.62mm off by 120 m/s at 800m), but is good enough for quick indicator +--range in m, vel is m/s +function ACF.PenRanging(MuzzleVel, DragCoef, ProjMass, PenArea, LimitVel, Range) + local V0 = MuzzleVel * 39.37 * ACF.Scale --initial velocity + local D0 = DragCoef * V0 ^ 2 / ACF.DragDiv --initial drag + local K1 = (D0 / (V0 ^ 1.5)) ^ -1 --estimated drag coefficient + local Vel = (math.sqrt(V0) - ((Range * 39.37) / (2 * K1))) ^ 2 + local Pen = ACF_Kinetic(Vel, ProjMass, LimitVel).Penetration / PenArea * ACF.KEtoRHA + + return math.Round(Vel * 0.0254, 2), math.Round(Pen, 2) +end + +do -- Ammo crate capacity calculation + local Axises = { + x = { Y = "y", Z = "z", Ang = Angle() }, + y = { Y = "x", Z = "z", Ang = Angle(0, 90) }, + z = { Y = "x", Z = "y", Ang = Angle(90, 90) } + } + + local function GetBoxDimensions(Axis, Size) + local AxisInfo = Axises[Axis] + local Y = Size[AxisInfo.Y] + local Z = Size[AxisInfo.Z] + + return Size[Axis], Y, Z, AxisInfo.Ang + end + + local function GetRoundsPerAxis(SizeX, SizeY, SizeZ, Length, Width, Height, Spacing) + -- Omitting spacing for the axises with just one round + if math.floor(SizeX / Length) > 1 then Length = Length + Spacing end + if math.floor(SizeY / Width) > 1 then Width = Width + Spacing end + if math.floor(SizeZ / Height) > 1 then Height = Height + Spacing end + + local RoundsX = math.floor(SizeX / Length) + local RoundsY = math.floor(SizeY / Width) + local RoundsZ = math.floor(SizeZ / Height) + + return RoundsX, RoundsY, RoundsZ + end + + -- Split this off from the original function, + -- All this does is compare a distance against a table of distances with string indexes for the shortest fitting size + -- It returns the string index of the dimension, or nil if it fails to fit + local function ShortestSize(Length, Width, Height, Spacing, Dimensions, ExtraData, IsIrregular) + local BestCount = 0 + local BestAxis + + for Axis in pairs(Axises) do + local X, Y, Z = GetBoxDimensions(Axis, Dimensions) + local Multiplier = 1 + + if not IsIrregular then + local MagSize = ExtraData.MagSize + + if MagSize and MagSize > 0 then + Multiplier = MagSize + end + end + + local RoundsX, RoundsY, RoundsZ = GetRoundsPerAxis(X, Y, Z, Length, Width, Height, Spacing) + local Count = RoundsX * RoundsY * RoundsZ * Multiplier + + if Count > BestCount then + BestAxis = Axis + BestCount = Count + end + end + + return BestAxis, BestCount + end + + -- BoxSize is just OBBMaxs-OBBMins + -- Removed caliber and round length inputs, uses GunData and BulletData now + -- AddSpacing is just extra spacing (directly reduces storage, but can later make it harder to detonate) + -- AddArmor is literally just extra armor on the ammo crate, but inside (also directly reduces storage) + -- For missiles/bombs, they MUST have ActualLength and ActualWidth (of the model in cm, and in the round table) to use this, otherwise it will fall back to the original calculations + -- Made by LiddulBOFH :) + function ACF.GetAmmoCrateCapacity(BoxSize, GunData, BulletData, AddSpacing, AddArmor) + -- gives a nice number of rounds per refill box + if BulletData.Type == "Refill" then return math.ceil(BoxSize.x * BoxSize.y * BoxSize.z * 0.01) end + + local GunCaliber = GunData.Caliber * 0.1 -- mm to cm + local RoundCaliber = GunCaliber * ACF.AmmoCaseScale + local RoundLength = BulletData.PropLength + BulletData.ProjLength + (BulletData.Tracer or 0) + local ExtraData = {} + + -- Filters for missiles, and sets up data + if GunData.Round.ActualWidth then + RoundCaliber = GunData.Round.ActualWidth + RoundLength = GunData.Round.ActualLength + ExtraData.IsRacked = true + elseif GunData.Class.Entity == "acf_rack" then + local Efficiency = 0.1576 * ACF.AmmoMod + local Volume = math.floor(BoxSize.x * BoxSize.y * BoxSize.z) * Efficiency + local CapMul = (Volume > 40250) and ((math.log(Volume * 0.00066) / math.log(2) - 4) * 0.15 + 1) or 1 + + return math.floor(CapMul * Volume * 16.38 / BulletData.RoundVolume) -- Fallback to old capacity + end + + local Rounds = 0 + local Spacing = math.max(AddSpacing, 0) + 0.125 + local MagSize = GunData.MagSize or 1 + local IsBoxed = GunData.Class.IsBoxed + local Rotate + + -- Converting everything to source units + local Length = RoundLength * 0.3937 -- cm to inches + local Width = RoundCaliber * 0.3937 -- cm to inches + local Height = Width + + ExtraData.Spacing = Spacing + + -- This block alters the stored round size, making it more like a container of the rounds + -- This cuts a little bit of ammo storage out + if MagSize > 1 then + if IsBoxed and not ExtraData.IsRacked then + -- Makes certain automatic ammo stored by boxes + Width = Width * math.sqrt(MagSize) + Height = Width + + ExtraData.MagSize = MagSize + ExtraData.IsBoxed = true + else + MagSize = 1 + end + end + + if AddArmor then + -- Converting millimeters to inches then multiplying by two since the armor is on both sides + local BoxArmor = AddArmor * 0.039 * 2 + local X = math.max(BoxSize.x - BoxArmor, 0) + local Y = math.max(BoxSize.y - BoxArmor, 0) + local Z = math.max(BoxSize.z - BoxArmor, 0) + + BoxSize = Vector(X, Y, Z) + end + + local ShortestFit = ShortestSize(Length, Width, Height, Spacing, BoxSize, ExtraData) + + -- If ShortestFit is nil, that means the round isn't able to fit at all in the box + -- If its a racked munition that doesn't fit, it will go ahead and try to fit 2-pice + -- Otherwise, checks if the caliber is over 100mm before trying 2-piece ammunition + -- It will flatout not do anything if its boxed and not fitting + if not ShortestFit and not ExtraData.IsBoxed and (GunCaliber >= 10 or ExtraData.IsRacked) then + Length = Length * 0.5 -- Not exactly accurate, but cuts the round in two + Width = Width * 2 -- two pieces wide + + ExtraData.IsTwoPiece = true + + local ShortestFit1, Count1 = ShortestSize(Length, Width, Height, Spacing, BoxSize, ExtraData, true) + local ShortestFit2, Count2 = ShortestSize(Length, Height, Width, Spacing, BoxSize, ExtraData, true) + + if Count1 > Count2 then + ShortestFit = ShortestFit1 + else + ShortestFit = ShortestFit2 + Rotate = true + end + end + + -- If it still doesn't fit the box, then it's just too small + if ShortestFit then + local SizeX, SizeY, SizeZ, LocalAng = GetBoxDimensions(ShortestFit, BoxSize) + + ExtraData.LocalAng = LocalAng + ExtraData.RoundSize = Vector(Length, Width, Height) + + -- In case the round was cut and needs to be rotated, then we do some minor changes + if Rotate then + local OldY = SizeY + + SizeY = SizeZ + SizeZ = OldY + + ExtraData.LocalAng = ExtraData.LocalAng + Angle(0, 0, 90) + end + + local RoundsX, RoundsY, RoundsZ = GetRoundsPerAxis(SizeX, SizeY, SizeZ, Length, Width, Height, Spacing) + + ExtraData.FitPerAxis = Vector(RoundsX, RoundsY, RoundsZ) + + Rounds = RoundsX * RoundsY * RoundsZ * MagSize + end + + return Rounds, ExtraData + end +end \ No newline at end of file diff --git a/lua/acf/base/sh_tool_functions.lua b/lua/acf/base/sh_tool_functions.lua new file mode 100644 index 000000000..d365c605a --- /dev/null +++ b/lua/acf/base/sh_tool_functions.lua @@ -0,0 +1,608 @@ +local ACF = ACF + +ACF.Tools = ACF.Tools or {} + +local Tools = ACF.Tools + +local function GetToolData(Tool) + if not Tools[Tool] then + Tools[Tool] = { + Indexed = {}, + Stages = {}, + Count = 0, + } + + ACF.RegisterOperation(Tool, "Main", "Idle", {}) + ACF.RegisterToolInfo(Tool, "Main", "Idle", { + name = "info", + text = "Select an option on the menu." + }) + end + + return Tools[Tool] +end + +do -- Tool Stage/Operation Registration function + local function RegisterStage(Data, Name) + local Stage = Data.Stages[Name] + + if not Stage then + local Count = Data.Count + + Stage = { + Ops = {}, + Count = 0, + Name = Name, + Indexed = {}, + Index = Count, + } + + Data.Stages[Name] = Stage + Data.Indexed[Count] = Stage + + Data.Count = Count + 1 + end + + return Stage + end + + function ACF.RegisterOperation(Tool, StageName, OpName, ToolFuncs) + if not Tool then return end + if not OpName then return end + if not StageName then return end + if not istable(ToolFuncs) then return end + + local Data = GetToolData(Tool) + local Stage = RegisterStage(Data, StageName) + local Operation = Stage.Ops[OpName] + local Count = Stage.Count + + if not Operation then + Operation = {} + + Stage.Ops[OpName] = Operation + Stage.Indexed[Count] = Operation + Stage.Count = Count + 1 + end + + for K, V in pairs(ToolFuncs) do + Operation[K] = V + end + + Operation.Name = OpName + Operation.Index = Count + + return Operation + end +end + +do -- Tool Information Registration function + local function GetInformation(Tool) + local Data = Tools[Tool] + + if not Data.Information then + Data.Information = {} + Data.InfoLookup = {} + Data.InfoCount = 0 + end + + return Data.Information + end + + -- This function will add entries to the tool's Information table + -- For more reference about the values you can give it see: + -- https://wiki.facepunch.com/gmod/Tool_Information_Display + -- Note: name, stage and op will be assigned automatically + function ACF.RegisterToolInfo(Tool, Stage, Op, Info) + if SERVER then return end + if not Tool then return end + if not Stage then return end + if not Op then return end + if not istable(Info) then return end + if not Info.name then return end + if not Info.text then return end + + local Data = GetToolData(Tool) + local Stages = Data.Stages[Stage] + + if not Stages then return end + + local Ops = Stages.Ops[Op] + + if not Ops then return end + + local StageIdx, OpIdx = Stages.Index, Ops.Index + local Name = Info.name .. "_" .. StageIdx .. "_" .. OpIdx + local ToolInfo = GetInformation(Tool) + local New = Data.InfoLookup[Name] + + if not New then + local Count = Data.InfoCount + 1 + + New = {} + + Data.InfoLookup[Name] = New + Data.InfoCount = Count + + ToolInfo[Count] = New + end + + for K, V in pairs(Info) do + New[K] = V + end + + New.name = Name + New.stage = StageIdx + New.op = OpIdx + + return New + end +end + +do -- Tool Functions Loader + local Category = GetConVar("acf_tool_category") + + if SERVER then + util.AddNetworkString("ACF_ToolNetVars") + + hook.Add("PlayerCanPickupWeapon", "ACF Tools", function(Player, Weapon) + if Weapon:GetClass() ~= "gmod_tool" then return end + + for Name in pairs(Tools) do + local Tool = Player:GetTool(Name) + + if Tool then + Tool:RestoreMode() + end + end + end) + else + net.Receive("ACF_ToolNetVars", function() + local ToolName = net.ReadString() + local Name = net.ReadString() + local Value = net.ReadInt(8) + + local Data = Tools[ToolName] + local Tool = LocalPlayer():GetTool(ToolName) + + if not Data then return end + if not Tool then return end + + if Name == "Stage" then + Tool.Stage = Value + Tool.StageData = Data.Indexed[Value] + elseif Name == "Operation" then + Tool.Operation = Value + Tool.OpData = Tool.StageData.Indexed[Value] + Tool.DrawToolScreen = Tool.OpData.DrawToolScreen + end + end) + end + + local function UpdateNetvar(Tool, Name, Value) + net.Start("ACF_ToolNetVars") + net.WriteString(Tool.Mode) + net.WriteString(Name) + net.WriteInt(Value, 8) + net.Send(Tool:GetOwner()) + end + + function ACF.LoadToolFunctions(Tool) + if not Tool then return end + if not Tools[Tool.Mode] then return end + + local Mode = Tool.Mode + local Data = Tools[Mode] + Data.Tool = Tool + + function Tool:SetStage(Stage) + if CLIENT then return end + if not Stage then return end + if not Data.Indexed[Stage] then return end + + self.Stage = Stage + self.StageData = Data.Indexed[Stage] + + UpdateNetvar(self, "Stage", Stage) + + self:SetOperation(0) + end + + function Tool:GetStage() + return self.Stage + end + + function Tool:SetOperation(Op) + if CLIENT then return end + if not Op then return end + if not self.StageData.Indexed[Op] then return end + + self.Operation = Op + self.OpData = self.StageData.Indexed[Op] + + UpdateNetvar(self, "Operation", Op) + end + + function Tool:GetOperation() + return self.Operation + end + + if CLIENT then + Tool.Category = Category:GetBool() and "ACF" or "Construction" + + if Data.Information then + Tool.Information = {} + + for K, V in ipairs(Data.Information) do + Tool.Information[K] = V + + language.Add("Tool." .. Mode .. "." .. V.name, V.text) + end + end + + function Tool:LeftClick(Trace) + return not Trace.HitSky + end + + function Tool:RightClick(Trace) + return not Trace.HitSky + end + + function Tool:Reload(Trace) + return not Trace.HitSky + end + else + -- Helper function, allows you to set both stage and op at the same time with their names + function Tool:SetMode(StageName, OpName) + if not StageName then return end + if not OpName then return end + + local Stage = Data.Stages[StageName] + + if not Stage then return end + + local Op = Stage.Ops[OpName] + + if not Op then return end + + self:SetStage(Stage.Index) + self:SetOperation(Op.Index) + end + + function Tool:RestoreMode() + local ToolMode = ACF.GetClientString(self:GetOwner(), "ToolMode:" .. self.Mode) + + if ToolMode then + local Stage, Op = unpack(string.Explode(":", ToolMode), 1, 2) + + self:SetMode(Stage, Op) + end + end + + function Tool:LeftClick(Trace) + if self.OpData then + local OnLeftClick = self.OpData.OnLeftClick + + if OnLeftClick then + return OnLeftClick(self, Trace) + end + end + + return false + end + + function Tool:RightClick(Trace) + if self.OpData then + local OnRightClick = self.OpData.OnRightClick + + if OnRightClick then + return OnRightClick(self, Trace) + end + end + + return false + end + + function Tool:Reload(Trace) + if self.OpData then + local OnReload = self.OpData.OnReload + + if OnReload then + return OnReload(self, Trace) + end + end + + return false + end + + function Tool:Deploy() + self:RestoreMode() + + if self.OpData then + local OnDeploy = self.OpData.OnDeploy + + if OnDeploy then + OnDeploy(self) + end + end + end + + function Tool:Holster() + if self.OpData then + local OnHolster = self.OpData.OnHolster + + if OnHolster then + OnHolster(self) + end + end + end + + function Tool:Think() + if self.OpData then + local OnThink = self.OpData.OnThink + + if OnThink then + OnThink(self) + end + end + end + end + end +end + +do -- Clientside Tool interaction + if SERVER then + hook.Add("ACF_OnClientDataUpdate", "ACF ToolMode", function(Player, Key, Value) + local Header, Name = unpack(string.Explode(":", Key), 1, 2) + + if Header ~= "ToolMode" then return end + + local Tool = Player:GetTool(Name) + + if not Tool then return end + if not Tool.SetMode then return end + + local Stage, Op = unpack(string.Explode(":", Value), 1, 2) + + Tool:SetMode(Stage, Op) + end) + else + local Key = "ToolMode:%s" + local Value = "%s:%s" + + function ACF.SetToolMode(Tool, Stage, Op) + if not isstring(Tool) then return end + if not isstring(Stage) then return end + if not isstring(Op) then return end + + ACF.SetClientData(Key:format(Tool), Value:format(Stage, Op)) + end + end +end + +do -- Generic Spawner/Linker operation creator + local Entities = {} + local SpawnText = "Spawn a new %s or update an existing one." + + local function GetPlayerEnts(Player) + local Ents = Entities[Player] + + if not Ents then + Ents = {} + Entities[Player] = Ents + end + + return Ents + end + + local function CanUpdate(Entity, ClassName) + if not IsValid(Entity) then return false end + + return Entity:GetClass() == ClassName + end + + local function SpawnEntity(Player, ClassName, Trace, Data) + if not ClassName or ClassName == "N/A" then return false end + + local Entity = Trace.Entity + + if CanUpdate(Entity, ClassName) then + local Result, Message = ACF.UpdateEntity(Entity, Data) + + ACF.SendMessage(Player, Result and "Info" or "Error", Message) + + return true + end + + local Position = Trace.HitPos + Trace.HitNormal * 128 + local Angles = Trace.HitNormal:Angle():Up():Angle() + + local Success, Result = ACF.CreateEntity(ClassName, Player, Position, Angles, Data) + + if Success then + local PhysObj = Result:GetPhysicsObject() + + Result:DropToFloor() + + if IsValid(PhysObj) then + PhysObj:EnableMotion(false) + end + else + ACF.SendMessage(Player, "Error", "Couldn't create entity: " .. Result) + end + + return Success + end + + function ACF.CreateMenuOperation(Name, Primary, Secondary) + if not isstring(Name) then return end + if not isstring(Primary) then return end + + Secondary = ACF.CheckString(Secondary) + + local function UnselectEntity(Tool, Player, Entity) + local Ents = GetPlayerEnts(Player) + + Entity:RemoveCallOnRemove("ACF_ToolLinking") + Entity:SetColor(Ents[Entity]) + + Ents[Entity] = nil + + if not next(Ents) then + Tool:SetMode("Spawner", Name) + end + end + + local function SelectEntity(Tool, Player, Entity) + if not IsValid(Entity) then return false end + + local Ents = GetPlayerEnts(Player) + + if not next(Ents) then + Tool:SetMode("Linker", Name) + end + + Ents[Entity] = Entity:GetColor() + Entity:SetColor(Color(0, 255, 0)) + Entity:CallOnRemove("ACF_ToolLinking", function() + UnselectEntity(Tool, Player, Entity) + end) + + return true + end + + do -- Spawner stuff + ACF.RegisterOperation("acf_menu", "Spawner", Name, { + OnLeftClick = function(Tool, Trace) + if Trace.HitSky then return false end + + local Player = Tool:GetOwner() + local Data = ACF.GetAllClientData(Player) + local UseSecond = Player:KeyDown(IN_SPEED) or Player:KeyDown(IN_RELOAD) + local ClassName = UseSecond and Data.SecondaryClass or Data.PrimaryClass + + return SpawnEntity(Player, ClassName, Trace, Data) + end, + OnRightClick = function(Tool, Trace) + local Player = Tool:GetOwner() + + return SelectEntity(Tool, Player, Trace.Entity) + end, + }) + + ACF.RegisterToolInfo("acf_menu", "Spawner", Name, { + name = "left", + text = SpawnText:format(Primary), + }) + + if Secondary then + ACF.RegisterToolInfo("acf_menu", "Spawner", Name, { + name = "left_secondary", + text = "(Hold Shift or R) " .. SpawnText:format(Secondary), + icon2 = "gui/info", + }) + end + + ACF.RegisterToolInfo("acf_menu", "Spawner", Name, { + name = "right", + text = "Select the entity you want to link or unlink.", + }) + end + + do -- Linker stuff + local function LinkEntities(Tool, Player, Entity, Ents) + local Total, Done = 0, 0 + local Unlink = Player:KeyDown(IN_RELOAD) + local Action = Unlink and Entity.Unlink or Entity.Link + + for K in pairs(Ents) do + local EntAction = Unlink and K.Unlink or K.Link + local Success = false + + if EntAction then + Success = EntAction(K, Entity) + elseif Action then + Success = Action(Entity, K) + end + + Total = Total + 1 + + if Success then + Done = Done + 1 + end + + UnselectEntity(Tool, Player, K) + end + + -- TODO: Add list of reasons for failed links + if Done > 0 then + local Status = (Unlink and "unlinked " or "linked ") .. Done .. " out of " .. Total + + ACF.SendMessage(Player, "Info", "Successfully ", Status, " entities to ", tostring(Entity), ".") + else + local Status = Total .. " entities could be " .. (Unlink and "unlinked" or "linked") + + ACF.SendMessage(Player, "Error", "None of the ", Status, " to ", tostring(Entity), ".") + end + end + + ACF.RegisterOperation("acf_menu", "Linker", Name, { + OnRightClick = function(Tool, Trace) + local Player = Tool:GetOwner() + local Entity = Trace.Entity + + if Trace.HitWorld then Tool:Holster() return true end + if not IsValid(Entity) then return false end + + local Ents = GetPlayerEnts(Player) + + if not Player:KeyDown(IN_SPEED) then + LinkEntities(Tool, Player, Entity, Ents) + return true + end + + if not Ents[Entity] then + SelectEntity(Tool, Player, Entity) + else + UnselectEntity(Tool, Player, Entity) + end + + return true + end, + OnHolster = function(Tool) + local Player = Tool:GetOwner() + local Ents = GetPlayerEnts(Player) + + if not next(Ents) then return end + + for Entity in pairs(Ents) do + UnselectEntity(Tool, Player, Entity) + end + end, + }) + + ACF.RegisterToolInfo("acf_menu", "Linker", Name, { + name = "right", + text = "Link all the selected entities to an entity.", + }) + + ACF.RegisterToolInfo("acf_menu", "Linker", Name, { + name = "right_r", + text = "Unlink all the selected entities from an entity.", + icon2 = "gui/r.png", + }) + + ACF.RegisterToolInfo("acf_menu", "Linker", Name, { + name = "right_shift", + text = "Select another entity to link.", + icon2 = "gui/info", + }) + + ACF.RegisterToolInfo("acf_menu", "Linker", Name, { + name = "right_world", + text = "(Hit the World) Unselected all selected entities.", + icon2 = "gui/info", + }) + end + end +end diff --git a/lua/acf/base/sh_traceline.lua b/lua/acf/base/sh_traceline.lua index 0df7995ea..7d7b11cf5 100644 --- a/lua/acf/base/sh_traceline.lua +++ b/lua/acf/base/sh_traceline.lua @@ -9,12 +9,20 @@ -- Note from Dakota: Check impacts on wedge joints, especially if they are visclipped. Hitnormal might be fucked in some cases. -- Note from the wiki: This function may not always give desired results clientside due to certain physics mechanisms not existing on the client. +-- Known issues: +-- MASK_SHOT ignores all entities. + local Hull = util.TraceHull local Zero = Vector() +-- Available for use, just in case +if not util.LegacyTraceLine then + util.LegacyTraceLine = util.TraceLine +end + function util.TraceLine(TraceData, ...) - if TraceData then - TraceData.mins = -Zero -- I wonder if negating it is necessary at all. + if istable(TraceData) then + TraceData.mins = Zero TraceData.maxs = Zero end diff --git a/lua/acf/base/sv_validation.lua b/lua/acf/base/sv_validation.lua index 89d8ab5ee..ea8a7db16 100644 --- a/lua/acf/base/sv_validation.lua +++ b/lua/acf/base/sv_validation.lua @@ -1,8 +1,7 @@ -- Entity validation for ACF -local LegalHints = CreateConVar("acf_legalhints", 1, FCVAR_ARCHIVE) -- Local Vars ----------------------------------- -local Gamemode = GetConVar("acf_gamemode") +local ACF = ACF local StringFind = string.find local TimerSimple = timer.Simple local Baddies = ACF.GlobalFilter @@ -24,7 +23,7 @@ local Baddies = ACF.GlobalFilter end ]]-- local function IsLegal(Entity) - if Gamemode:GetInt() == 0 then return true end -- Gamemode is set to Sandbox, legal checks don't apply + if ACF.Gamemode == 1 then return true end -- Gamemode is set to Sandbox, legal checks don't apply local Phys = Entity:GetPhysicsObject() @@ -69,17 +68,17 @@ local function CheckLegal(Entity) Entity:Disable() -- Let the entity know it's disabled if Entity.UpdateOverlay then Entity:UpdateOverlay(true) end -- Update overlay if it has one (Passes true to update overlay instantly) - if LegalHints:GetBool() then -- Notify the owner + if tobool(Owner:GetInfo("acf_legalhints")) then -- Notify the owner local Name = Entity.WireDebugName .. " [" .. Entity:EntIndex() .. "]" if Reason == "Not drawn" or Reason == "Not solid" then -- Thank you garry, very cool timer.Simple(1.1, function() -- Remover tool sets nodraw and removes 1 second later, causing annoying alerts if not IsValid(Entity) then return end - ACF_SendNotify(Owner, false, Name .. " has been disabled: " .. Description) + ACF.SendNotify(Owner, false, Name .. " has been disabled: " .. Description) end) else - ACF_SendNotify(Owner, false, Name .. " has been disabled: " .. Description) + ACF.SendNotify(Owner, false, Name .. " has been disabled: " .. Description) end end end @@ -99,7 +98,7 @@ local function CheckLegal(Entity) return false end - if Gamemode:GetInt() ~= 0 then + if ACF.Gamemode ~= 1 then TimerSimple(math.Rand(1, 3), function() -- Entity is legal... test again in random 1 to 3 seconds if IsValid(Entity) then CheckLegal(Entity) @@ -110,7 +109,7 @@ local function CheckLegal(Entity) return true end -- Global Funcs --------------------------------- -function ACF_Check(Entity, ForceUpdate) -- IsValid but for ACF +function ACF.Check(Entity, ForceUpdate) -- IsValid but for ACF if not IsValid(Entity) then return false end local Class = Entity:GetClass() @@ -126,15 +125,15 @@ function ACF_Check(Entity, ForceUpdate) -- IsValid but for ACF return false end - ACF_Activate(Entity) + ACF.Activate(Entity) elseif ForceUpdate or Entity.ACF.Mass ~= PhysObj:GetMass() or Entity.ACF.PhysObj ~= PhysObj then - ACF_Activate(Entity, true) + ACF.Activate(Entity, true) end return Entity.ACF.Type end -function ACF_Activate(Entity, Recalc) +function ACF.Activate(Entity, Recalc) --Density of steel = 7.8g cm3 so 7.8kg for a 1mx1m plate 1m thick local PhysObj = Entity:GetPhysicsObject() @@ -149,6 +148,7 @@ function ACF_Activate(Entity, Recalc) end -- TODO: Figure out what are the 6.45 and 0.52505066107 multipliers for + -- NOTE: Why are we applying multipliers to the stored surface area? local SurfaceArea = PhysObj:GetSurfaceArea() if SurfaceArea then -- Normal collisions @@ -190,116 +190,8 @@ function ACF_Activate(Entity, Recalc) end end -do -- Entity Links ------------------------------ - local EntityLink = {} - local function GetEntityLinks(Entity, VarName, SingleEntry) - if not Entity[VarName] then return {} end - - if SingleEntry then - return { [Entity[VarName]] = true } - end - - local Result = {} - - for K in pairs(Entity[VarName]) do - Result[K] = true - end - - return Result - end - - -- If your entity can link/unlink other entities, you should use this - function ACF.RegisterLinkSource(Class, VarName, SingleEntry) - local Data = EntityLink[Class] - - if not Data then - EntityLink[Class] = { - [VarName] = function(Entity) - return GetEntityLinks(Entity, VarName, SingleEntry) - end - } - else - Data[VarName] = function(Entity) - return GetEntityLinks(Entity, VarName, SingleEntry) - end - end - end - - function ACF.GetAllLinkSources(Class) - if not EntityLink[Class] then return {} end - - local Result = {} - - for K, V in pairs(EntityLink[Class]) do - Result[K] = V - end - - return Result - end - - function ACF.GetLinkSource(Class, VarName) - if not EntityLink[Class] then return end - - return EntityLink[Class][VarName] - end - - local ClassLink = { Link = {}, Unlink = {} } - local function RegisterNewLink(Action, Class1, Class2, Function) - if not isfunction(Function) then return end - - local Target = ClassLink[Action] - local Data1 = Target[Class1] - - if not Data1 then - Target[Class1] = { - [Class2] = function(Ent1, Ent2) - return Function(Ent1, Ent2) - end - } - else - Data1[Class2] = function(Ent1, Ent2) - return Function(Ent1, Ent2) - end - end - - if Class1 == Class2 then return end - - local Data2 = Target[Class2] - - if not Data2 then - Target[Class2] = { - [Class1] = function(Ent2, Ent1) - return Function(Ent1, Ent2) - end - } - else - Data2[Class1] = function(Ent2, Ent1) - return Function(Ent1, Ent2) - end - end - end - - function ACF.RegisterClassLink(Class1, Class2, Function) - RegisterNewLink("Link", Class1, Class2, Function) - end - - function ACF.GetClassLink(Class1, Class2) - if not ClassLink.Link[Class1] then return end - - return ClassLink.Link[Class1][Class2] - end - - function ACF.RegisterClassUnlink(Class1, Class2, Function) - RegisterNewLink("Unlink", Class1, Class2, Function) - end - - function ACF.GetClassUnlink(Class1, Class2) - if not ClassLink.Unlink[Class1] then return end - - return ClassLink.Unlink[Class1][Class2] - end -end --------------------------------------------- - -- Globalize ------------------------------------ -ACF_IsLegal = IsLegal -ACF_CheckLegal = CheckLegal +ACF_IsLegal = IsLegal +ACF_CheckLegal = CheckLegal +ACF_Check = ACF.Check +ACF_Activate = ACF.Activate diff --git a/lua/acf/base/util/cl_util.lua b/lua/acf/base/util/cl_util.lua index f8e7e817d..ac5bd5318 100644 --- a/lua/acf/base/util/cl_util.lua +++ b/lua/acf/base/util/cl_util.lua @@ -1,74 +1,89 @@ local ACF = ACF -local Types = { - Normal = { - Prefix = "", - Color = Color(80, 255, 80) - }, - Info = { - Prefix = " - Info", - Color = Color(0, 233, 255) - }, - Warning = { - Prefix = " - Warning", - Color = Color(255, 160, 0) - }, - Error = { - Prefix = " - Error", - Color = Color(255, 80, 80) + +do -- Clientside chat messages + local Types = { + Normal = { + Prefix = "", + Color = Color(80, 255, 80) + }, + Info = { + Prefix = " - Info", + Color = Color(0, 233, 255) + }, + Warning = { + Prefix = " - Warning", + Color = Color(255, 160, 0) + }, + Error = { + Prefix = " - Error", + Color = Color(255, 80, 80) + } } -} -function ACF.AddMessageType(Name, Prefix, TitleColor) - if not Name then return end + function ACF.AddMessageType(Name, Prefix, TitleColor) + if not Name then return end - Types[Name] = { - Prefix = Prefix and (" - " .. Prefix) or "", - Color = TitleColor or Color(80, 255, 80), - } -end + Types[Name] = { + Prefix = Prefix and (" - " .. Prefix) or "", + Color = TitleColor or Color(80, 255, 80), + } + end -local function PrintToChat(Type, ...) - if not ... then return end + local function PrintToChat(Type, ...) + if not ... then return end - local Data = Types[Type] or Types.Normal - local Prefix = "[ACF" .. Data.Prefix .. "] " - local Message = istable(...) and ... or { ... } + local Data = Types[Type] or Types.Normal + local Prefix = "[ACF" .. Data.Prefix .. "] " + local Message = istable(...) and ... or { ... } - chat.AddText(Data.Color, Prefix, color_white, unpack(Message)) -end + chat.AddText(Data.Color, Prefix, color_white, unpack(Message)) + end -ACF.PrintToChat = PrintToChat + ACF.PrintToChat = PrintToChat -net.Receive("ACF_ChatMessage", function() - local Type = net.ReadString() - local Message = net.ReadTable() + net.Receive("ACF_ChatMessage", function() + local Type = net.ReadString() + local Message = net.ReadTable() - PrintToChat(Type, Message) -end) + PrintToChat(Type, Message) + end) +end -surface.CreateFont("ACF_Title", { - font = "Roboto", - size = 23, - weight = 1000, -}) +do -- Custom fonts + surface.CreateFont("ACF_Title", { + font = "Roboto", + size = 18, + weight = 850, + }) + + surface.CreateFont("ACF_Label", { + font = "Roboto", + size = 14, + weight = 650, + }) + + surface.CreateFont("ACF_Control", { + font = "Roboto", + size = 14, + weight = 550, + }) +end + +do -- Networked notifications + local notification = notification -surface.CreateFont("ACF_Subtitle", { - font = "Roboto", - size = 18, - weight = 1000, -}) + net.Receive("ACF_Notify", function() + local Type = NOTIFY_ERROR -surface.CreateFont("ACF_Paragraph", { - font = "Roboto", - size = 14, - weight = 750, -}) + if net.ReadBool() then + Type = NOTIFY_GENERIC + else + surface.PlaySound("buttons/button10.wav") + end -surface.CreateFont("ACF_Control", { - font = "Roboto", - size = 14, - weight = 550, -}) + notification.AddLegacy(net.ReadString(), Type, 7) + end) +end do -- Clientside visclip check local function CheckClip(Entity, Clip, Center, Pos) @@ -97,3 +112,41 @@ do -- Clientside visclip check return false end end + +do -- Panel helpers + local Sorted = {} + + function ACF.LoadSortedList(Panel, List, Member) + local Data = Sorted[List] + + if not Data then + local Choices = {} + local Count = 0 + + for _, Value in pairs(List) do + Count = Count + 1 + + Choices[Count] = Value + end + + table.SortByMember(Choices, Member, true) + + Data = { + Choices = Choices, + Index = 1, + } + + Sorted[List] = Data + end + + local Current = Data.Index + + Panel.ListData = Data + + Panel:Clear() + + for Index, Value in ipairs(Data.Choices) do + Panel:AddChoice(Value.Name, Value, Index == Current) + end + end +end \ No newline at end of file diff --git a/lua/acf/base/util/sh_util.lua b/lua/acf/base/util/sh_util.lua index 47feb2fca..b34c12627 100644 --- a/lua/acf/base/util/sh_util.lua +++ b/lua/acf/base/util/sh_util.lua @@ -103,7 +103,7 @@ do -- Ricochet/Penetration materials end end -do -- Time lapse function +do -- Unit conversion local Units = { { Unit = "year", Reduction = 1970 }, { Unit = "month", Reduction = 1 }, @@ -135,6 +135,18 @@ do -- Time lapse function end end end + + function ACF.GetProperMass(Kilograms) + local Unit, Mult = "g", 1000 + + if Kilograms >= 1000 then + Unit, Mult = "t", 0.001 + elseif Kilograms >= 1 then + Unit, Mult = "kg", 1 + end + + return math.Round(Kilograms * Mult, 2) .. " " .. Unit + end end do -- Trace functions @@ -149,8 +161,6 @@ do -- Trace functions return ACF.Trace(TraceData) end - debugoverlay.Line(TraceData.start, T.HitPos, 15, Color(0, 255, 0)) - return T end @@ -177,6 +187,297 @@ do -- Trace functions end end +-- Pretty much unused, should be moved into the ACF namespace or just removed +function switch(cases, arg) + local Var = cases[arg] + + if Var ~= nil then return Var end + + return cases.default +end + +function ACF.RandomVector(Min, Max) + local X = math.Rand(Min.x, Max.x) + local Y = math.Rand(Min.y, Max.y) + local Z = math.Rand(Min.z, Max.z) + + return Vector(X, Y, Z) +end + +do -- Native type verification functions + function ACF.CheckNumber(Value, Default) + if not Value then return Default end + + return tonumber(Value) or Default + end + + function ACF.CheckString(Value, Default) + if Value == nil then return Default end + + return tostring(Value) or Default + end +end + +do -- Attachment storage + local IsUseless = IsUselessModel + local EntTable = FindMetaTable("Entity") + local Models = {} + + local function GetModelData(Model, NoCreate) + local Table = Models[Model] + + if not (Table or NoCreate) then + Table = {} + + Models[Model] = Table + end + + return Table + end + + local function SaveAttachments(Model, Attachments, Clear) + if IsUseless(Model) then return end + + local Data = GetModelData(Model) + local Count = Clear and 0 or #Data + + if Clear then + for K in pairs(Data) do Data[K] = nil end + end + + for I, Attach in ipairs(Attachments) do + local Index = Count + I + local Name = ACF.CheckString(Attach.Name, "Unnamed" .. Index) + + Data[Index] = { + Index = Index, + Name = Name, + Pos = Attach.Pos or Vector(), + Ang = Attach.Ang or Angle(), + Bone = Attach.Bone, + } + end + + if not next(Data) then + Models[Model] = nil + end + end + + local function GetAttachData(Entity) + if not Entity.AttachData then + Entity.AttachData = GetModelData(Entity:GetModel(), true) + end + + return Entity.AttachData + end + + ------------------------------------------------------------------- + + function ACF.AddCustomAttachment(Model, Name, Pos, Ang, Bone) + if not isstring(Model) then return end + + SaveAttachments(Model, {{ + Name = Name, + Pos = Pos, + Ang = Ang, + Bone = Bone, + }}) + end + + function ACF.AddCustomAttachments(Model, Attachments) + if not isstring(Model) then return end + if not istable(Attachments) then return end + + SaveAttachments(Model, Attachments) + end + + function ACF.SetCustomAttachment(Model, Name, Pos, Ang, Bone) + if not isstring(Model) then return end + + SaveAttachments(Model, {{ + Name = Name, + Pos = Pos, + Ang = Ang, + Bone = Bone, + }}, true) + end + + function ACF.SetCustomAttachments(Model, Attachments) + if not isstring(Model) then return end + if not istable(Attachments) then return end + + SaveAttachments(Model, Attachments, true) + end + + function ACF.RemoveCustomAttachment(Model, Index) + if not isstring(Model) then return end + + local Data = GetModelData(Model, true) + + if not Data then return end + + table.remove(Data, Index) + + if not next(Data) then + Models[Model] = nil + end + end + + function ACF.RemoveCustomAttachments(Model) + if not isstring(Model) then return end + + local Data = GetModelData(Model, true) + + if not Data then return end + + for K in pairs(Data) do + Data[K] = nil + end + + Models[Model] = nil + end + + EntTable.LegacySetModel = EntTable.LegacySetModel or EntTable.SetModel + EntTable.LegacyGetAttachment = EntTable.LegacyGetAttachment or EntTable.GetAttachment + EntTable.LegacyGetAttachments = EntTable.LegacyGetAttachments or EntTable.GetAttachments + EntTable.LegacyLookupAttachment = EntTable.LegacyLookupAttachment or EntTable.LookupAttachment + + function EntTable:SetModel(Path, ...) + self:LegacySetModel(Path, ...) + + self.AttachData = GetModelData(Path, true) + end + + function EntTable:GetAttachment(Index, ...) + local Data = GetAttachData(self) + + if not Data then + return self:LegacyGetAttachment(Index, ...) + end + + local Attachment = Data[Index] + + if not Attachment then return end + + local Pos = Attachment.Pos + + if self.Scale then + Pos = Pos * self.Scale + end + + return { + Pos = self:LocalToWorld(Pos), + Ang = self:LocalToWorldAngles(Attachment.Ang), + } + end + + function EntTable:GetAttachments(...) + local Data = GetAttachData(self) + + if not Data then + return self:LegacyGetAttachments(...) + end + + local Result = {} + + for Index, Info in ipairs(Data) do + Result[Index] = { + id = Index, + name = Info.Name, + } + end + + return Result + end + + function EntTable:LookupAttachment(Name, ...) + local Data = GetAttachData(self) + + if not Data then + return self:LegacyLookupAttachment(Name, ...) + end + + for Index, Info in ipairs(Data) do + if Info.Name == Name then + return Index + end + end + + return 0 + end +end + +do -- File creation + function ACF.FolderExists(Path, Create) + if not isstring(Path) then return end + + local Exists = file.Exists(Path, "DATA") + + if not Exists and Create then + file.CreateDir(Path) + + return true + end + + return Exists + end + + function ACF.SaveToJSON(Path, Name, Table, GoodFormat) + if not isstring(Path) then return end + if not isstring(Name) then return end + if not istable(Table) then return end + + ACF.FolderExists(Path, true) -- Creating the folder if it doesn't exist + + local FullPath = Path .. "/" .. Name + + file.Write(FullPath, util.TableToJSON(Table, GoodFormat)) + end + + function ACF.LoadFromFile(Path, Name) + if not isstring(Path) then return end + if not isstring(Name) then return end + + local FullPath = Path .. "/" .. Name + + if not file.Exists(FullPath, "DATA") then return end + + return util.JSONToTable(file.Read(FullPath, "DATA")) + end +end + +do -- Ballistic functions + -- changes here will be automatically reflected in the armor properties tool + function ACF_CalcArmor(Area, Ductility, Mass) + return (Mass * 1000 / Area / 0.78) / (1 + Ductility) ^ 0.5 * ACF.ArmorMod + end + + function ACF_MuzzleVelocity(Propellant, Mass) + local PEnergy = ACF.PBase * ((1 + Propellant) ^ ACF.PScale - 1) + local Speed = ((PEnergy * 2000 / Mass) ^ ACF.MVScale) + local Final = Speed -- - Speed * math.Clamp(Speed/2000,0,0.5) + + return Final + end + + function ACF_Kinetic(Speed, Mass, LimitVel) + LimitVel = LimitVel or 99999 + Speed = Speed / 39.37 + + local Energy = { + Kinetic = (Mass * (Speed ^ 2)) / 2000, --Energy in KiloJoules + Momentum = Speed * Mass, + } + local KE = (Mass * (Speed ^ ACF.KinFudgeFactor)) / 2000 + Energy.Momentum + + Energy.Penetration = math.max(KE - (math.max(Speed - LimitVel, 0) ^ 2) / (LimitVel * 5) * (KE / 200) ^ 0.95, KE * 0.1) + --Energy.Penetration = math.max( KE - (math.max(Speed-LimitVel,0)^2)/(LimitVel*5) * (KE/200)^0.95 , KE*0.1 ) + --Energy.Penetration = math.max(Energy.Momentum^ACF.KinFudgeFactor - math.max(Speed-LimitVel,0)/(LimitVel*5) * Energy.Momentum , Energy.Momentum*0.1) + + return Energy + end +end + do -- Sound aliases local Stored = {} local Lookup = {} diff --git a/lua/acf/base/util/sv_util.lua b/lua/acf/base/util/sv_util.lua index c1ffc0033..9b13be9ad 100644 --- a/lua/acf/base/util/sv_util.lua +++ b/lua/acf/base/util/sv_util.lua @@ -1,58 +1,435 @@ -util.AddNetworkString("ACF_ChatMessage") +local ACF = ACF -function ACF.SendMessage(Player, Type, ...) - if not ... then return end +do -- Serverside console log messages + local Types = { + Normal = { + Prefix = "", + Color = Color(80, 255, 80) + }, + Info = { + Prefix = " - Info", + Color = Color(0, 233, 255) + }, + Warning = { + Prefix = " - Warning", + Color = Color(255, 160, 0) + }, + Error = { + Prefix = " - Error", + Color = Color(255, 80, 80) + } + } + + function ACF.AddLogType(Name, Prefix, TitleColor) + if not Name then return end + + Types[Name] = { + Prefix = Prefix and (" - " .. Prefix) or "", + Color = TitleColor or Color(80, 255, 80), + } + end + + function ACF.PrintLog(Type, ...) + if not ... then return end + + local Data = Types[Type] or Types.Normal + local Prefix = "[ACF" .. Data.Prefix .. "] " + local Message = istable(...) and ... or { ... } + + Message[#Message + 1] = "\n" + + MsgC(Data.Color, Prefix, color_white, unpack(Message)) + end +end + +do -- Clientside message delivery + util.AddNetworkString("ACF_ChatMessage") + + function ACF.SendMessage(Player, Type, ...) + if not ... then return end - local Message = istable(...) and ... or { ... } + local Message = istable(...) and ... or { ... } - net.Start("ACF_ChatMessage") - net.WriteString(Type or "Normal") - net.WriteTable(Message) - if IsValid(Player) then + net.Start("ACF_ChatMessage") + net.WriteString(Type or "Normal") + net.WriteTable(Message) + if IsValid(Player) then + net.Send(Player) + else + net.Broadcast() + end + end +end + +do -- Networked notifications + util.AddNetworkString("ACF_Notify") + + function ACF.SendNotify(Player, Success, Message) + net.Start("ACF_Notify") + net.WriteBool(Success or false) + net.WriteString(Message or "") net.Send(Player) - else - net.Broadcast() end + + ACF_SendNotify = ACF.SendNotify -- Backwards compatibility end -local Types = { - Normal = { - Prefix = "", - Color = Color(80, 255, 80) - }, - Info = { - Prefix = " - Info", - Color = Color(0, 233, 255) - }, - Warning = { - Prefix = " - Warning", - Color = Color(255, 160, 0) - }, - Error = { - Prefix = " - Error", - Color = Color(255, 80, 80) - } -} +do -- HTTP Request + local NoRequest = true + local http = http + local Queue = {} + local Count = 0 -function ACF.AddLogType(Name, Prefix, TitleColor) - if not Name then return end + local function SuccessfulRequest(Code, Body, OnSuccess, OnFailure) + local Data = Body and util.JSONToTable(Body) + local Error - Types[Name] = { - Prefix = Prefix and (" - " .. Prefix) or "", - Color = TitleColor or Color(80, 255, 80), - } + if not Body then + Error = "No data found on request." + elseif Code ~= 200 then + Error = "Request unsuccessful (Code " .. Code .. ")." + elseif not (Data and next(Data)) then + Error = "Empty request result." + end + + if Error then + ACF.PrintLog("HTTP_Error", Error) + + if OnFailure then + OnFailure(Error) + end + elseif OnSuccess then + OnSuccess(Body, Data) + end + end + + function ACF.StartRequest(Link, OnSuccess, OnFailure, Headers) + if not isstring(Link) then return end + if not isfunction(OnSuccess) then OnSuccess = nil end + if not isfunction(OnFailure) then OnFailure = nil end + if not istable(Headers) then Headers = nil end + + if NoRequest then + Count = Count + 1 + + Queue[Count] = { + Link = Link, + OnSuccess = OnSuccess, + OnFailure = OnFailure, + Headers = Headers, + } + + return + end + + http.Fetch( + Link, + function(Body, _, _, Code) + SuccessfulRequest(Code, Body, OnSuccess, OnFailure) + end, + function(Error) + ACF.PrintLog("HTTP_Error", Error) + + if OnFailure then + OnFailure(Error) + end + end, + Headers) + end + + hook.Add("Initialize", "ACF Allow Requests", function() + timer.Simple(0, function() + NoRequest = nil + + if Count > 0 then + for _, Request in ipairs(Queue) do + ACF.StartRequest( + Request.Link, + Request.OnSuccess, + Request.OnFailure, + Request.Headers + ) + end + end + + Count = nil + Queue = nil + end) + + hook.Remove("Initialize", "ACF Allow Requests") + end) + + ACF.AddLogType("HTTP_Error", "HTTP", Color(241, 80, 47)) +end + +do -- Entity saving and restoring + local Constraints = duplicator.ConstraintType + local Saved = {} + + function ACF.SaveEntity(Entity) + if not IsValid(Entity) then return end + + local PhysObj = Entity:GetPhysicsObject() + + if not IsValid(PhysObj) then return end + + Saved[Entity] = { + Constraints = constraint.GetTable(Entity), + Gravity = PhysObj:IsGravityEnabled(), + Motion = PhysObj:IsMotionEnabled(), + Contents = PhysObj:GetContents(), + Material = PhysObj:GetMaterial(), + } + + Entity:CallOnRemove("ACF_RestoreEntity", function() + Saved[Entity] = nil + end) + end + + function ACF.RestoreEntity(Entity) + if not IsValid(Entity) then return end + if not Saved[Entity] then return end + + local PhysObj = Entity:GetPhysicsObject() + local EntData = Saved[Entity] + + PhysObj:EnableGravity(EntData.Gravity) + PhysObj:EnableMotion(EntData.Motion) + PhysObj:SetContents(EntData.Contents) + PhysObj:SetMaterial(EntData.Material) + + for _, Data in ipairs(EntData.Constraints) do + local Constraint = Data.Type and Constraints[Data.Type] + + if not Constraint then continue end + + local Args = {} + + for Index, Name in ipairs(Constraint.Args) do + Args[Index] = Data[Name] + end + + Constraint.Func(unpack(Args)) + end + + Saved[Entity] = nil + + Entity:RemoveCallOnRemove("ACF_RestoreEntity") + end +end + +do -- Entity linking + local EntityLink = {} + local function GetEntityLinks(Entity, VarName, SingleEntry) + if not Entity[VarName] then return {} end + + if SingleEntry then + return { [Entity[VarName]] = true } + end + + local Result = {} + + for K in pairs(Entity[VarName]) do + Result[K] = true + end + + return Result + end + + -- If your entity can link/unlink other entities, you should use this + function ACF.RegisterLinkSource(Class, VarName, SingleEntry) + local Data = EntityLink[Class] + + if not Data then + EntityLink[Class] = { + [VarName] = function(Entity) + return GetEntityLinks(Entity, VarName, SingleEntry) + end + } + else + Data[VarName] = function(Entity) + return GetEntityLinks(Entity, VarName, SingleEntry) + end + end + end + + function ACF.GetAllLinkSources(Class) + if not EntityLink[Class] then return {} end + + local Result = {} + + for K, V in pairs(EntityLink[Class]) do + Result[K] = V + end + + return Result + end + + function ACF.GetLinkSource(Class, VarName) + if not EntityLink[Class] then return end + + return EntityLink[Class][VarName] + end + + function ACF.GetLinkedEntities(Entity) + if not IsValid(Entity) then return {} end + + local Links = EntityLink[Entity:GetClass()] + + if not Links then return {} end + + local Result = {} + + for _, Function in pairs(Links) do + for Ent in pairs(Function(Entity)) do + Result[Ent] = true + end + end + + return Result + end + + local ClassLink = { Link = {}, Unlink = {} } + local function RegisterNewLink(Action, Class1, Class2, Function) + if not isfunction(Function) then return end + + local Target = ClassLink[Action] + local Data1 = Target[Class1] + + if not Data1 then + Target[Class1] = { + [Class2] = function(Ent1, Ent2) + return Function(Ent1, Ent2) + end + } + else + Data1[Class2] = function(Ent1, Ent2) + return Function(Ent1, Ent2) + end + end + + if Class1 == Class2 then return end + + local Data2 = Target[Class2] + + if not Data2 then + Target[Class2] = { + [Class1] = function(Ent2, Ent1) + return Function(Ent1, Ent2) + end + } + else + Data2[Class1] = function(Ent2, Ent1) + return Function(Ent1, Ent2) + end + end + end + + function ACF.RegisterClassLink(Class1, Class2, Function) + RegisterNewLink("Link", Class1, Class2, Function) + end + + function ACF.GetClassLink(Class1, Class2) + if not ClassLink.Link[Class1] then return end + + return ClassLink.Link[Class1][Class2] + end + + function ACF.RegisterClassUnlink(Class1, Class2, Function) + RegisterNewLink("Unlink", Class1, Class2, Function) + end + + function ACF.GetClassUnlink(Class1, Class2) + if not ClassLink.Unlink[Class1] then return end + + return ClassLink.Unlink[Class1][Class2] + end end -function ACF.PrintLog(Type, ...) - if not ... then return end +do -- Entity inputs + local Inputs = {} + + local function GetClass(Class) + if not Inputs[Class] then + Inputs[Class] = {} + end + + return Inputs[Class] + end + + function ACF.AddInputAction(Class, Name, Action) + if not Class then return end + if not Name then return end + if not isfunction(Action) then return end + + local Data = GetClass(Class) - local Data = Types[Type] or Types.Normal - local Prefix = "[ACF" .. Data.Prefix .. "] " - local Message = istable(...) and ... or { ... } + Data[Name] = Action + end + + function ACF.GetInputAction(Class, Name) + if not Class then return end + if not Name then return end + + local Data = GetClass(Class) + + return Data[Name] + end + + function ACF.GetInputActions(Class) + if not Class then return end + + return GetClass(Class) + end +end - Message[#Message + 1] = "\n" +do -- Extra overlay text + local Classes = {} - MsgC(Data.Color, Prefix, color_white, unpack(Message)) + function ACF.RegisterOverlayText(ClassName, Identifier, Function) + if not isstring(ClassName) then return end + if Identifier == nil then return end + if not isfunction(Function) then return end + + local Class = Classes[ClassName] + + if not Class then + Classes[ClassName] = { + [Identifier] = Function + } + else + Class[Identifier] = Function + end + end + + function ACF.RemoveOverlayText(ClassName, Identifier) + if not isstring(ClassName) then return end + if Identifier == nil then return end + + local Class = Classes[ClassName] + + if not Class then return end + + Class[Identifier] = nil + end + + function ACF.GetOverlayText(Entity) + local Class = Classes[Entity:GetClass()] + + if not Class then return "" end + + local Result = "" + + for _, Function in pairs(Class) do + local Text = Function(Entity) + + if Text and Text ~= "" then + Result = Result .. "\n\n" .. Text + end + end + + return Result + end end function ACF_GetHitAngle(HitNormal, HitVector) diff --git a/lua/acf/base/version/cl_version.lua b/lua/acf/base/version/cl_version.lua index 58b4d4fd1..9bcbcca48 100644 --- a/lua/acf/base/version/cl_version.lua +++ b/lua/acf/base/version/cl_version.lua @@ -1,11 +1,9 @@ -local Repos = ACF.Repositories +local ACF = ACF do -- Server syncronization and status printing - local PrintToChat = ACF.PrintToChat - - local Unique = { - Branches = true, - } + local Repos = ACF.Repositories + local Queue = {} + local Standby local Messages = { ["Unable to check"] = { @@ -22,50 +20,68 @@ do -- Server syncronization and status printing }, } - local function GenerateCopy(Name, Data) - local Version = ACF.GetVersion(Name) + local function StoreInfo(Name, Data, Destiny) + local NewData = Data[Destiny] - if not Version.Server then - Version.Server = {} - end + if not NewData then return end - for K, V in pairs(Data) do - if not Unique[K] then - Version.Server[K] = V - else - Version[K] = V - end - end + local Repo = ACF.GetRepository(Name) + local Source = Repo[Destiny] - ACF.GetVersionStatus(Name) + for K, V in pairs(NewData) do + Source[K] = V + end end local function PrintStatus(Server) - local Branch = ACF.GetBranch(Server.Name, Server.Head) - local Lapse = Branch and ACF.GetTimeLapse(Branch.Date) - - local Data = Messages[Server.Status or "Unable to check"] - local Message = Data.Message + local Branch = ACF.GetBranch(Server.Name, Server.Head) + local Lapse = Branch and ACF.GetTimeLapse(Branch.Date) + local Data = Messages[Server.Status or "Unable to check"] - PrintToChat(Data.Type, Message:format(Server.Name, Server.Code, Lapse)) + ACF.PrintToChat(Data.Type, Data.Message:format(Server.Name, Server.Code, Lapse)) end - net.Receive("ACF_VersionSync", function() - local Table = net.ReadTable() - - for Name, Data in pairs(Table) do - GenerateCopy(Name, Data) - end + local function PrepareStatus() + if Standby then return end hook.Add("CreateMove", "ACF Print Version", function(Move) if Move:GetButtons() ~= 0 then - for _, Data in pairs(Repos) do - PrintStatus(Data.Server) + for Name, Repo in pairs(Queue) do + PrintStatus(Repo.Server) + + Queue[Name] = nil end + Standby = nil + hook.Remove("CreateMove", "ACF Print Version") end end) + + Standby = true + end + + local function QueueStatusMessage(Name) + if Queue[Name] then return end + + Queue[Name] = ACF.GetRepository(Name) + + PrepareStatus() + end + + net.Receive("ACF_VersionSync", function() + local Values = util.JSONToTable(net.ReadString()) + + for Name, Repo in pairs(Values) do + StoreInfo(Name, Repo, "Branches") + StoreInfo(Name, Repo, "Server") + + ACF.CheckLocalStatus(Name) + + QueueStatusMessage(Name) + + hook.Run("ACF_UpdatedRepository", Name, Repos[Name]) + end end) ACF.AddMessageType("Update_Ok", "Updates") diff --git a/lua/acf/base/version/sh_version.lua b/lua/acf/base/version/sh_version.lua index 9bd3f163d..b57027668 100644 --- a/lua/acf/base/version/sh_version.lua +++ b/lua/acf/base/version/sh_version.lua @@ -1,30 +1,29 @@ +local ACF = ACF local Repos = ACF.Repositories +local Realm = SERVER and "Server" or "Client" -do -- Repository tracking - function ACF.AddRepository(Owner, Name, Path) - if not Owner then return end - if not Name then return end - if not Path then return end - if Repos[Name] then return end - Repos[Name] = { - Owner = Owner, - Name = Name, - Path = "addons/%s/" .. Path, - Code = false, - Date = false, - } - end - - ACF.AddRepository("Stooberton", "ACF-3", "lua/autorun/acf_loader.lua") -end +do -- Local repository version checking + local file = file + local os = os -do -- ACF.GetVersion function local function LocalToUTC(Time) return os.time(os.date("!*t", Time)) end - local function SetRealOwner(Path, Version) + local function GetPath(Data) + local _, Folders = file.Find("addons/*", "GAME") + local Pattern = Data.Pattern + + for _, Folder in ipairs(Folders) do + if file.Exists(Pattern:format(Folder), "GAME") then + return "addons/" .. Folder + end + end + end + + -- Makes sure the owner of the repo is correct, deals with forks + local function UpdateOwner(Path, Data) if not file.Exists(Path .. "/.git/FETCH_HEAD", "GAME") then return end local Fetch = file.Read(Path .. "/.git/FETCH_HEAD", "GAME") @@ -32,21 +31,19 @@ do -- ACF.GetVersion function if not Start then return end -- File is empty - Version.Owner = Fetch:sub(Start + 11, End - 1) + Data.Owner = Fetch:sub(Start + 11, End - 1) end - local function GetGitData(Path, Version) + local function GetGitData(Path, Data) local _, _, Head = file.Read(Path .. "/.git/HEAD", "GAME"):find("heads/(.+)$") local Heads = Path .. "/.git/refs/heads/" local Files = file.Find(Heads .. "*", "GAME") local Code, Date - SetRealOwner(Path, Version) - - Version.Head = Head:Trim() + Data.Head = Head:Trim() for _, Name in ipairs(Files) do - if Name == Version.Head then + if Name == Data.Head then local SHA = file.Read(Heads .. Name, "GAME"):Trim() Code = Name .. "-" .. SHA:sub(1, 7) @@ -59,83 +56,126 @@ do -- ACF.GetVersion function return Code, Date end - function ACF.GetVersion(Name) - local Version = Repos[Name] + ------------------------------------------------------------------- - if not Version then return end - if Version.Code then return Version end + function ACF.CheckLocalVersion(Name) + if not isstring(Name) then return end - local _, Folders = file.Find("addons/*", "GAME") - local Pattern = Version.Path - local Path, Code, Date + local Data = ACF.GetLocalRepo(Name) - for _, Folder in ipairs(Folders) do - if file.Exists(Pattern:format(Folder), "GAME") then - Path = "addons/" .. Folder - break - end - end + if not Data then return end + + local Path = GetPath(Data) if not Path then - Version.Code = "Not Installed" - Version.Date = 0 + Data.Code = "Not Installed" + Data.Date = 0 + Data.NoFiles = true elseif file.Exists(Path .. "/.git/HEAD", "GAME") then - Code, Date = GetGitData(Path, Version) + local Code, Date = GetGitData(Path, Data) - Version.Code = "Git-" .. Code - Version.Date = LocalToUTC(Date) + UpdateOwner(Path, Data) + + Data.Code = "Git-" .. Code + Data.Date = LocalToUTC(Date) elseif file.Exists(Path .. "/LICENSE", "GAME") then - Date = file.Time(Path .. "/LICENSE", "GAME") + local Date = file.Time(Path .. "/LICENSE", "GAME") - Version.Code = "ZIP-Unknown" - Version.Date = LocalToUTC(Date) + Data.Code = "ZIP-Unknown" + Data.Date = LocalToUTC(Date) end - if not Version.Head then - Version.Head = "master" + if not Data.Head then + Data.Head = "master" end - - return Version end end -function ACF.GetBranch(Name, Branch) - local Version = Repos[Name] +do -- Local repository status checking + local function IsUpdated(Data, Branch) + if not isnumber(Data.Date) then return false end - if not Version then return end - if not Version.Branches then return end + return Data.Date >= Branch.Date + end - Branch = Branch or Version.Head + function ACF.CheckLocalStatus(Name) + if not isstring(Name) then return end - -- Just in case both server and client are using different forks with different branches - return Version.Branches[Branch] or Version.Branches.master -end + local Repo = ACF.GetRepository(Name) + + if not Repo then return end -local function CheckVersionDate(Version, Branch) - if not isnumber(Version.Date) then return false end - if not isnumber(Branch.Date) then return false end + local Data = Repo[Realm] + local Branches = Repo.Branches + local Branch = Branches[Data.Head] or Branches.master - return Version.Date >= Branch.Date + if not (Branch and Branch.Date) or Data.NoFiles then + Data.Status = "Unable to check" + elseif Data.Code == Branch.Code or IsUpdated(Data, Branch) then + Data.Status = "Up to date" + else + Data.Status = "Out of date" + end + end end -function ACF.GetVersionStatus(Name) - local Version = Repos[Name] +do -- Repository functions + function ACF.AddRepository(Owner, Name, File) + if not isstring(Owner) then return end + if not isstring(Name) then return end + if not isstring(File) then return end + if Repos[Name] then return end - if not Version then return end - if Version.Status then return Version.Status end + Repos[Name] = { + [Realm] = { + Pattern = "addons/%s/" .. File, + Owner = Owner, + Name = Name, + }, + Branches = {}, + } - local Branch = ACF.GetBranch(Name) - local Status + if CLIENT then + Repos[Name].Server = {} + end + + ACF.CheckLocalVersion(Name) + end - if not Branch or Version.Code == "Not Installed" then - Status = "Unable to check" - elseif Version.Code == Branch.Code or CheckVersionDate(Version, Branch) then - Status = "Up to date" - else - Status = "Out of date" + function ACF.GetRepository(Name) + if not isstring(Name) then return end + + return Repos[Name] end - Version.Status = Status + function ACF.GetLocalRepo(Name) + if not isstring(Name) then return end + + local Data = Repos[Name] - return Status + return Data and Data[Realm] + end + + ACF.AddRepository("Stooberton", "ACF-3", "lua/autorun/acf_loader.lua") +end + +do -- Branch functions + function ACF.GetBranches(Name) + if not isstring(Name) then return end + + local Data = Repos[Name] + + return Data and Data.Branches + end + + function ACF.GetBranch(Name, Branch) + if not isstring(Name) then return end + if not isstring(Branch) then return end + + local Data = Repos[Name] + + if not Data then return end + + return Data.Branches[Branch] + end end diff --git a/lua/acf/base/version/sv_version.lua b/lua/acf/base/version/sv_version.lua index 263a2ab95..ce0623ba8 100644 --- a/lua/acf/base/version/sv_version.lua +++ b/lua/acf/base/version/sv_version.lua @@ -1,60 +1,25 @@ +local ACF = ACF local Repos = ACF.Repositories -local PrintLog = ACF.PrintLog - -do -- HTTP Request - local function SuccessfulRequest(Code, Body, OnSuccess, OnFailure) - local Data = Body and util.JSONToTable(Body) - local Error - - if not Body then - Error = "No data found on request." - elseif Code ~= 200 then - Error = "Request unsuccessful (Code " .. Code .. ")." - elseif not (Data and next(Data)) then - Error = "Empty request result." - end - - if Error then - PrintLog("Error", Error) - return OnFailure(Error) - end +do -- Github data conversion + local string = string + local os = os - OnSuccess(Body, Data) - end + local DateData = { year = true, month = true, day = true, hour = true, min = true, sec = true } - function ACF.StartRequest(Link, OnSuccess, OnFailure, Headers) - OnSuccess = OnSuccess or function() end - OnFailure = OnFailure or function() end - - http.Fetch( - Link, - function(Body, _, _, Code) - SuccessfulRequest(Code, Body, OnSuccess, OnFailure) - end, - function(Error) - PrintLog("Error", Error) - - OnFailure(Error) - end, - Headers) - end -end - -do -- Github data conversion function ACF.GetDateEpoch(GitDate) local Date, Time = unpack(string.Explode("T", GitDate)) local Year, Month, Day = unpack(string.Explode("-", Date)) local Hour, Min, Sec = unpack(string.Explode(":", Time)) - return os.time({ - year = Year, - month = Month, - day = Day, - hour = Hour, - min = Min, - sec = Sec:sub(1, 2), - }) + DateData.year = Year + DateData.month = Month + DateData.day = Day + DateData.hour = Hour + DateData.min = Min + DateData.sec = Sec:sub(1, 2) + + return os.time(DateData) end function ACF.GetCommitMessage(Message) @@ -65,14 +30,14 @@ do -- Github data conversion Message = Message:Replace("\n\n", "\n"):gsub("[\r]*[\n]+[%s]+", "\n- ") local Title = Start and Message:sub(1, Start - 1) or Message - local Body = Start and Message:sub(Start + 1, #Message) or "No Commit Message" + local Body = Start and Message:sub(Start + 1, #Message) or "No Commit Message" return Title, Body end end do -- Branch version retrieval and version printing - local Branches = "https://api.github.com/repos/%s/%s/branches" + local BranchLink = "https://api.github.com/repos/%s/%s/branches" local Commits = "https://api.github.com/repos/%s/%s/commits?per_page=1&sha=%s" local Messages = { @@ -90,77 +55,62 @@ do -- Branch version retrieval and version printing }, } - local function PrintStatus(Version) - local Branch = ACF.GetBranch(Version.Name) - local Lapse = ACF.GetTimeLapse(Branch.Date) - - local Data = Messages[Version.Status] - local Message = Data.Message + local function PrintStatus(Data) + local Branch = ACF.GetBranch(Data.Name, Data.Head) + local Lapse = ACF.GetTimeLapse(Branch.Date) + local LogData = Messages[Data.Status] - PrintLog(Data.Type, Message:format(Version.Name, Version.Code, Lapse)) + ACF.PrintLog(LogData.Type, LogData.Message:format(Data.Name, Data.Code, Lapse)) end local function GetBranchData(Data, Branch, Commit) local Title, Body = ACF.GetCommitMessage(Commit.commit.message) local Date = ACF.GetDateEpoch(Commit.commit.author.date) - Branch.Title = Title - Branch.Body = Body - Branch.Date = Date - Branch.Link = Commit.html_url + Branch.Title = Title + Branch.Body = Body + Branch.Date = Date + Branch.Author = Commit.commit.author.name + Branch.Link = Commit.html_url - if Data.Head == Branch.Name then - ACF.GetVersionStatus(Data.Name) + if Branch.Name == Data.Head then + ACF.CheckLocalStatus(Data.Name) PrintStatus(Data) end end - local function GetBranches(Name, Data, List) - local Request = ACF.StartRequest - - Data.Branches = {} - + local function LoadBranches(Data, Branches, List) for _, Branch in ipairs(List) do local SHA = Branch.commit.sha - - Data.Branches[Branch.name] = { + local Current = { Name = Branch.name, Code = "Git-" .. Branch.name .. "-" .. SHA:sub(1, 7), - Title = false, - Body = false, - Date = false, - Link = false, } - local Current = Data.Branches[Branch.name] + Branches[Branch.name] = Current - Request( - Commits:format(Data.Owner, Name, SHA), + ACF.StartRequest( + Commits:format(Data.Owner, Data.Name, SHA), function(_, Commit) GetBranchData(Data, Current, unpack(Commit)) - end) + end + ) end end - local function CheckAllRepos() - local Request = ACF.StartRequest - - for Name, Data in pairs(Repos) do - ACF.GetVersion(Name) + hook.Add("Initialize", "ACF Request Git Data", function() + for Name, Repo in pairs(Repos) do + local Data = Repo.Server - Request( - Branches:format(Data.Owner, Name), + ACF.StartRequest( + BranchLink:format(Data.Owner, Name), function(_, List) - GetBranches(Name, Data, List) + LoadBranches(Data, Repo.Branches, List) end) end - end - - hook.Add("Initialize", "ACF Request Git Data", function() - timer.Simple(0, CheckAllRepos) - hook.Add("Initialize", "ACF Request Git Data") + hook.Remove("Initialize", "ACF Request Git Data") end) ACF.AddLogType("Update_Ok", "Updates") @@ -171,17 +121,11 @@ end do -- Client syncronization util.AddNetworkString("ACF_VersionSync") - local function SyncInformation(Player) - if not IsValid(Player) then return end + hook.Add("ACF_OnPlayerLoaded", "ACF_VersionSync", function(Player) + local JSON = util.TableToJSON(Repos) net.Start("ACF_VersionSync") - net.WriteTable(Repos) + net.WriteString(JSON) net.Send(Player) - end - - hook.Add("PlayerInitialSpawn", "ACF_VersionSync", function(Player) - timer.Simple(7.5, function() - SyncInformation(Player) - end) end) end diff --git a/lua/acf/client/ammo_menu.lua b/lua/acf/client/ammo_menu.lua new file mode 100644 index 000000000..901f90dca --- /dev/null +++ b/lua/acf/client/ammo_menu.lua @@ -0,0 +1,286 @@ + +local ACF = ACF +local Classes = ACF.Classes +local AmmoTypes = Classes.AmmoTypes +local BoxSize = Vector() +local Ammo, BulletData + +local CrateText = [[ + Crate Armor: %s mm + Crate Mass : %s + Crate Capacity : %s round(s)]] + +local function CopySettings(Settings) + local Copy = {} + + if Settings then + for K, V in pairs(Settings) do + Copy[K] = V + end + end + + return Copy +end + +local function GetAmmoList(Class) + local Result = {} + + for K, V in pairs(AmmoTypes) do + if V.Unlistable then continue end + if V.Blacklist[Class] then continue end + + Result[K] = V + end + + return Result +end + +local function GetWeaponData(ToolData) + local Destiny = Classes[ToolData.Destiny or "Weapons"] + local Class = ACF.GetClassGroup(Destiny, ToolData.Weapon) + + return Class.Lookup[ToolData.Weapon] +end + +local function GetEmptyMass() + local Armor = ACF.AmmoArmor * 0.039 -- Millimeters to inches + local ExteriorVolume = BoxSize.x * BoxSize.y * BoxSize.z + local InteriorVolume = (BoxSize.x - Armor) * (BoxSize.y - Armor) * (BoxSize.z - Armor) + + return math.Round((ExteriorVolume - InteriorVolume) * 0.13, 2) +end + +local function AddPreview(Base, Settings, ToolData) + if Settings.SuppressPreview then return end + + local Preview = Base:AddModelPreview() + Preview:SetCamPos(Vector(45, 45, 30)) + Preview:SetHeight(120) + Preview:SetFOV(50) + + if Ammo.AddAmmoPreview then + Ammo:AddAmmoPreview(Preview, ToolData, BulletData) + end + + hook.Run("ACF_AddAmmoPreview", Preview, ToolData, Ammo, BulletData) +end + +local function AddControls(Base, Settings, ToolData) + if Settings.SuppressControls then return end + + local RoundLength = Base:AddLabel() + RoundLength:TrackClientData("Projectile", "SetText", "GetText") + RoundLength:TrackClientData("Propellant") + RoundLength:TrackClientData("Tracer") + RoundLength:DefineSetter(function() + local Text = "Round Length: %s / %s cm" + local CurLength = BulletData.ProjLength + BulletData.PropLength + BulletData.Tracer + local MaxLength = BulletData.MaxRoundLength + + return Text:format(CurLength, MaxLength) + end) + + local Projectile = Base:AddSlider("Projectile Length", 0, BulletData.MaxRoundLength, 2) + Projectile:SetClientData("Projectile", "OnValueChanged") + Projectile:DefineSetter(function(Panel, _, _, Value, IsTracked) + ToolData.Projectile = Value + + if not IsTracked then + BulletData.Priority = "Projectile" + end + + Ammo:UpdateRoundData(ToolData, BulletData) + + ACF.SetClientData("Propellant", BulletData.PropLength) + + Panel:SetValue(BulletData.ProjLength) + + return BulletData.ProjLength + end) + + local Propellant = Base:AddSlider("Propellant Length", 0, BulletData.MaxRoundLength, 2) + Propellant:SetClientData("Propellant", "OnValueChanged") + Propellant:DefineSetter(function(Panel, _, _, Value, IsTracked) + ToolData.Propellant = Value + + if not IsTracked then + BulletData.Priority = "Propellant" + end + + Ammo:UpdateRoundData(ToolData, BulletData) + + ACF.SetClientData("Projectile", BulletData.ProjLength) + + Panel:SetValue(BulletData.PropLength) + + return BulletData.PropLength + end) + + if Ammo.AddAmmoControls then + Ammo:AddAmmoControls(Base, ToolData, BulletData) + end + + hook.Run("ACF_AddAmmoControls", Base, ToolData, Ammo, BulletData) + + -- We'll create the tracer checkbox after all the other controls + if not Settings.SuppressTracer then + local Tracer = Base:AddCheckBox("Tracer") + Tracer:SetClientData("Tracer", "OnChange") + Tracer:DefineSetter(function(Panel, _, _, Value) + ToolData.Tracer = Value + + Ammo:UpdateRoundData(ToolData, BulletData) + + ACF.SetClientData("Projectile", BulletData.ProjLength) + ACF.SetClientData("Propellant", BulletData.PropLength) + + Panel:SetText("Tracer : " .. BulletData.Tracer .. " cm") + Panel:SetValue(ToolData.Tracer) + + return ToolData.Tracer + end) + else + ACF.SetClientData("Tracer", false) -- Disabling the tracer, as it takes up spaces on ammo. + end +end + +local function AddInformation(Base, Settings, ToolData) + if Settings.SuppressInformation then return end + + if not Settings.SuppressCrateInformation then + local Trackers = {} + + local Crate = Base:AddLabel() + Crate:TrackClientData("Weapon", "SetText") + Crate:TrackClientData("CrateSizeX") + Crate:TrackClientData("CrateSizeY") + Crate:TrackClientData("CrateSizeZ") + Crate:DefineSetter(function() + local Weapon = GetWeaponData(ToolData) + local Spacing = Weapon.Caliber * 0.0039 + local Rounds = ACF.GetAmmoCrateCapacity(BoxSize, Weapon, BulletData, Spacing, ACF.AmmoArmor) + local Empty = GetEmptyMass() + local Load = math.floor(BulletData.CartMass * Rounds) + local Mass = ACF.GetProperMass(math.floor(Empty + Load)) + + return CrateText:format(ACF.AmmoArmor, Mass, Rounds) + end) + + if Ammo.AddCrateDataTrackers then + Ammo:AddCrateDataTrackers(Trackers, ToolData, BulletData) + end + + hook.Run("ACF_AddCrateDataTrackers", Trackers, ToolData, Ammo, BulletData) + + for Tracker in pairs(Trackers) do + Crate:TrackClientData(Tracker) + end + end + + if Ammo.AddAmmoInformation then + Ammo:AddAmmoInformation(Base, ToolData, BulletData) + end + + hook.Run("ACF_AddAmmoInformation", Base, ToolData, Ammo, BulletData) +end + +function ACF.GetCurrentAmmoData() + return BulletData +end + +function ACF.UpdateAmmoMenu(Menu, Settings) + if not Ammo then return end + + local ToolData = ACF.GetAllClientData() + local Base = Menu.AmmoBase + + BulletData = Ammo:ClientConvert(ToolData) + Settings = CopySettings(Settings) + + if Ammo.SetupAmmoMenuSettings then + Ammo:SetupAmmoMenuSettings(Settings, ToolData, BulletData) + end + + hook.Run("ACF_SetupAmmoMenuSettings", Settings, ToolData, Ammo, BulletData) + + Menu:ClearTemporal(Base) + Menu:StartTemporal(Base) + + if not Settings.SuppressMenu then + AddPreview(Base, Settings, ToolData) + AddControls(Base, Settings, ToolData) + AddInformation(Base, Settings, ToolData) + end + + Base:AddLabel("This entity can be fully parented.") + + Menu:EndTemporal(Base) +end + +function ACF.CreateAmmoMenu(Menu, Settings) + Menu:AddTitle("Ammo Settings") + + local List = Menu:AddComboBox() + + local SizeX = Menu:AddSlider("Crate Width", 6, 96, 2) + SizeX:SetClientData("CrateSizeX", "OnValueChanged") + SizeX:DefineSetter(function(Panel, _, _, Value) + local X = math.Round(Value, 2) + + Panel:SetValue(X) + + BoxSize.x = X + + return X + end) + + local SizeY = Menu:AddSlider("Crate Height", 6, 96, 2) + SizeY:SetClientData("CrateSizeY", "OnValueChanged") + SizeY:DefineSetter(function(Panel, _, _, Value) + local Y = math.Round(Value, 2) + + Panel:SetValue(Y) + + BoxSize.y = Y + + return Y + end) + + local SizeZ = Menu:AddSlider("Crate Depth", 6, 96, 2) + SizeZ:SetClientData("CrateSizeZ", "OnValueChanged") + SizeZ:DefineSetter(function(Panel, _, _, Value) + local Z = math.Round(Value, 2) + + Panel:SetValue(Z) + + BoxSize.z = Z + + return Z + end) + + local Base = Menu:AddCollapsible("Ammo Information") + local Desc = Base:AddLabel() + + function List:LoadEntries(Class) + ACF.LoadSortedList(self, GetAmmoList(Class), "Name") + end + + function List:OnSelect(Index, _, Data) + if self.Selected == Data then return end + + self.ListData.Index = Index + self.Selected = Data + + Ammo = Data + + ACF.SetClientData("AmmoType", Data.ID) + + Desc:SetText(Data.Description) + + ACF.UpdateAmmoMenu(Menu, Settings) + end + + Menu.AmmoBase = Base + + return List +end diff --git a/lua/acf/client/cl_ballistics.lua b/lua/acf/client/ballistics.lua similarity index 100% rename from lua/acf/client/cl_ballistics.lua rename to lua/acf/client/ballistics.lua diff --git a/lua/acf/client/cl_damage.lua b/lua/acf/client/cl_damage.lua deleted file mode 100644 index 81bb69f01..000000000 --- a/lua/acf/client/cl_damage.lua +++ /dev/null @@ -1,70 +0,0 @@ -local Damaged = { - CreateMaterial("ACF_Damaged1", "VertexLitGeneric", { - ["$basetexture"] = "damaged/damaged1" - }), - CreateMaterial("ACF_Damaged2", "VertexLitGeneric", { - ["$basetexture"] = "damaged/damaged2" - }), - CreateMaterial("ACF_Damaged3", "VertexLitGeneric", { - ["$basetexture"] = "damaged/damaged3" - }) -} - -hook.Add("PostDrawOpaqueRenderables", "ACF_RenderDamage", function() - if not ACF_HealthRenderList then return end - cam.Start3D(EyePos(), EyeAngles()) - - for k, ent in pairs(ACF_HealthRenderList) do - if IsValid(ent) then - render.ModelMaterialOverride(ent.ACF_Material) - render.SetBlend(math.Clamp(1 - ent.ACF_HealthPercent, 0, 0.8)) - ent:DrawModel() - elseif ACF_HealthRenderList then - table.remove(ACF_HealthRenderList, k) - end - end - - render.ModelMaterialOverride() - render.SetBlend(1) - cam.End3D() -end) - -net.Receive("ACF_RenderDamage", function() - local Table = net.ReadTable() - - for _, v in ipairs(Table) do - local ent, Health, MaxHealth = ents.GetByIndex(v.ID), v.Health, v.MaxHealth - if not IsValid(ent) then return end - - if Health ~= MaxHealth then - ent.ACF_Health = Health - ent.ACF_MaxHealth = MaxHealth - ent.ACF_HealthPercent = Health / MaxHealth - - if ent.ACF_HealthPercent > 0.7 then - ent.ACF_Material = Damaged[1] - elseif ent.ACF_HealthPercent > 0.3 then - ent.ACF_Material = Damaged[2] - elseif ent.ACF_HealthPercent <= 0.3 then - ent.ACF_Material = Damaged[3] - end - - ACF_HealthRenderList = ACF_HealthRenderList or {} - ACF_HealthRenderList[ent:EntIndex()] = ent - else - if ACF_HealthRenderList then - if #ACF_HealthRenderList <= 1 then - ACF_HealthRenderList = nil - else - table.remove(ACF_HealthRenderList, ent:EntIndex()) - end - - if ent.ACF then - ent.ACF.Health = nil - ent.ACF.MaxHealth = nil - end - end - end - end -end) - diff --git a/lua/acf/client/copy_menu.lua b/lua/acf/client/copy_menu.lua new file mode 100644 index 000000000..e72ddf70f --- /dev/null +++ b/lua/acf/client/copy_menu.lua @@ -0,0 +1,149 @@ +local CopiedData = {} +local Disabled = {} +local Selected + +net.Receive("ACF_SendCopyData", function(_, Player) + local Class = net.ReadString() + local List = util.JSONToTable(net.ReadString()) + + if IsValid(Player) then return end -- Trust nobody, not even net messages + + table.SortByMember(List, "Key", true) + + CopiedData[Class] = List + Selected = Class + + if not Disabled[Class] then + Disabled[Class] = {} + end + + RunConsoleCommand("acf_reload_copy_menu") -- Yeah. +end) + +local function GetIcon(Class, Key) + local Data = Disabled[Class] + + return Data[Key] and "icon16/delete.png" or "icon16/accept.png" +end + +local function PopulateTree(Tree, Data) + local Height = 0.5 + + Tree:Clear() + + for _, Info in ipairs(Data) do + local Icon = GetIcon(Selected, Info.Key) + local Node = Tree:AddNode(Info.Key, Icon) + local Value = Info.Value + local Type = type(Value) + local Size = 3 + + local TypeNode = Node:AddNode("Type: " .. Type, "icon16/cog.png") + TypeNode.RootNode = Node + + if Type ~= "table" then + local Base = Node:AddNode("Value: " .. tostring(Value), "icon16/information.png") + Base.RootNode = Node + else + local Base = Node:AddNode("Value:", "icon16/information.png") + Base.RootNode = Node + + for K, V in pairs(Value) do + local Extra = Base:AddNode(tostring(K) .. " = " .. tostring(V), "icon16/bullet_black.png") + Extra.RootNode = Node + + Size = Size + 1 + end + + Base:ExpandTo(true) + + -- We don't want this node to be collapsible + function Base:SetExpanded() + end + end + + Node:ExpandTo(true) + + -- We don't want this node to be collapsible + function Node:SetExpanded() + end + + Height = Height + Size + end + + Tree:SetHeight(Tree:GetLineHeight() * Height) +end + +local function UpdateComboBox(ComboBox) + ComboBox:Clear() + + for Class, Data in pairs(CopiedData) do + ComboBox:AddChoice(Class, Data, Class == Selected) + end +end + +function ACF.CreateCopyMenu(Panel) + local Menu = ACF.CopyMenu + + if not IsValid(Menu) then + Menu = vgui.Create("ACF_Panel") + Menu.Panel = Panel + + Panel:AddItem(Menu) + + ACF.CopyMenu = Menu + else + Menu:ClearAllTemporal() + Menu:ClearAll() + end + + local Reload = Menu:AddButton("Reload Menu") + Reload:SetTooltip("You can also type 'acf_reload_copy_menu' in console.") + function Reload:DoClickInternal() + RunConsoleCommand("acf_reload_copy_menu") + end + + ACF.SetToolMode("acfcopy", "Main", "CopyPaste") + + if not Selected then + return Menu:AddLabel("Right click an ACF entity to copy its data.") + end + + local ClassList = Menu:AddComboBox() + local TreeList = Menu:AddPanel("DTree") + + function ClassList:OnSelect(_, Class, Data) + Selected = Class + + ACF.SetClientData("CopyClass", Class) + + PopulateTree(TreeList, Data) + end + + function TreeList:OnNodeSelected(Node) + if Node.RootNode then + return self:SetSelectedItem(Node.RootNode) + end + + local Key = Node:GetText() + local Data = Disabled[Selected] + + -- A ternary won't work here + if Data[Key] then + Data[Key] = nil + else + Data[Key] = true + end + + net.Start("ACF_SendDisabledData") + net.WriteString(Selected) + net.WriteString(Key) + net.WriteBool(Data[Key] or false) + net.SendToServer() + + Node:SetIcon(GetIcon(Selected, Key)) + end + + UpdateComboBox(ClassList) +end + diff --git a/lua/acf/client/damage.lua b/lua/acf/client/damage.lua new file mode 100644 index 000000000..6a28e1f6b --- /dev/null +++ b/lua/acf/client/damage.lua @@ -0,0 +1,237 @@ +local ACF = ACF +local Damaged = { + CreateMaterial("ACF_Damaged1", "VertexLitGeneric", { + ["$basetexture"] = "damaged/damaged1" + }), + CreateMaterial("ACF_Damaged2", "VertexLitGeneric", { + ["$basetexture"] = "damaged/damaged2" + }), + CreateMaterial("ACF_Damaged3", "VertexLitGeneric", { + ["$basetexture"] = "damaged/damaged3" + }) +} + +hook.Add("PostDrawOpaqueRenderables", "ACF_RenderDamage", function() + if not ACF_HealthRenderList then return end + cam.Start3D(EyePos(), EyeAngles()) + + for k, ent in pairs(ACF_HealthRenderList) do + if IsValid(ent) then + render.ModelMaterialOverride(ent.ACF_Material) + render.SetBlend(math.Clamp(1 - ent.ACF_HealthPercent, 0, 0.8)) + ent:DrawModel() + elseif ACF_HealthRenderList then + table.remove(ACF_HealthRenderList, k) + end + end + + render.ModelMaterialOverride() + render.SetBlend(1) + cam.End3D() +end) + +net.Receive("ACF_RenderDamage", function() + local Table = net.ReadTable() + + for _, v in ipairs(Table) do + local ent, Health, MaxHealth = ents.GetByIndex(v.ID), v.Health, v.MaxHealth + if not IsValid(ent) then return end + + if Health ~= MaxHealth then + ent.ACF_Health = Health + ent.ACF_MaxHealth = MaxHealth + ent.ACF_HealthPercent = Health / MaxHealth + + if ent.ACF_HealthPercent > 0.7 then + ent.ACF_Material = Damaged[1] + elseif ent.ACF_HealthPercent > 0.3 then + ent.ACF_Material = Damaged[2] + elseif ent.ACF_HealthPercent <= 0.3 then + ent.ACF_Material = Damaged[3] + end + + ACF_HealthRenderList = ACF_HealthRenderList or {} + ACF_HealthRenderList[ent:EntIndex()] = ent + else + if ACF_HealthRenderList then + if #ACF_HealthRenderList <= 1 then + ACF_HealthRenderList = nil + else + table.remove(ACF_HealthRenderList, ent:EntIndex()) + end + + if ent.ACF then + ent.ACF.Health = nil + ent.ACF.MaxHealth = nil + end + end + end + end +end) + +do -- Debris Effects ------------------------ + local AllowDebris = GetConVar("acf_debris") + local CollideAll = GetConVar("acf_debris_collision") + local DebrisLife = GetConVar("acf_debris_lifetime") + local GibMult = GetConVar("acf_debris_gibmultiplier") + local GibLife = GetConVar("acf_debris_giblifetime") + local GibModel = "models/gibs/metal_gib%s.mdl" + + local function Particle(Entity, Effect) + return CreateParticleSystem(Entity, Effect, PATTACH_ABSORIGIN_FOLLOW) + end + + local function FadeAway(Entity) + if not IsValid(Entity) then return end + + local Smoke = Entity.SmokeParticle + local Ember = Entity.EmberParticle + + Entity:SetRenderMode(RENDERMODE_TRANSCOLOR) + Entity:SetRenderFX(kRenderFxFadeSlow) -- NOTE: Not synced to CurTime() + + if Smoke then Smoke:StopEmission() end + if Ember then Ember:StopEmission() end + + timer.Simple(5, function() + Entity:StopAndDestroyParticles() + Entity:Remove() + end) + end + + local function Ignite(Entity, Lifetime, IsGib) + if IsGib then + Particle(Entity, "burning_gib_01") + + timer.Simple(Lifetime * 0.2, function() + if not IsValid(Entity) then return end + + Entity:StopParticlesNamed("burning_gib_01") + end) + else + Entity.SmokeParticle = Particle(Entity, "smoke_small_01b") + + Particle(Entity, "env_fire_small_smoke") + + timer.Simple(Lifetime * 0.4, function() + if not IsValid(Entity) then return end + + Entity:StopParticlesNamed("env_fire_small_smoke") + end) + end + end + + local function CreateDebris(Data) + local Debris = ents.CreateClientProp(Data.Model) + + if not IsValid(Debris) then return end + + local Lifetime = DebrisLife:GetFloat() * math.Rand(0.5, 1) + + Debris:SetPos(Data.Position) + Debris:SetAngles(Data.Angles) + Debris:SetColor(Data.Color) + Debris:SetMaterial(Data.Material) + + if not CollideAll:GetBool() then + Debris:SetCollisionGroup(COLLISION_GROUP_WORLD) + end + + Debris:Spawn() + + Debris.EmberParticle = Particle(Debris, "embers_medium_01") + + if Data.Ignite and math.Rand(0, 0.5) < ACF.DebrisIgniteChance then + Ignite(Debris, Lifetime) + else + Debris.SmokeParticle = Particle(Debris, "smoke_exhaust_01a") + end + + local PhysObj = Debris:GetPhysicsObject() + + if IsValid(PhysObj) then + PhysObj:ApplyForceOffset(Data.Normal * Data.Power, Data.Position + VectorRand() * 20) + end + + timer.Simple(Lifetime, function() + FadeAway(Debris) + end) + + return Debris + end + + local function CreateGib(Data, Min, Max) + local Gib = ents.CreateClientProp(GibModel:format(math.random(1, 5))) + + if not IsValid(Gib) then return end + + local Lifetime = GibLife:GetFloat() * math.Rand(0.5, 1) + local Offset = ACF.RandomVector(Min, Max) + + Offset:Rotate(Data.Angles) + + Gib:SetPos(Data.Position + Offset) + Gib:SetAngles(AngleRand(-180, 180)) + Gib:SetModelScale(math.Rand(0.5, 2)) + Gib:SetMaterial(Data.Material) + Gib:SetColor(Data.Color) + Gib:Spawn() + + Gib.SmokeParticle = Particle(Gib, "smoke_gib_01") + + if math.random() < ACF.DebrisIgniteChance then + Ignite(Gib, Lifetime, true) + end + + local PhysObj = Gib:GetPhysicsObject() + + if IsValid(PhysObj) then + PhysObj:ApplyForceOffset(Data.Normal * Data.Power, Gib:GetPos() + VectorRand() * 20) + end + + timer.Simple(Lifetime, function() + FadeAway(Gib) + end) + + return true + end + + net.Receive("ACF_Debris", function() + local Data = util.JSONToTable(net.ReadString()) + + if not AllowDebris:GetBool() then return end + + local Debris = CreateDebris(Data) + + if IsValid(Debris) then + local Multiplier = GibMult:GetFloat() + local Radius = Debris:BoundingRadius() + local Min = Debris:OBBMins() + local Max = Debris:OBBMaxs() + + if Data.CanGib and Multiplier > 0 then + local GibCount = math.Clamp(Radius * 0.1, 1, math.max(10 * Multiplier, 1)) + + for _ = 1, GibCount do + if not CreateGib(Data, Min, Max) then + break + end + end + end + end + + local Effect = EffectData() + Effect:SetOrigin(Data.Position) -- TODO: Change this to the hit vector, but we need to redefine HitVec as HitNorm + Effect:SetScale(20) + util.Effect("cball_explode", Effect) + end) + + game.AddParticles("particles/fire_01.pcf") + + PrecacheParticleSystem("burning_gib_01") + PrecacheParticleSystem("env_fire_small_smoke") + PrecacheParticleSystem("smoke_gib_01") + PrecacheParticleSystem("smoke_exhaust_01a") + PrecacheParticleSystem("smoke_small_01b") + PrecacheParticleSystem("embers_medium_01") +end ----------------------------------------- \ No newline at end of file diff --git a/lua/acf/client/menu_items/components_menu.lua b/lua/acf/client/menu_items/components_menu.lua new file mode 100644 index 000000000..e18f4500e --- /dev/null +++ b/lua/acf/client/menu_items/components_menu.lua @@ -0,0 +1,72 @@ +local ACF = ACF +local Components = ACF.Classes.Components + +local function CreateMenu(Menu) + ACF.SetClientData("PrimaryClass", "N/A") + ACF.SetClientData("SecondaryClass", "N/A") + + ACF.SetToolMode("acf_menu", "Spawner", "Component") + + if not next(Components) then + Menu:AddTitle("No Components Registered") + Menu:AddLabel("No components have been registered. If this is incorrect, check your console for errors and contact the server owner.") + return + end + + Menu:AddTitle("Component Settings") + + local ComponentClass = Menu:AddComboBox() + local ComponentList = Menu:AddComboBox() + + local Base = Menu:AddCollapsible("Component Information") + local ComponentName = Base:AddTitle() + local ComponentDesc = Base:AddLabel() + local ComponentPreview = Base:AddModelPreview() + + function ComponentClass:OnSelect(Index, _, Data) + if self.Selected == Data then return end + + self.ListData.Index = Index + self.Selected = Data + + ACF.SetClientData("ComponentClass", Data.ID) + + ACF.LoadSortedList(ComponentList, Data.Items, "ID") + end + + function ComponentList:OnSelect(Index, _, Data) + if self.Selected == Data then return end + + self.ListData.Index = Index + self.Selected = Data + + local Preview = Data.Preview + local ClassData = ComponentClass.Selected + + ACF.SetClientData("Component", Data.ID) + + ComponentName:SetText(Data.Name) + ComponentDesc:SetText(Data.Description or "No description provided.") + + ComponentPreview:SetModel(Data.Model) + ComponentPreview:SetCamPos(Preview and Preview.Offset or Vector(45, 60, 45)) + ComponentPreview:SetLookAt(Preview and Preview.Position or Vector()) + ComponentPreview:SetHeight(Preview and Preview.Height or 80) + ComponentPreview:SetFOV(Preview and Preview.FOV or 75) + + Menu:ClearTemporal(Base) + Menu:StartTemporal(Base) + + local CustomMenu = Data.CreateMenu or ClassData.CreateMenu + + if CustomMenu then + CustomMenu(Data, Base) + end + + Menu:EndTemporal(Base) + end + + ACF.LoadSortedList(ComponentClass, Components, "ID") +end + +ACF.AddMenuItem(501, "Entities", "Components", "drive", CreateMenu) diff --git a/lua/acf/client/menu_items/contact.lua b/lua/acf/client/menu_items/contact.lua new file mode 100644 index 000000000..de0072d42 --- /dev/null +++ b/lua/acf/client/menu_items/contact.lua @@ -0,0 +1,55 @@ +local function CreateMenu(Menu) + Menu:AddTitle("Your feedback is important.") + Menu:AddLabel("For this reason, we've setup a variety of methods to generate discussion among the members of the ACF community.") + + do -- Official Discord Server + local Base = Menu:AddCollapsible("Official Discord Server", false) + + Base:AddLabel("We have a Discord server! You can discuss the addon's development or just hang around on one of the off-topic channels.") + + local Link = Base:AddButton("Join the Discord Server") + + function Link:DoClickInternal() + gui.OpenURL("https://discordapp.com/invite/shk5sc5") + end + end + + do -- Official Steam Group + local Base = Menu:AddCollapsible("Official Steam Group", false) + + Base:AddLabel("There's also a Steam group, you'll find all important announcements about the addon's development there.") + + local Link = Base:AddButton("Join the Steam Group") + + function Link:DoClickInternal() + gui.OpenURL("https://steamcommunity.com/groups/officialacf") + end + end + + do -- "Github Issues & Suggestions" + local Base = Menu:AddCollapsible("Github Issues & Suggestions", false) + + Base:AddLabel("The recommended method for bug reporting and suggestion posting is the Issues tab on the Github repository.") + Base:AddLabel("By using this method, you'll be able to easily track your issue and the discussion related to it.") + + local Link = Base:AddButton("Report an Issue") + + function Link:DoClickInternal() + gui.OpenURL("https://github.com/Stooberton/ACF-3/issues/new/choose") + end + end + + do -- How to Contribute + local Base = Menu:AddCollapsible("How to Contribute", false) + + Base:AddLabel("To make it easier for first time contributors, we've left a guide about how to contribute to the addon.") + + local Link = Base:AddButton("Contributing to ACF") + + function Link:DoClickInternal() + gui.OpenURL("https://github.com/Stooberton/ACF-3/blob/master/CONTRIBUTING.md") + end + end +end + +ACF.AddMenuItem(301, "About the Addon", "Contact Us", "feed", CreateMenu) diff --git a/lua/acf/client/menu_items/engines_menu.lua b/lua/acf/client/menu_items/engines_menu.lua new file mode 100644 index 000000000..7a8b3fe5e --- /dev/null +++ b/lua/acf/client/menu_items/engines_menu.lua @@ -0,0 +1,219 @@ +local ACF = ACF +local EngineTypes = ACF.Classes.EngineTypes +local FuelTypes = ACF.Classes.FuelTypes +local FuelTanks = ACF.Classes.FuelTanks +local Engines = ACF.Classes.Engines +local RPMText = [[ + Idle RPM : %s RPM + Powerband : %s-%s RPM + Redline : %s RPM + Mass : %s + + This entity can be fully parented. + %s + %s]] +local PowerText = [[ + Peak Torque : %s n/m - %s ft-lb + Peak Power : %s kW - %s HP @ %s RPM]] +local ConsumptionText = [[ + %s Consumption : + %s L/min - %s gal/min @ %s RPM]] + +-- Fuel consumption is increased on competitive servers +local function GetEfficiencyMult() + return ACF.Gamemode == 3 and ACF.CompFuelRate or 1 +end + +local function UpdateEngineStats(Label, Data) + local RPM = Data.RPM + local PeakkW = Data.Torque * RPM.PeakMax / 9548.8 + local PeakkWRPM = RPM.PeakMax + local MinPower = RPM.PeakMin + local MaxPower = RPM.PeakMax + local Mass = ACF.GetProperMass(Data.Mass) + local Torque = math.floor(Data.Torque) + local TorqueFeet = math.floor(Data.Torque * 0.73) + local Type = EngineTypes[Data.Type] + local Efficiency = Type.Efficiency * GetEfficiencyMult() + local FuelList = "" + + -- Electric motors and turbines get peak power in middle of rpm range + if Data.IsElectric then + PeakkW = Data.Torque * (1 + RPM.PeakMax / RPM.Limit) * RPM.Limit / (4 * 9548.8) + PeakkWRPM = math.floor(RPM.Limit * 0.5) + MinPower = RPM.Idle + MaxPower = PeakkWRPM + end + + for K in pairs(Data.Fuel) do + if not FuelTypes[K] then continue end + + local Fuel = FuelTypes[K] + local AddText = "" + + if Fuel.ConsumptionText then + AddText = Fuel.ConsumptionText(PeakkW, PeakkWRPM, Efficiency, Type, Fuel) + else + local Rate = ACF.FuelRate * Efficiency * PeakkW / (60 * Fuel.Density) + + AddText = ConsumptionText:format(Fuel.Name, math.Round(Rate, 2), math.Round(Rate * 0.264, 2), PeakkWRPM) + end + + FuelList = FuelList .. "\n" .. AddText .. "\n" + + Data.Fuel[K] = Fuel -- TODO: Replace once engines use the proper class functions + end + + local Power = PowerText:format(Torque, TorqueFeet, math.floor(PeakkW), math.floor(PeakkW * 1.34), PeakkWRPM) + + Label:SetText(RPMText:format(RPM.Idle, MinPower, MaxPower, RPM.Limit, Mass, FuelList, Power)) +end + +local function CreateMenu(Menu) + Menu:AddTitle("Engine Settings") + + local EngineClass = Menu:AddComboBox() + local EngineList = Menu:AddComboBox() + + local EngineBase = Menu:AddCollapsible("Engine Information") + local EngineName = EngineBase:AddTitle() + local EngineDesc = EngineBase:AddLabel() + local EnginePreview = EngineBase:AddModelPreview() + local EngineStats = EngineBase:AddLabel() + + Menu:AddTitle("Fuel Tank Settings") + + local FuelClass = Menu:AddComboBox() + local FuelList = Menu:AddComboBox() + local FuelType = Menu:AddComboBox() + local FuelBase = Menu:AddCollapsible("Fuel Tank Information") + local FuelDesc = FuelBase:AddLabel() + local FuelPreview = FuelBase:AddModelPreview() + local FuelInfo = FuelBase:AddLabel() + + ACF.SetClientData("PrimaryClass", "acf_engine") + ACF.SetClientData("SecondaryClass", "acf_fueltank") + + ACF.SetToolMode("acf_menu", "Spawner", "Engine") + + function EngineClass:OnSelect(Index, _, Data) + if self.Selected == Data then return end + + self.ListData.Index = Index + self.Selected = Data + + ACF.SetClientData("EngineClass", Data.ID) + + ACF.LoadSortedList(EngineList, Data.Items, "Mass") + end + + function EngineList:OnSelect(Index, _, Data) + if self.Selected == Data then return end + + self.ListData.Index = Index + self.Selected = Data + + local Preview = Data.Preview + local ClassData = EngineClass.Selected + local ClassDesc = ClassData.Description + + ACF.SetClientData("Engine", Data.ID) + + EngineName:SetText(Data.Name) + EngineDesc:SetText((ClassDesc and (ClassDesc .. "\n\n") or "") .. Data.Description) + + EnginePreview:SetModel(Data.Model) + EnginePreview:SetCamPos(Preview and Preview.Offset or Vector(45, 60, 45)) + EnginePreview:SetLookAt(Preview and Preview.Position or Vector()) + EnginePreview:SetHeight(Preview and Preview.Height or 80) + EnginePreview:SetFOV(Preview and Preview.FOV or 75) + + UpdateEngineStats(EngineStats, Data) + + ACF.LoadSortedList(FuelType, Data.Fuel, "ID") + end + + function FuelClass:OnSelect(Index, _, Data) + if self.Selected == Data then return end + + self.ListData.Index = Index + self.Selected = Data + + ACF.LoadSortedList(FuelList, Data.Items, "ID") + end + + function FuelList:OnSelect(Index, _, Data) + if self.Selected == Data then return end + + self.ListData.Index = Index + self.Selected = Data + + local Preview = Data.Preview + local ClassData = FuelClass.Selected + local ClassDesc = ClassData.Description + + self.Description = (ClassDesc and (ClassDesc .. "\n\n") or "") .. Data.Description + + ACF.SetClientData("FuelTank", Data.ID) + + FuelPreview:SetModel(Data.Model) + FuelPreview:SetCamPos(Preview and Preview.Offset or Vector(45, 60, 45)) + FuelPreview:SetLookAt(Preview and Preview.Position or Vector()) + FuelPreview:SetHeight(Preview and Preview.Height or 80) + FuelPreview:SetFOV(Preview and Preview.FOV or 75) + + FuelType:UpdateFuelText() + end + + function FuelType:OnSelect(Index, _, Data) + if self.Selected == Data then return end + + self.ListData.Index = Index + self.Selected = Data + + ACF.SetClientData("FuelType", Data.ID) + + self:UpdateFuelText() + end + + function FuelType:UpdateFuelText() + if not self.Selected then return end + if not FuelList.Selected then return end + + local FuelTank = FuelList.Selected + local TextFunc = self.Selected.FuelTankText + local FuelText = "" + + local Wall = 0.03937 --wall thickness in inches (1mm) + local Volume = FuelTank.Volume - (FuelTank.SurfaceArea * Wall) -- total volume of tank (cu in), reduced by wall thickness + local Capacity = Volume * ACF.CuIToLiter * ACF.TankVolumeMul * 0.4774 --internal volume available for fuel in liters, with magic realism number + local EmptyMass = FuelTank.SurfaceArea * Wall * 16.387 * 0.0079 -- total wall volume * cu in to cc * density of steel (kg/cc) + local Mass = EmptyMass + Capacity * self.Selected.Density -- weight of tank + weight of fuel + + if TextFunc then + FuelText = FuelText .. TextFunc(Capacity, Mass, EmptyMass) + else + local Text = "Capacity : %s L - %s gal\nFull Mass : %s\nEmpty Mass : %s\n\nThis entity can be fully parented." + local Liters = math.Round(Capacity, 2) + local Gallons = math.Round(Capacity * 0.264172, 2) + + FuelText = FuelText .. Text:format(Liters, Gallons, ACF.GetProperMass(Mass), ACF.GetProperMass(EmptyMass)) + end + + if not FuelTank.IsExplosive then + FuelText = FuelText .. "\n\nThis fuel tank won't explode if damaged." + end + + if FuelTank.Unlinkable then + FuelText = FuelText .. "\n\nThis fuel tank cannot be linked to other ACF entities." + end + + FuelDesc:SetText(FuelList.Description) + FuelInfo:SetText(FuelText) + end + + ACF.LoadSortedList(EngineClass, Engines, "ID") + ACF.LoadSortedList(FuelClass, FuelTanks, "ID") +end + +ACF.AddMenuItem(201, "Entities", "Engines", "car", CreateMenu) diff --git a/lua/acf/client/menu_items/fun_menu.lua b/lua/acf/client/menu_items/fun_menu.lua new file mode 100644 index 000000000..ccdb4d597 --- /dev/null +++ b/lua/acf/client/menu_items/fun_menu.lua @@ -0,0 +1,127 @@ +local ACF = ACF + +do -- Piledrivers menu + local Info = "Mass : %s kg\nRate of Fire : %s rpm\nMax Charges : %s\nRecharge Rate : %s charges/s" + local Stats = "Penetration : %s mm RHA\nSpike Velocity : %s m/s\nSpike Length : %s cm\nSpike Mass : %s" + local Piledrivers = ACF.Classes.Piledrivers + local AmmoTypes = ACF.Classes.AmmoTypes + local Ammo, BulletData + + local function CreateMenu(Menu) + Menu:AddTitle("Piledriver Settings") + + local ClassList = Menu:AddComboBox() + local Caliber = Menu:AddSlider("Caliber", 0, 1, 2) + + local ClassBase = Menu:AddCollapsible("Piledriver Information") + local ClassName = ClassBase:AddTitle() + local ClassDesc = ClassBase:AddLabel() + local ClassPreview = ClassBase:AddModelPreview() + local ClassInfo = ClassBase:AddLabel() + local ClassStats = ClassBase:AddLabel() + + ClassBase:AddLabel("This entity can be fully parented.") + + ACF.SetClientData("PrimaryClass", "acf_piledriver") + ACF.SetClientData("SecondaryClass", "N/A") + ACF.SetClientData("Destiny", "Piledrivers") + ACF.SetClientData("AmmoType", "HP") + ACF.SetClientData("Propellant", 0) + ACF.SetClientData("Tracer", false) + + ACF.SetToolMode("acf_menu", "Spawner", "Weapon") + + function ClassList:OnSelect(Index, _, Data) + if self.Selected == Data then return end + + self.ListData.Index = Index + self.Selected = Data + + local Preview = Data.Preview + local Bounds = Data.Caliber + local Min, Max = Bounds.Min, Bounds.Max + local Current = math.Clamp(ACF.GetClientNumber("Caliber", Min), Min, Max) + + Ammo = AmmoTypes.HP() + + Caliber:SetMinMax(Min, Max) + + ClassDesc:SetText(Data.Description) + + ClassPreview:SetModel(Data.Model) + ClassPreview:SetCamPos(Preview and Preview.Offset or Vector(45, 60, 45)) + ClassPreview:SetLookAt(Preview and Preview.Position or Vector()) + ClassPreview:SetHeight(Preview and Preview.Height or 80) + ClassPreview:SetFOV(Preview and Preview.FOV or 75) + + ACF.SetClientData("Weapon", Data.ID) + ACF.SetClientData("Caliber", Current, true) + end + + Caliber:SetClientData("Caliber", "OnValueChanged") + Caliber:DefineSetter(function(Panel, _, _, Value) + if not ClassList.Selected then return Value end + + local Class = ClassList.Selected + local Scale = Value / Class.Caliber.Base + local Length = Class.Round.MaxLength * Scale + + Ammo.SpikeLength = Length + + ACF.SetClientData("Projectile", Length) + + BulletData = Ammo:ClientConvert(ACF.GetAllClientData()) + + Panel:SetValue(Value) + + return Value + end) + + ClassName:TrackClientData("Weapon", "SetText") + ClassName:TrackClientData("Caliber") + ClassName:DefineSetter(function() + local Current = math.Round(Caliber:GetValue(), 2) + local Name = ClassList.Selected.Name + + return Current .. "mm " .. Name + end) + + ClassInfo:TrackClientData("Weapon", "SetText") + ClassInfo:TrackClientData("Caliber") + ClassInfo:DefineSetter(function() + if not BulletData then return "" end + + local Class = ClassList.Selected + local Current = math.Round(Caliber:GetValue(), 2) + local Scale = Current / Class.Caliber.Base + local Mass = Class.Mass * Scale + local FireRate = Class.Cyclic + local Total = Class.MagSize + local Charge = Class.ChargeRate + + return Info:format(Mass, FireRate, Total, Charge) + end) + + ClassStats:TrackClientData("Weapon", "SetText") + ClassStats:TrackClientData("Caliber") + ClassStats:DefineSetter(function() + if not BulletData then return "" end + + local MaxPen = math.Round(BulletData.MaxPen, 2) + local MuzzleVel = math.Round(BulletData.MuzzleVel, 2) + local Length = BulletData.ProjLength + local Mass = ACF.GetProperMass(BulletData.ProjMass) + + return Stats:format(MaxPen, MuzzleVel, Length, Mass) + end) + + ACF.LoadSortedList(ClassList, Piledrivers, "Name") + end + + ACF.AddMenuItem(1, "Fun Stuff", "Piledrivers", "pencil", CreateMenu) +end + +hook.Add("ACF_AllowMenuOption", "Allow Fun Menu", function(_, Name) + if Name ~= "Fun Stuff" then return end + if not ACF.GetServerBool("ShowFunMenu") then return false end +end) diff --git a/lua/acf/client/menu_items/gearboxes_menu.lua b/lua/acf/client/menu_items/gearboxes_menu.lua new file mode 100644 index 000000000..ee3927fe5 --- /dev/null +++ b/lua/acf/client/menu_items/gearboxes_menu.lua @@ -0,0 +1,443 @@ +local ACF = ACF +local Gearboxes = ACF.Classes.Gearboxes + +local function CreateMenu(Menu) + Menu:AddTitle("Gearbox Settings") + + local GearboxClass = Menu:AddComboBox() + local GearboxList = Menu:AddComboBox() + + local Base = Menu:AddCollapsible("Gearbox Information") + local GearboxName = Base:AddTitle() + local GearboxDesc = Base:AddLabel() + local GearboxPreview = Base:AddModelPreview() + + ACF.SetClientData("PrimaryClass", "acf_gearbox") + ACF.SetClientData("SecondaryClass", "N/A") + + ACF.SetToolMode("acf_menu", "Spawner", "Gearbox") + + function GearboxClass:OnSelect(Index, _, Data) + if self.Selected == Data then return end + + self.ListData.Index = Index + self.Selected = Data + + ACF.SetClientData("GearboxClass", Data.ID) + + ACF.LoadSortedList(GearboxList, Data.Items, "ID") + end + + function GearboxList:OnSelect(Index, _, Data) + if self.Selected == Data then return end + + self.ListData.Index = Index + self.Selected = Data + + local Preview = Data.Preview + local ClassData = GearboxClass.Selected + + ACF.SetClientData("Gearbox", Data.ID) + + GearboxName:SetText(Data.Name) + GearboxDesc:SetText(Data.Description) + + GearboxPreview:SetModel(Data.Model) + GearboxPreview:SetCamPos(Preview and Preview.Offset or Vector(45, 60, 45)) + GearboxPreview:SetLookAt(Preview and Preview.Position or Vector()) + GearboxPreview:SetHeight(Preview and Preview.Height or 80) + GearboxPreview:SetFOV(Preview and Preview.FOV or 75) + + Menu:ClearTemporal(Base) + Menu:StartTemporal(Base) + + if ClassData.CreateMenu then + ClassData:CreateMenu(Data, Menu, Base) + end + + Menu:EndTemporal(Base) + end + + ACF.LoadSortedList(GearboxClass, Gearboxes, "ID") +end + +ACF.AddMenuItem(301, "Entities", "Gearboxes", "cog", CreateMenu) + +do -- Default Menus + local Values = {} + + do -- Manual Gearbox Menu + function ACF.ManualGearboxMenu(Class, Data, Menu, Base) + local Text = "Mass : %s\nTorque Rating : %s n/m - %s fl-lb\n\nThis entity can be fully parented." + local Mass = ACF.GetProperMass(Data.Mass) + local Gears = Class.Gears + local Torque = math.floor(Data.MaxTorque * 0.73) + + Base:AddLabel(Text:format(Mass, Data.MaxTorque, Torque)) + + if Data.DualClutch then + Base:AddLabel("The dual clutch allows you to apply power and brake each side independently.") + end + + ----------------------------------- + + local GearBase = Menu:AddCollapsible("Gear Settings") + + Values[Class.ID] = Values[Class.ID] or {} + + local ValuesData = Values[Class.ID] + + for I = 1, Gears.Max do + local Variable = "Gear" .. I + local Default = ValuesData[Variable] + + if not Default then + Default = math.Clamp(I * 0.1, -1, 1) + + ValuesData[Variable] = Default + end + + ACF.SetClientData(Variable, Default) + + local Control = GearBase:AddSlider("Gear " .. I, -1, 1, 2) + Control:SetClientData(Variable, "OnValueChanged") + Control:DefineSetter(function(Panel, _, _, Value) + Value = math.Round(Value, 2) + + ValuesData[Variable] = Value + + Panel:SetValue(Value) + + return Value + end) + end + + if not ValuesData.FinalDrive then + ValuesData.FinalDrive = 1 + end + + ACF.SetClientData("FinalDrive", ValuesData.FinalDrive) + + local FinalDrive = GearBase:AddSlider("Final Drive", -1, 1, 2) + FinalDrive:SetClientData("FinalDrive", "OnValueChanged") + FinalDrive:DefineSetter(function(Panel, _, _, Value) + Value = math.Round(Value, 2) + + ValuesData.FinalDrive = Value + + Panel:SetValue(Value) + + return Value + end) + end + end + + do -- CVT Gearbox Menu + local CVTData = { + { + Name = "Gear 2", + Variable = "Gear2", + Min = -1, + Max = 1, + Decimals = 2, + Default = -0.1, + }, + { + Name = "Min Target RPM", + Variable = "MinRPM", + Min = 1, + Max = 9900, + Decimals = 0, + Default = 3000, + }, + { + Name = "Max Target RPM", + Variable = "MaxRPM", + Min = 101, + Max = 10000, + Decimals = 0, + Default = 5000, + }, + { + Name = "Final Drive", + Variable = "FinalDrive", + Min = -1, + Max = 1, + Decimals = 2, + Default = 1, + }, + } + + function ACF.CVTGearboxMenu(Class, Data, Menu, Base) + local Text = "Mass : %s\nTorque Rating : %s n/m - %s fl-lb\n\nThis entity can be fully parented." + local Mass = ACF.GetProperMass(Data.Mass) + local Torque = math.floor(Data.MaxTorque * 0.73) + + Base:AddLabel(Text:format(Mass, Data.MaxTorque, Torque)) + + if Data.DualClutch then + Base:AddLabel("The dual clutch allows you to apply power and brake each side independently.") + end + + ----------------------------------- + + local GearBase = Menu:AddCollapsible("Gear Settings") + + Values[Class.ID] = Values[Class.ID] or {} + + local ValuesData = Values[Class.ID] + + ACF.SetClientData("Gear1", 0.01) + + for _, GearData in ipairs(CVTData) do + local Variable = GearData.Variable + local Default = ValuesData[Variable] + + if not Default then + Default = GearData.Default + + ValuesData[Variable] = Default + end + + ACF.SetClientData(Variable, Default) + + local Control = GearBase:AddSlider(GearData.Name, GearData.Min, GearData.Max, GearData.Decimals) + Control:SetClientData(Variable, "OnValueChanged") + Control:DefineSetter(function(Panel, _, _, Value) + Value = math.Round(Value, GearData.Decimals) + + ValuesData[Variable] = Value + + Panel:SetValue(Value) + + return Value + end) + end + end + end + + do -- Automatic Gearbox Menu + local UnitMult = 10.936 -- km/h is set by default + local AutoData = { + { + Name = "Reverse Gear", + Variable = "Reverse", + Min = -1, + Max = 1, + Decimals = 2, + Default = -0.1, + }, + { + Name = "Final Drive", + Variable = "FinalDrive", + Min = -1, + Max = 1, + Decimals = 2, + Default = 1, + }, + } + + local GenData = { + { + Name = "Upshift RPM", + Variable = "UpshiftRPM", + Tooltip = "Target engine RPM to upshift at.", + Min = 0, + Max = 10000, + Decimals = 0, + Default = 5000, + }, + { + Name = "Total Ratio", + Variable = "TotalRatio", + Tooltip = "Total ratio is the ratio of all gearboxes (exluding this one) multiplied together.\nFor example, if you use engine to automatic to diffs to wheels, your total ratio would be (diff gear ratio * diff final ratio).", + Min = 0, + Max = 1, + Decimals = 2, + Default = 0.1, + }, + { + Name = "Wheel Diameter", + Variable = "WheelDiameter", + Tooltip = "If you use default spherical settings, add 0.5 to your wheel diameter.\nFor treaded vehicles, use the diameter of road wheels, not drive wheels.", + Min = 0, + Max = 1000, + Decimals = 2, + Default = 30, + }, + } + + function ACF.AutomaticGearboxMenu(Class, Data, Menu, Base) + local Text = "Mass : %s\nTorque Rating : %s n/m - %s fl-lb\n\nThis entity can be fully parented." + local Mass = ACF.GetProperMass(Data.Mass) + local Gears = Class.Gears + local Torque = math.floor(Data.MaxTorque * 0.73) + + Base:AddLabel(Text:format(Mass, Data.MaxTorque, Torque)) + + if Data.DualClutch then + Base:AddLabel("The dual clutch allows you to apply power and brake each side independently.") + end + + ----------------------------------- + + local GearBase = Menu:AddCollapsible("Gear Settings") + + Values[Class.ID] = Values[Class.ID] or {} + + local ValuesData = Values[Class.ID] + + GearBase:AddLabel("Upshift Speed Unit :") + + ACF.SetClientData("ShiftUnit", UnitMult) + + local Unit = GearBase:AddComboBox() + Unit:AddChoice("KPH", 10.936) + Unit:AddChoice("MPH", 17.6) + Unit:AddChoice("GMU", 1) + + function Unit:OnSelect(_, _, Mult) + if UnitMult == Mult then return end + + local Delta = UnitMult / Mult + + for I = 1, Gears.Max do + local Var = "Shift" .. I + local Old = ACF.GetClientNumber(Var) + + ACF.SetClientData(Var, Old * Delta) + end + + ACF.SetClientData("ShiftUnit", Mult) + + UnitMult = Mult + end + + for I = 1, Gears.Max do + local GearVar = "Gear" .. I + local DefGear = ValuesData[GearVar] + + if not DefGear then + DefGear = math.Clamp(I * 0.1, -1, 1) + + ValuesData[GearVar] = DefGear + end + + ACF.SetClientData(GearVar, DefGear) + + local Gear = GearBase:AddSlider("Gear " .. I, -1, 1, 2) + Gear:SetClientData(GearVar, "OnValueChanged") + Gear:DefineSetter(function(Panel, _, _, Value) + Value = math.Round(Value, 2) + + ValuesData[GearVar] = Value + + Panel:SetValue(Value) + + return Value + end) + + local ShiftVar = "Shift" .. I + local DefShift = ValuesData[ShiftVar] + + if not DefShift then + DefShift = I * 10 + + ValuesData[ShiftVar] = DefShift + end + + ACF.SetClientData(ShiftVar, DefShift) + + local Shift = GearBase:AddNumberWang("Gear " .. I .. " Upshift Speed", 0, 9999, 2) + Shift:HideWang() + Shift:SetClientData(ShiftVar, "OnValueChanged") + Shift:DefineSetter(function(Panel, _, _, Value) + Value = math.Round(Value, 2) + + ValuesData[ShiftVar] = Value + + Panel:SetValue(Value) + + return Value + end) + end + + for _, GearData in ipairs(AutoData) do + local Variable = GearData.Variable + local Default = ValuesData[Variable] + + if not Default then + Default = GearData.Default + + ValuesData[Variable] = Default + end + + ACF.SetClientData(Variable, Default) + + local Control = GearBase:AddSlider(GearData.Name, GearData.Min, GearData.Max, GearData.Decimals) + Control:SetClientData(Variable, "OnValueChanged") + Control:DefineSetter(function(Panel, _, _, Value) + Value = math.Round(Value, GearData.Decimals) + + ValuesData[Variable] = Value + + Panel:SetValue(Value) + + return Value + end) + end + + Unit:ChooseOptionID(1) + + ----------------------------------- + + local GenBase = Menu:AddCollapsible("Shift Point Generator") + + for _, PanelData in ipairs(GenData) do + local Variable = PanelData.Variable + local Default = ValuesData[Variable] + + if not Default then + Default = PanelData.Default + + ValuesData[Variable] = Default + end + + ACF.SetClientData(Variable, Default) + + local Panel = GenBase:AddNumberWang(PanelData.Name, PanelData.Min, PanelData.Max, PanelData.Decimals) + Panel:HideWang() + Panel:SetClientData(Variable, "OnValueChanged") + Panel:DefineSetter(function(_, _, _, Value) + Value = math.Round(Value, PanelData.Decimals) + + ValuesData[Variable] = Value + + Panel:SetValue(Value) + + return Value + end) + + if PanelData.Tooltip then + Panel:SetTooltip(PanelData.Tooltip) + end + end + + local Button = GenBase:AddButton("Calculate") + + function Button:DoClickInternal() + local UpshiftRPM = ValuesData.UpshiftRPM + local TotalRatio = ValuesData.TotalRatio + local FinalDrive = ValuesData.FinalDrive + local WheelDiameter = ValuesData.WheelDiameter + local Multiplier = math.pi * UpshiftRPM * TotalRatio * FinalDrive * WheelDiameter / (60 * UnitMult) + + for I = 1, Gears.Max do + local Gear = ValuesData["Gear" .. I] + + ACF.SetClientData("Shift" .. I, Gear * Multiplier) + end + end + end + end +end diff --git a/lua/acf/client/menu_items/online_wiki.lua b/lua/acf/client/menu_items/online_wiki.lua new file mode 100644 index 000000000..1cecb67bc --- /dev/null +++ b/lua/acf/client/menu_items/online_wiki.lua @@ -0,0 +1,16 @@ +local function CreateMenu(Menu) + Menu:AddTitle("Online Wiki") + + Menu:AddLabel("The new ACF wiki will have a greater focus on providing content aimed towards the average builder.") + Menu:AddLabel("There's also gonna be a section where we'll document anything that could be useful for extension developers.") + + local Wiki = Menu:AddButton("Open the Wiki") + + function Wiki:DoClickInternal() + gui.OpenURL("https://github.com/Stooberton/ACF-3/wiki") + end + + Menu:AddHelp("The wiki is still a work in progress, it'll get populated as time passes.") +end + +ACF.AddMenuItem(1, "About the Addon", "Online Wiki", "book_open", CreateMenu) diff --git a/lua/acf/client/menu_items/sensors_menu.lua b/lua/acf/client/menu_items/sensors_menu.lua new file mode 100644 index 000000000..bb133f118 --- /dev/null +++ b/lua/acf/client/menu_items/sensors_menu.lua @@ -0,0 +1,72 @@ +local ACF = ACF +local Sensors = ACF.Classes.Sensors + +local function CreateMenu(Menu) + ACF.SetClientData("PrimaryClass", "N/A") + ACF.SetClientData("SecondaryClass", "N/A") + + ACF.SetToolMode("acf_menu", "Spawner", "Sensor") + + if not next(Sensors) then + Menu:AddTitle("No Sensors Registered") + Menu:AddLabel("No sensors have been registered. If this is incorrect, check your console for errors and contact the server owner.") + return + end + + Menu:AddTitle("Sensor Settings") + + local SensorClass = Menu:AddComboBox() + local SensorList = Menu:AddComboBox() + + local Base = Menu:AddCollapsible("Sensor Information") + local SensorName = Base:AddTitle() + local SensorDesc = Base:AddLabel() + local SensorPreview = Base:AddModelPreview() + + function SensorClass:OnSelect(Index, _, Data) + if self.Selected == Data then return end + + self.ListData.Index = Index + self.Selected = Data + + ACF.SetClientData("SensorClass", Data.ID) + + ACF.LoadSortedList(SensorList, Data.Items, "ID") + end + + function SensorList:OnSelect(Index, _, Data) + if self.Selected == Data then return end + + self.ListData.Index = Index + self.Selected = Data + + local Preview = Data.Preview + local ClassData = SensorClass.Selected + + ACF.SetClientData("Sensor", Data.ID) + + SensorName:SetText(Data.Name) + SensorDesc:SetText(Data.Description or "No description provided.") + + SensorPreview:SetModel(Data.Model) + SensorPreview:SetCamPos(Preview and Preview.Offset or Vector(45, 60, 45)) + SensorPreview:SetLookAt(Preview and Preview.Position or Vector()) + SensorPreview:SetHeight(Preview and Preview.Height or 80) + SensorPreview:SetFOV(Preview and Preview.FOV or 75) + + Menu:ClearTemporal(Base) + Menu:StartTemporal(Base) + + local CustomMenu = Data.CreateMenu or ClassData.CreateMenu + + if CustomMenu then + CustomMenu(Data, Base) + end + + Menu:EndTemporal(Base) + end + + ACF.LoadSortedList(SensorClass, Sensors, "ID") +end + +ACF.AddMenuItem(401, "Entities", "Sensors", "transmit", CreateMenu) diff --git a/lua/acf/client/menu_items/settings_menu.lua b/lua/acf/client/menu_items/settings_menu.lua new file mode 100644 index 000000000..90f4fd2db --- /dev/null +++ b/lua/acf/client/menu_items/settings_menu.lua @@ -0,0 +1,307 @@ +local ACF = ACF + +do -- Clientside settings + local Ent_Info = GetConVar("acf_show_entity_info") + local InfoHelp = { + [0] = "ACF entities will never display their information bubble when the player looks at them.", + [1] = "ACF entities will only display their information bubble when the player looks at them while they're not seated.", + [2] = "ACF entities will always display their information bubble when a player looks at them." + } + + ACF.AddMenuItem(1, "Settings", "Clientside Settings", "user", ACF.GenerateClientSettings) + + ACF.AddClientSettings(1, "Entity Information", function(Base) + local InfoValue = InfoHelp[Ent_Info:GetInt()] and Ent_Info:GetInt() or 1 + + Base:AddLabel("Display ACF entity information:") + + local Info = Base:AddComboBox() + Info:AddChoice("Never", 0) + Info:AddChoice("When not seated", 1) + Info:AddChoice("Always", 2) + + local InfoDesc = Base:AddHelp() + InfoDesc:SetText(InfoHelp[InfoValue]) + + function Info:OnSelect(_, _, Data) + if not InfoHelp[Data] then + Data = 1 + end + + Ent_Info:SetInt(Data) + + InfoDesc:SetText(InfoHelp[Data]) + end + + Info:ChooseOptionID(InfoValue + 1) + + local HitBox = Base:AddCheckBox("Draw hitboxes on ACF entities.") + HitBox:SetConVar("acf_drawboxes") + + Base:AddHelp("Some entities might display more than just their hitbox.") + + local Rounds = Base:AddSlider("Max Rounds", 0, 64, 0) + Rounds:SetConVar("ACF_MaxRoundsDisplay") + + Base:AddHelp("Defines the maximum amount of rounds an ammo crate needs to have before using bulk display.") + Base:AddHelp("Requires hitboxes to be enabled.") + end) + + ACF.AddClientSettings(100, "Sound Volume", function(Base) + local Volume = Base:AddSlider("Client Sound Volume", 0, 1, 2) + Volume:SetClientData("Volume", "OnValueChanged") + Volume:DefineSetter(function(Panel, _, _, Value) + Panel:SetValue(Value) + + return Value + end) + + Base:AddHelp("For the moment, this will only affect sounds that are played from the clientside.") + end) + + ACF.AddClientSettings(101, "Effects and Visual Elements", function(Base) + local Ropes = Base:AddCheckBox("Create mobility rope links.") + Ropes:SetConVar("acf_mobilityropelinks") + + local Particles = Base:AddSlider("Particle Mult.", 0.1, 1, 2) + Particles:SetConVar("acf_cl_particlemul") + + Base:AddHelp("Defines the clientside particle multiplier, reduce it if you're experiencing lag when ACF effects are created.") + end) + + ACF.AddClientSettings(201, "Legal Checks", function(Base) + local Hints = Base:AddCheckBox("Enable hints on entity disabling.") + Hints:SetConVar("acf_legalhints") + end) + + ACF.AddClientSettings(301, "Debris", function(Base) + local Debris = Base:AddCheckBox("Allow creation of clientside debris.") + Debris:SetConVar("acf_debris") + + local Collisions = Base:AddCheckBox("Allow debris to collide with entities.") + Collisions:SetConVar("acf_debris_collision") + + Base:AddHelp("Disabling this can prevent certain types of spam-induced lag and crashes.") + + local Lifetime = Base:AddSlider("Debris Lifetime", 1, 300) + Lifetime:SetConVar("acf_debris_lifetime") + + Base:AddHelp("Defines how long each debris will live before fading out.") + + local Multiplier = Base:AddSlider("Debris Gib Amount", 0.01, 1, 2) + Multiplier:SetConVar("acf_debris_gibmultiplier") + + Base:AddHelp("Multiplier for the amount of clientside debris gibs to be created.") + + local GibLifetime = Base:AddSlider("Debris Gib Lifetime", 1, 300) + GibLifetime:SetConVar("acf_debris_giblifetime") + + Base:AddHelp("Defines how long each debris gib will live before fading out.") + end) + + ACF.AddClientSettings(401, "Tool Category", function(Base) + local Category = Base:AddCheckBox("Use custom category for ACF tools.") + Category:SetConVar("acf_tool_category") + + Base:AddHelp("You will need to rejoin the server for this option to apply.") + end) +end + +do -- Serverside settings + ACF.AddMenuItem(101, "Settings", "Serverside Settings", "server", ACF.GenerateServerSettings) + + ACF.AddServerSettings(1, "General Settings", function(Base) + Base:AddLabel("ACF Gamemode for this server:") + + local Gamemode = Base:AddComboBox() + Gamemode:AddChoice("Sandbox") + Gamemode:AddChoice("Classic") + Gamemode:AddChoice("Competitive") + Gamemode:SetServerData("Gamemode", "OnSelect") + Gamemode:DefineSetter(function(Panel, _, _, Value) + if Panel.selected == Value then return end -- God bless derma + + Panel:ChooseOptionID(Value) + + return Value + end) + + Base:AddHelp("Each gamemode has its own restrictions, Sandbox being the most relaxed and Competitive the most strict.") + + local Admins = Base:AddCheckBox("Allow admins to control server data.") + Admins:SetServerData("ServerDataAllowAdmin", "OnChange") + Admins:DefineSetter(function(Panel, _, _, Value) + Panel:SetValue(Value) + + return Value + end) + + Base:AddHelp("If enabled, admins will be able to mess with the settings on this panel.") + + local Info = Base:AddCheckBox("Restrict entity information.") + Info:SetServerData("RestrictInfo", "OnChange") + Info:DefineSetter(function(Panel, _, _, Value) + Panel:SetValue(Value) + + return Value + end) + + Base:AddHelp("You'll need the player's permissions in order to check relevant information on entities owned by them.") + + local Gunfire = Base:AddCheckBox("Allow weapon fire.") + Gunfire:SetServerData("GunfireEnabled", "OnChange") + Gunfire:DefineSetter(function(Panel, _, _, Value) + Panel:SetValue(Value) + + return Value + end) + + local Health = Base:AddSlider("Health Factor", 0.01, 2, 2) + Health:SetServerData("HealthFactor", "OnValueChanged") + Health:DefineSetter(function(Panel, _, _, Value) + Panel:SetValue(Value) + + return Value + end) + + local Fuel = Base:AddSlider("Fuel Factor", 0.01, 2, 2) + Fuel:SetServerData("FuelFactor", "OnValueChanged") + Fuel:DefineSetter(function(Panel, _, _, Value) + Panel:SetValue(Value) + + return Value + end) + + local CompFuel = Base:AddSlider("Competitive Fuel Factor", 0.01, 2, 2) + CompFuel:SetServerData("CompFuelFactor", "OnValueChanged") + CompFuel:DefineSetter(function(Panel, _, _, Value) + Panel:SetValue(Value) + + return Value + end) + + Base:AddHelp("Only applies to servers with their ACF Gamemode set to Competitive.") + end) + + ACF.AddServerSettings(100, "Sound Volume", function(Base) + local Volume = Base:AddSlider("Server Sound Volume", 0, 1, 2) + Volume:SetServerData("Volume", "OnValueChanged") + Volume:DefineSetter(function(Panel, _, _, Value) + Panel:SetValue(Value) + + return Value + end) + + Base:AddHelp("Will affect a handful of sounds that are played from the serverside. This will be deprecated in the future.") + end) + + ACF.AddServerSettings(101, "Entity Pushing", function(Base) + local HEPush = Base:AddCheckBox("Push entities due to HE forces.") + HEPush:SetServerData("HEPush", "OnChange") + HEPush:DefineSetter(function(Panel, _, _, Value) + Panel:SetValue(Value) + + return Value + end) + + local KEPush = Base:AddCheckBox("Push entities due to kinetic forces.") + KEPush:SetServerData("KEPush", "OnChange") + KEPush:DefineSetter(function(Panel, _, _, Value) + Panel:SetValue(Value) + + return Value + end) + + local Recoil = Base:AddCheckBox("Push entities due to weapon recoil.") + Recoil:SetServerData("RecoilPush", "OnChange") + Recoil:DefineSetter(function(Panel, _, _, Value) + Panel:SetValue(Value) + + return Value + end) + end) + + ACF.AddServerSettings(201, "Fun Entities and Menu", function(Base) + local Entities = Base:AddCheckBox("Allow use of Fun Entities.") + Entities:SetServerData("AllowFunEnts", "OnChange") + Entities:DefineSetter(function(Panel, _, _, Value) + Panel:SetValue(Value) + + return Value + end) + + Base:AddHelp("Entities can be still spawned if this option is disabled.") + + local Menu = Base:AddCheckBox("Show Fun Entities menu option.") + Menu:SetServerData("ShowFunMenu", "OnChange") + Menu:DefineSetter(function(Panel, _, _, Value) + Panel:SetValue(Value) + + return Value + end) + + Base:AddHelp("Changes on this option will only take effect once the players reload their menu.") + end) + + ACF.AddServerSettings(301, "Workshop Content", function(Base) + local Content = Base:AddCheckBox("Enable workshop content download for clients.") + Content:SetServerData("WorkshopContent", "OnChange") + Content:DefineSetter(function(Panel, _, _, Value) + Panel:SetValue(Value) + + return Value + end) + + local Extra = Base:AddCheckBox("Enable extra workshop content download for clients.") + Extra:SetServerData("WorkshopExtras", "OnChange") + Extra:DefineSetter(function(Panel, _, _, Value) + Panel:SetValue(Value) + + return Value + end) + + Base:AddHelp("Both of these options require a server restart to apply changes.") + end) + + ACF.AddServerSettings(401, "Custom Killicons", function(Base) + local Icons = Base:AddCheckBox("Use custom killicons for ACF entities.") + Icons:SetServerData("UseKillicons", "OnChange") + Icons:DefineSetter(function(Panel, _, _, Value) + Panel:SetValue(Value) + + return Value + end) + + Base:AddHelp("Changing this option will require a server restart.") + end) + + ACF.AddServerSettings(501, "Debris", function(Base) + local Debris = Base:AddCheckBox("Allow networking of debris to clients.") + Debris:SetServerData("CreateDebris", "OnChange") + Debris:DefineSetter(function(Panel, _, _, Value) + Panel:SetValue(Value) + + return Value + end) + + local Fireballs = Base:AddCheckBox("Allow creation of serverside debris fireballs.") + Fireballs:SetServerData("CreateFireballs", "OnChange") + Fireballs:DefineSetter(function(Panel, _, _, Value) + Panel:SetValue(Value) + + return Value + end) + + Base:AddHelp("Allows compatibility with addons such as vFire, but is more taxing on server resources.") + + local Multiplier = Base:AddSlider("Fireball Amount", 0.01, 1, 2) + Multiplier:SetServerData("FireballMult", "OnValueChanged") + Multiplier:DefineSetter(function(Panel, _, _, Value) + Panel:SetValue(Value) + + return Value + end) + + Base:AddHelp("Multiplier for the amount of serverside fireballs to be created.") + end) +end diff --git a/lua/acf/client/menu_items/updates_menu.lua b/lua/acf/client/menu_items/updates_menu.lua new file mode 100644 index 000000000..69e6a55c5 --- /dev/null +++ b/lua/acf/client/menu_items/updates_menu.lua @@ -0,0 +1,90 @@ +local ACF = ACF +local Repository, MenuBase + +local function LoadCommit(Base, Commit) + local Date = Commit.Date + + Base:AddTitle(Commit.Title or "No Title") + Base:AddLabel("Author: " .. (Commit.Author or "Unknown")) + Base:AddLabel("Date: " .. (Date and os.date("%D", Date) or "Unknown")) + Base:AddLabel("Time: " .. (Date and os.date("%T", Date) or "Unknown")) + Base:AddLabel(Commit.Body or "No commit message.") + + local View = Base:AddButton("View this commit") + function View:DoClickInternal() + gui.OpenURL(Commit.Link) + end +end + +local function AddStatus(Name, Branches) + local Data = Repository[Name] + local Branch = Branches[Data.Head] or Branches.master + local Base = MenuBase:AddCollapsible(Name .. " Status") + + Base:SetTooltip("Left-click to copy the " .. Name .. " version to your clipboard!") + + function Base:OnMousePressed(Code) + if Code ~= MOUSE_LEFT then return end + + SetClipboardText(Data.Code or "Unknown") + end + + Base:AddTitle("Status: " .. (Data.Status or "Unknown")) + Base:AddLabel("Version: " .. (Data.Code or "Unknown")) + + if Branch and Data.Status ~= "Up to date" then + Base:AddLabel("Latest: " .. Branch.Code) + end + + Base:AddLabel("Branch: " .. (Data.Head or "Unknown")) + + if Branch then + local Commit, Header = Base:AddCollapsible("Latest Commit", false) + + function Header:OnToggle(Expanded) + if not Expanded then return end + if self.Loaded then return end + + LoadCommit(Commit, Branch) + + self.Loaded = true + end + else + Base:AddTitle("Unable to retrieve the latest commit.") + end + + MenuBase:AddLabel("") -- Empty space +end + +local function UpdateMenu() + if not IsValid(MenuBase) then return end + if not Repository then return end + + local Branches = Repository.Branches + + MenuBase:ClearTemporal() + MenuBase:StartTemporal() + + AddStatus("Server", Branches) + AddStatus("Client", Branches) + + MenuBase:EndTemporal() +end + +local function CreateMenu(Menu) + Menu:AddTitle("ACF Version Status") + + MenuBase = Menu:AddPanel("ACF_Panel") + + UpdateMenu() +end + +ACF.AddMenuItem(101, "About the Addon", "Updates", "newspaper", CreateMenu) + +hook.Add("ACF_UpdatedRepository", "ACF Updates Menu", function(Name, Repo) + if Name ~= "ACF-3" then return end + + Repository = Repo + + UpdateMenu() +end) diff --git a/lua/acf/client/menu_items/weapons_menu.lua b/lua/acf/client/menu_items/weapons_menu.lua new file mode 100644 index 000000000..58231ef60 --- /dev/null +++ b/lua/acf/client/menu_items/weapons_menu.lua @@ -0,0 +1,84 @@ +local ACF = ACF +local Weapons = ACF.Classes.Weapons + +local function CreateMenu(Menu) + local EntText = "Mass : %s kg\nFirerate : %s rpm\nSpread : %s degrees%s\n\nThis entity can be fully parented." + local MagText = "\nRounds : %s rounds\nReload : %s seconds" + + Menu:AddTitle("Weapon Settings") + + local ClassList = Menu:AddComboBox() + local EntList = Menu:AddComboBox() + + local WeaponBase = Menu:AddCollapsible("Weapon Information") + local EntName = WeaponBase:AddTitle() + local ClassDesc = WeaponBase:AddLabel() + local EntPreview = WeaponBase:AddModelPreview() + local EntData = WeaponBase:AddLabel() + + local AmmoList = ACF.CreateAmmoMenu(Menu) + + ACF.SetClientData("PrimaryClass", "acf_gun") + ACF.SetClientData("SecondaryClass", "acf_ammo") + + ACF.SetToolMode("acf_menu", "Spawner", "Weapon") + + function ClassList:OnSelect(Index, _, Data) + if self.Selected == Data then return end + + self.ListData.Index = Index + self.Selected = Data + + ACF.SetClientData("WeaponClass", Data.ID) + + ClassDesc:SetText(Data.Description) + + ACF.LoadSortedList(EntList, Data.Items, "Caliber") + + AmmoList:LoadEntries(Data.ID) + end + + function EntList:OnSelect(Index, _, Data) + if self.Selected == Data then return end + + self.ListData.Index = Index + self.Selected = Data + + local Preview = Data.Preview + + ACF.SetClientData("Weapon", Data.ID) + ACF.SetClientData("Destiny", Data.Destiny or "Weapons") + + EntName:SetText(Data.Name) + + EntPreview:SetModel(Data.Model) + EntPreview:SetCamPos(Preview and Preview.Offset or Vector(45, 60, 45)) + EntPreview:SetLookAt(Preview and Preview.Position or Vector()) + EntPreview:SetHeight(Preview and Preview.Height or 80) + EntPreview:SetFOV(Preview and Preview.FOV or 75) + + ACF.UpdateAmmoMenu(Menu) + end + + EntData:TrackClientData("Projectile", "SetText") + EntData:TrackClientData("Propellant") + EntData:TrackClientData("Tracer") + EntData:DefineSetter(function() + local Class = ClassList.Selected + local Data = EntList.Selected + + if not Class then return "" end + if not Data then return "" end + + local AmmoData = ACF.GetCurrentAmmoData() + local ReloadTime = AmmoData and (ACF.BaseReload + (AmmoData.ProjMass + AmmoData.PropMass) * ACF.MassToTime) or 60 + local Firerate = Data.Cyclic or 60 / ReloadTime + local Magazine = Data.MagSize and MagText:format(Data.MagSize, Data.MagReload) or "" + + return EntText:format(Data.Mass, math.Round(Firerate, 2), Class.Spread, Magazine) + end) + + ACF.LoadSortedList(ClassList, Weapons, "Name") +end + +ACF.AddMenuItem(1, "Entities", "Weapons", "gun", CreateMenu) diff --git a/lua/acf/client/cl_permissions.lua b/lua/acf/client/permissions.lua similarity index 99% rename from lua/acf/client/cl_permissions.lua rename to lua/acf/client/permissions.lua index 8f21b904a..369604529 100644 --- a/lua/acf/client/cl_permissions.lua +++ b/lua/acf/client/permissions.lua @@ -189,7 +189,7 @@ end local cat = Menu.Category local item = Menu.Name -local var = Menu.Command +local var = Menu.Command local open = Menu.OnSpawnmenuOpen local panel = Menu.MakePanel local hookname = string.Replace(item," ","_") diff --git a/lua/acf/client/persisted_vars.lua b/lua/acf/client/persisted_vars.lua new file mode 100644 index 000000000..e7adf27d4 --- /dev/null +++ b/lua/acf/client/persisted_vars.lua @@ -0,0 +1,9 @@ +-- Variables that should be persisted between servers + +-- Settings +ACF.PersistClientData("Volume", 1) + +-- Crate size +ACF.PersistClientData("CrateSizeX", 24) +ACF.PersistClientData("CrateSizeY", 24) +ACF.PersistClientData("CrateSizeZ", 24) diff --git a/lua/acf/client/sk_menu.lua b/lua/acf/client/sk_menu.lua deleted file mode 100644 index d924390f3..000000000 --- a/lua/acf/client/sk_menu.lua +++ /dev/null @@ -1,493 +0,0 @@ -function PANEL:Init() - acfmenupanel = self.Panel - -- height - self:SetTall(surface.ScreenHeight() - 120) - --Weapon Select - self.WeaponSelect = vgui.Create("DTree", self) - self.WeaponData = ACF.Weapons - local Classes = ACF.Classes - self.Classes = {} - - for ID, Table in pairs(Classes) do - self.Classes[ID] = {} - - for ClassID, Class in pairs(Table) do - Class.id = ClassID - table.insert(self.Classes[ID], Class) - end - - table.sort(self.Classes[ID], function(a, b) return a.id < b.id end) - end - - local WeaponDisplay = ACF.Weapons - self.WeaponDisplay = {} - - for ID, Table in pairs(WeaponDisplay) do - self.WeaponDisplay[ID] = {} - - for _, Data in pairs(Table) do - table.insert(self.WeaponDisplay[ID], Data) - end - - if ID == "Guns" then - table.sort(self.WeaponDisplay[ID], function(a, b) - if a.gunclass == b.gunclass then - return a.caliber < b.caliber - else - return a.gunclass < b.gunclass - end - end) - else - table.sort(self.WeaponDisplay[ID], function(a, b) return a.id < b.id end) - end - end - - local HomeNode = self.WeaponSelect:AddNode("ACF Home", "icon16/newspaper.png") - local OldSelect = HomeNode.OnNodeSelected - HomeNode.mytable = {} - - HomeNode.mytable.guicreate = (function(_, Table) - ACFHomeGUICreate(Table) - end or nil) - - function HomeNode:OnNodeSelected(Node) - acfmenupanel:UpdateDisplay(self.mytable) - - OldSelect(self, Node) - end - - self.WeaponSelect:SetSelectedItem(HomeNode) - - local RoundAttribs = ACF.RoundTypes - self.RoundAttribs = {} - - for ID, Table in pairs(RoundAttribs) do - Table.id = ID - table.insert(self.RoundAttribs, Table) - end - - table.sort(self.RoundAttribs, function(a, b) return a.id < b.id end) - local Guns = self.WeaponSelect:AddNode("Guns") - - for _, Class in pairs(self.Classes["GunClass"]) do - local SubNode = Guns:AddNode(Class.name or "No Name") - - for _, Ent in pairs(self.WeaponDisplay["Guns"]) do - if Ent.gunclass == Class.id then - local EndNode = SubNode:AddNode(Ent.name or "No Name") - EndNode.mytable = Ent - - function EndNode:DoClick() - RunConsoleCommand("acfmenu_type", self.mytable.type) - acfmenupanel:UpdateDisplay(self.mytable) - end - - EndNode.Icon:SetImage("icon16/newspaper.png") - end - end - end - - local Ammo = self.WeaponSelect:AddNode("Ammo") - - for _, AmmoTable in pairs(self.RoundAttribs) do - local EndNode = Ammo:AddNode(AmmoTable.name or "No Name") - EndNode.mytable = AmmoTable - - function EndNode:DoClick() - RunConsoleCommand("acfmenu_type", self.mytable.type) - acfmenupanel:UpdateDisplay(self.mytable) - end - - EndNode.Icon:SetImage("icon16/newspaper.png") - end - - local Mobility = self.WeaponSelect:AddNode("Mobility") - local Engines = Mobility:AddNode("Engines") - local Gearboxes = Mobility:AddNode("Gearboxes") - local FuelTanks = Mobility:AddNode("Fuel Tanks") - local EngineSubcats = {} - - for _, MobilityTable in pairs(self.WeaponDisplay["Mobility"]) do - local NodeAdd = Mobility - - if (MobilityTable.ent == "acf_engine") then - NodeAdd = Engines - elseif (MobilityTable.ent == "acf_gearbox") then - NodeAdd = Gearboxes - elseif (MobilityTable.ent == "acf_fueltank") then - NodeAdd = FuelTanks - end - - if ((EngineSubcats["misce"] == nil) and (EngineSubcats["miscg"] == nil)) then - EngineSubcats["misce"] = Engines:AddNode("Miscellaneous") - EngineSubcats["miscg"] = Gearboxes:AddNode("Miscellaneous") - end - - if MobilityTable.category and not EngineSubcats[MobilityTable.category] then - EngineSubcats[MobilityTable.category] = NodeAdd:AddNode(MobilityTable.category) - end - end - - for _, MobilityTable in pairs(self.WeaponDisplay["Mobility"]) do - local NodeAdd = Mobility - - if MobilityTable.ent == "acf_engine" then - NodeAdd = Engines - - if (MobilityTable.category) then - NodeAdd = EngineSubcats[MobilityTable.category] - else - NodeAdd = EngineSubcats["misce"] - end - elseif MobilityTable.ent == "acf_gearbox" then - NodeAdd = Gearboxes - - if (MobilityTable.category) then - NodeAdd = EngineSubcats[MobilityTable.category] - else - NodeAdd = EngineSubcats["miscg"] - end - elseif MobilityTable.ent == "acf_fueltank" then - NodeAdd = FuelTanks - - if (MobilityTable.category) then - NodeAdd = EngineSubcats[MobilityTable.category] - end - end - - local EndNode = NodeAdd:AddNode(MobilityTable.name or "No Name") - EndNode.mytable = MobilityTable - - function EndNode:DoClick() - RunConsoleCommand("acfmenu_type", self.mytable.type) - acfmenupanel:UpdateDisplay(self.mytable) - end - - EndNode.Icon:SetImage("icon16/newspaper.png") - end - --[[local Missiles = self.WeaponSelect:AddNode( "Missiles" ) - for MisID, MisTable in pairs(self.WeaponDisplay["Missiles"]) do - - local EndNode = Missiles:AddNode( MisTable.name or "No Name" ) - - EndNode.mytable = MisTable - function EndNode:DoClick() - RunConsoleCommand( "acfmenu_type", self.mytable.type ) - acfmenupanel:UpdateDisplay( self.mytable ) - end - - EndNode.Icon:SetImage( "icon16/newspaper.png") - - end]] - -- local Sensors = self.WeaponSelect:AddNode( "Sensors" ) - -- for SensorsID,SensorsTable in pairs(self.WeaponDisplay["Sensors"]) do - -- local EndNode = Sensors:AddNode( SensorsTable.name or "No Name" ) - -- EndNode.mytable = SensorsTable - -- function EndNode:DoClick() - -- RunConsoleCommand( "acfmenu_type", self.mytable.type ) - -- acfmenupanel:UpdateDisplay( self.mytable ) - -- end - -- EndNode.Icon:SetImage( "icon16/newspaper.png" ) - -- end -end - ---[[------------------------------------ - Think -------------------------------------]] -function PANEL:Think() -end - -function PANEL:UpdateDisplay(Table) - RunConsoleCommand("acfmenu_id", Table.id or 0) - - --If a previous display exists, erase it - if (acfmenupanel.CustomDisplay) then - acfmenupanel.CustomDisplay:Clear(true) - acfmenupanel.CustomDisplay = nil - acfmenupanel.CData = nil - end - - --Create the space to display the custom data - acfmenupanel.CustomDisplay = vgui.Create("DPanelList", acfmenupanel) - acfmenupanel.CustomDisplay:SetSpacing(10) - acfmenupanel.CustomDisplay:EnableHorizontal(false) - acfmenupanel.CustomDisplay:EnableVerticalScrollbar(false) - acfmenupanel.CustomDisplay:SetSize(acfmenupanel:GetWide(), acfmenupanel:GetTall()) - - if not acfmenupanel["CData"] then - --Create a table for the display to store data - acfmenupanel["CData"] = {} - end - - acfmenupanel.CreateAttribs = Table.guicreate - acfmenupanel.UpdateAttribs = Table.guiupdate - acfmenupanel:CreateAttribs(Table) - acfmenupanel:PerformLayout() -end - -function PANEL:PerformLayout() - --Starting positions - local vspacing = 10 - local ypos = 0 - --Selection Tree panel - acfmenupanel.WeaponSelect:SetPos(0, ypos) - acfmenupanel.WeaponSelect:SetSize(acfmenupanel:GetWide(), ScrH() * 0.4) - ypos = acfmenupanel.WeaponSelect.Y + acfmenupanel.WeaponSelect:GetTall() + vspacing - - if acfmenupanel.CustomDisplay then - --Custom panel - acfmenupanel.CustomDisplay:SetPos(0, ypos) - acfmenupanel.CustomDisplay:SetSize(acfmenupanel:GetWide(), acfmenupanel:GetTall() - acfmenupanel.WeaponSelect:GetTall() - 10) - ypos = acfmenupanel.CustomDisplay.Y + acfmenupanel.CustomDisplay:GetTall() + vspacing - end -end - -function ACFHomeGUICreate() - if not acfmenupanel.CustomDisplay then return end - - local Display = acfmenupanel.CustomDisplay - local CData = acfmenupanel.CData - - local Text = "%s Status\n\nVersion: %s\nBranch: %s\nStatus: %s\n\n" - local Repo = ACF.GetVersion("ACF-3") - local Server = Repo.Server - local SVCode = Server and Server.Code or "Unable to Retrieve" - local SVHead = Server and Server.Head or "Unable to Retrieve" - local SVStatus = Server and Server.Status or "Unable to Retrieve" - - if not Repo.Status then - ACF.GetVersionStatus("ACF-3") - end - - CData.Header = vgui.Create("DLabel") - CData.Header:SetText("ACF Version Status\n") - CData.Header:SetFont("ACF_Subtitle") - CData.Header:SetDark(true) - CData.Header:SizeToContents() - Display:AddItem(CData.Header) - - CData.ServerStatus = vgui.Create("DLabel") - CData.ServerStatus:SetText(Text:format("Server", SVCode, SVHead, SVStatus)) - CData.ServerStatus:SetFont("ACF_Paragraph") - CData.ServerStatus:SetDark(true) - CData.ServerStatus:SizeToContents() - Display:AddItem(CData.ServerStatus) - - CData.ClientStatus = vgui.Create("DLabel") - CData.ClientStatus:SetText(Text:format("Client", Repo.Code, Repo.Head, Repo.Status)) - CData.ClientStatus:SetFont("ACF_Paragraph") - CData.ClientStatus:SetDark(true) - CData.ClientStatus:SizeToContents() - Display:AddItem(CData.ClientStatus) -end - -function PANEL:AmmoSelect(Blacklist) - if not acfmenupanel.CustomDisplay then return end - - local AmmoData = acfmenupanel.AmmoData - - if not Blacklist then - Blacklist = {} - end - - if not AmmoData then - AmmoData = { - Id = "Ammo2x4x4", - Type = "Ammo", - Data = acfmenupanel.WeaponData.Guns["12.7mmMG"].round - } - - acfmenupanel.AmmoData = AmmoData - end - - --Creating the ammo crate selection - --[[ - acfmenupanel.CData.CrateSelect = vgui.Create("DComboBox", acfmenupanel.CustomDisplay) --Every display and slider is placed in the Round table so it gets trashed when selecting a new round type - acfmenupanel.CData.CrateSelect:SetSize(100, 30) - - for Key, Value in pairs(acfmenupanel.WeaponDisplay["Ammo"]) do - acfmenupanel.CData.CrateSelect:AddChoice(Value.id, Key) - end - - acfmenupanel.CData.CrateSelect.OnSelect = function(_, _, data) - RunConsoleCommand("acfmenu_id", data) - acfmenupanel.AmmoData["Id"] = data - self:UpdateAttribs() - end - - acfmenupanel.CData.CrateSelect:SetText(acfmenupanel.AmmoData["Id"]) - acfmenupanel.CustomDisplay:AddItem(acfmenupanel.CData.CrateSelect) - ]]-- - - RunConsoleCommand("acfmenu_id", AmmoData.Id) - --Create the caliber selection display - acfmenupanel.CData.CaliberSelect = vgui.Create("DComboBox", acfmenupanel.CustomDisplay) - acfmenupanel.CData.CaliberSelect:SetSize(100, 30) - - for Key, Value in pairs(acfmenupanel.WeaponDisplay["Guns"]) do - if (not table.HasValue(Blacklist, Value.gunclass)) then - acfmenupanel.CData.CaliberSelect:AddChoice(Value.id, Key) - end - end - - acfmenupanel.CData.CaliberSelect.OnSelect = function(_, _, data) - AmmoData.Data = acfmenupanel.WeaponData.Guns[data].round - - self:UpdateAttribs() - self:UpdateAttribs() --Note : this is intentional - end - - acfmenupanel.CData.CaliberSelect:SetText(AmmoData.Data.id) - acfmenupanel.CustomDisplay:AddItem(acfmenupanel.CData.CaliberSelect) - - -- Create ammo crate scale sliders - local X = AmmoData["Ammo Scale X"] or 24 - local Y = AmmoData["Ammo Scale Y"] or 24 - local Z = AmmoData["Ammo Scale Z"] or 24 - - acfmenupanel:AmmoSlider("Ammo Scale X", X, 6, 96, 3, "Crate X scale") - acfmenupanel:AmmoSlider("Ammo Scale Y", Y, 6, 96, 3, "Crate Y scale") - acfmenupanel:AmmoSlider("Ammo Scale Z", Z, 6, 96, 3, "Crate Z scale") - - acfmenupanel["CData"]["Ammo Scale X"].OnValueChanged = function(_, val) - if AmmoData["Ammo Scale X"] ~= val then - AmmoData["Ammo Scale X"] = val - - self:UpdateAttribs("Ammo Scale X") - - RunConsoleCommand("acfmenu_data11", val) - end - end - - acfmenupanel["CData"]["Ammo Scale Y"].OnValueChanged = function(_, val) - if AmmoData["Ammo Scale Y"] ~= val then - AmmoData["Ammo Scale Y"] = val - - self:UpdateAttribs("Ammo Scale Y") - - RunConsoleCommand("acfmenu_data12", val) - end - end - - acfmenupanel["CData"]["Ammo Scale Z"].OnValueChanged = function(_, val) - if AmmoData["Ammo Scale Z"] ~= val then - AmmoData["Ammo Scale Z"] = val - - self:UpdateAttribs("Ammo Scale Z") - - RunConsoleCommand("acfmenu_data13", val) - end - end -end - --- If it works don't fix it man, btw just add this function to every single ammo type -function PANEL:AmmoUpdate() - local AmmoData = acfmenupanel.AmmoData - - acfmenupanel:AmmoSlider("Ammo Scale X", AmmoData["Ammo Scale X"], 6, 96, 3, "Crate X scale") - acfmenupanel:AmmoSlider("Ammo Scale Y", AmmoData["Ammo Scale Y"], 6, 96, 3, "Crate Y scale") - acfmenupanel:AmmoSlider("Ammo Scale Z", AmmoData["Ammo Scale Z"], 6, 96, 3, "Crate Z scale") -end - ---Variable name in the table, Value, Min value, Max Value, slider text title, slider decimals, description text below slider -function PANEL:AmmoSlider(Name, Value, Min, Max, Decimals, Title, Desc) - local Panels = acfmenupanel.CData - - if not Panels[Name] then - Panels[Name] = vgui.Create("DNumSlider", acfmenupanel.CustomDisplay) - Panels[Name].Label:SetSize(0) --Note : this is intentional - Panels[Name]:SetTall(50) -- make the slider taller to fit the new label - Panels[Name]:SetMin(Min) - Panels[Name]:SetMax(Max) - Panels[Name]:SetDecimals(Decimals) - Panels[Name .. "_label"] = vgui.Create("DLabel", Panels[Name]) -- recreating the label - Panels[Name .. "_label"]:SetPos(0, 0) - Panels[Name .. "_label"]:SetText(Title) - Panels[Name .. "_label"]:SizeToContents() - Panels[Name .. "_label"]:SetDark(true) - - Panels[Name].OnValueChanged = function(_, val) - if acfmenupanel.AmmoData[Name] ~= val then - acfmenupanel.AmmoData[Name] = val - self:UpdateAttribs(Name) - end - end - - local OldValue = acfmenupanel.AmmoData[Name] - - if OldValue then - Panels[Name]:SetValue(OldValue) - end - - acfmenupanel.CustomDisplay:AddItem(Panels[Name]) - end - - Panels[Name]:SetMin(Min) - Panels[Name]:SetMax(Max) - Panels[Name]:SetValue(Value) - - if Desc then - if not Panels[Name .. "_text"] then - Panels[Name .. "_text"] = vgui.Create("DLabel") - Panels[Name .. "_text"]:SetText(Desc) - Panels[Name .. "_text"]:SetDark(true) - Panels[Name .. "_text"]:SetTall(20) - acfmenupanel.CustomDisplay:AddItem(Panels[Name .. "_text"]) - end - - Panels[Name .. "_text"]:SetText(Desc) - Panels[Name .. "_text"]:SetSize(acfmenupanel.CustomDisplay:GetWide(), 10) - Panels[Name .. "_text"]:SizeToContentsX() - end -end - ---Variable name in the table, slider text title, slider decimeals, description text below slider -function PANEL:AmmoCheckbox(Name, Title, Desc) - if not acfmenupanel["CData"][Name] then - acfmenupanel["CData"][Name] = vgui.Create("DCheckBoxLabel") - acfmenupanel["CData"][Name]:SetText(Title or "") - acfmenupanel["CData"][Name]:SetDark(true) - acfmenupanel["CData"][Name]:SizeToContents() - - if acfmenupanel.AmmoData[Name] ~= nil then - acfmenupanel["CData"][Name]:SetChecked(acfmenupanel.AmmoData[Name]) - else - acfmenupanel.AmmoData[Name] = false - end - - acfmenupanel["CData"][Name].OnChange = function(_, bval) - acfmenupanel.AmmoData[Name] = bval - self:UpdateAttribs({Name, bval}) - end - - acfmenupanel.CustomDisplay:AddItem(acfmenupanel["CData"][Name]) - end - - acfmenupanel["CData"][Name]:SetText(Title) - - if not acfmenupanel["CData"][Name .. "_text"] and Desc then - acfmenupanel["CData"][Name .. "_text"] = vgui.Create("DLabel") - acfmenupanel["CData"][Name .. "_text"]:SetText(Desc or "") - acfmenupanel["CData"][Name .. "_text"]:SetDark(true) - acfmenupanel.CustomDisplay:AddItem(acfmenupanel["CData"][Name .. "_text"]) - end - - acfmenupanel["CData"][Name .. "_text"]:SetText(Desc) - acfmenupanel["CData"][Name .. "_text"]:SetSize(acfmenupanel.CustomDisplay:GetWide(), 10) - acfmenupanel["CData"][Name .. "_text"]:SizeToContentsX() -end - -function PANEL:CPanelText(Name, Desc) - if not acfmenupanel["CData"][Name .. "_text"] then - acfmenupanel["CData"][Name .. "_text"] = vgui.Create("DLabel") - acfmenupanel["CData"][Name .. "_text"]:SetText(Desc or "") - acfmenupanel["CData"][Name .. "_text"]:SetDark(true) - acfmenupanel["CData"][Name .. "_text"]:SetWrap(true) - acfmenupanel["CData"][Name .. "_text"]:SetAutoStretchVertical(true) - acfmenupanel.CustomDisplay:AddItem(acfmenupanel["CData"][Name .. "_text"]) - end - - acfmenupanel["CData"][Name .. "_text"]:SetText(Desc) - acfmenupanel["CData"][Name .. "_text"]:SetSize(acfmenupanel.CustomDisplay:GetWide(), 10) - acfmenupanel["CData"][Name .. "_text"]:SizeToContentsY() -end \ No newline at end of file diff --git a/lua/acf/client/spawn_menu.lua b/lua/acf/client/spawn_menu.lua new file mode 100644 index 000000000..b19b622f6 --- /dev/null +++ b/lua/acf/client/spawn_menu.lua @@ -0,0 +1,278 @@ +ACF.MenuOptions = ACF.MenuOptions or {} +ACF.MenuLookup = ACF.MenuLookup or {} +ACF.MenuCount = ACF.MenuCount or 0 + +local Options = ACF.MenuOptions +local Lookup = ACF.MenuLookup + +do -- Menu population functions + local function DefaultAction(Menu) + Menu:AddTitle("There's nothing here.") + Menu:AddLabel("This option is either a work in progress or something isn't working as intended.") + end + + function ACF.AddMenuOption(Index, Name, Icon, Enabled) + if not Index then return end + if not Name then return end + if not isfunction(Enabled) then Enabled = nil end + + if not Lookup[Name] then + local Count = ACF.MenuCount + 1 + + Options[Count] = { + Icon = "icon16/" .. (Icon or "plugin") .. ".png", + IsEnabled = Enabled, + Index = Index, + Name = Name, + Lookup = {}, + List = {}, + Count = 0, + } + + Lookup[Name] = Options[Count] + + ACF.MenuCount = Count + else + local Option = Lookup[Name] + + Option.Icon = "icon16/" .. (Icon or "plugin") .. ".png" + Option.IsEnabled = Enabled + Option.Index = Index + end + end + + function ACF.AddMenuItem(Index, Option, Name, Icon, Action, Enabled) + if not Index then return end + if not Option then return end + if not Name then return end + if not Lookup[Option] then return end + if not isfunction(Enabled) then Enabled = nil end + + local Items = Lookup[Option] + local Item = Items.Lookup[Name] + + if not Item then + Items.Count = Items.Count + 1 + + Items.List[Items.Count] = { + Icon = "icon16/" .. (Icon or "plugin") .. ".png", + Action = Action or DefaultAction, + IsEnabled = Enabled, + Option = Option, + Index = Index, + Name = Name, + } + + Items.Lookup[Name] = Items.List[Items.Count] + else + Item.Icon = "icon16/" .. (Icon or "plugin") .. ".png" + Item.Action = Action or DefaultAction + Item.IsEnabled = Enabled + Item.Option = Option + Item.Index = Index + Item.Name = Name + end + end + + ACF.AddMenuOption(1, "About the Addon", "information") + ACF.AddMenuOption(101, "Settings", "wrench") + ACF.AddMenuOption(201, "Entities", "brick") + ACF.AddMenuOption(9999, "Fun Stuff", "bricks") +end + +do -- ACF Menu context panel + local function GetSortedList(List) + local Result = {} + + for K, V in ipairs(List) do + Result[K] = V + end + + table.SortByMember(Result, "Index", true) + + return Result + end + + local function AllowOption(Option) + if Option.IsEnabled and not Option:IsEnabled() then return false end + + return hook.Run("ACF_AllowMenuOption", Option.Index, Option.Name) ~= false + end + + local function AllowItem(Item) + if Item.IsEnabled and not Item:IsEnabled() then return false end + + return hook.Run("ACF_AllowMenuItem", Item.Index, Item.Option, Item.Name) ~= false + end + + local function PopulateTree(Tree) + local OptionList = GetSortedList(Options) + local First + + Tree.BaseHeight = 0.5 + + for _, Option in ipairs(OptionList) do + if not AllowOption(Option) then continue end + + local Parent = Tree:AddNode(Option.Name, Option.Icon) + local SetExpanded = Parent.SetExpanded + + Parent.Action = Option.Action + Parent.Master = true + Parent.Count = 0 + Parent.SetExpanded = function(Panel, Bool) + if not Panel.AllowExpand then return end + + SetExpanded(Panel, Bool) + + Panel.AllowExpand = nil + end + + Tree.BaseHeight = Tree.BaseHeight + 1 + + local ItemList = GetSortedList(Option.List) + for _, Item in ipairs(ItemList) do + if not AllowItem(Item) then continue end + + local Child = Parent:AddNode(Item.Name, Item.Icon) + Child.Action = Item.Action + Child.Parent = Parent + + Parent.Count = Parent.Count + 1 + + if not Parent.Selected then + Parent.Selected = Child + + if not First then + First = Child + end + end + end + end + + Tree:SetSelectedItem(First) + end + + local function UpdateTree(Tree, Old, New) + local OldParent = Old and Old.Parent + local NewParent = New.Parent + + if OldParent == NewParent then return end + + if OldParent then + OldParent.AllowExpand = true + OldParent:SetExpanded(false) + end + + NewParent.AllowExpand = true + NewParent:SetExpanded(true) + + Tree:SetHeight(Tree:GetLineHeight() * (Tree.BaseHeight + NewParent.Count)) + end + + function ACF.CreateSpawnMenu(Panel) + local Menu = ACF.SpawnMenu + + if not IsValid(Menu) then + Menu = vgui.Create("ACF_Panel") + Menu.Panel = Panel + + Panel:AddItem(Menu) + + ACF.SpawnMenu = Menu + else + Menu:ClearAllTemporal() + Menu:ClearAll() + end + + local Reload = Menu:AddButton("Reload Menu") + Reload:SetTooltip("You can also type 'acf_reload_spawn_menu' in console.") + function Reload:DoClickInternal() + ACF.CreateSpawnMenu(Panel) + end + + local Tree = Menu:AddPanel("DTree") + function Tree:OnNodeSelected(Node) + if self.Selected == Node then return end + + if Node.Master then + self:SetSelectedItem(Node.Selected) + return + end + + UpdateTree(self, self.Selected, Node) + + Node.Parent.Selected = Node + self.Selected = Node + + ACF.SetToolMode("acf_menu", "Main", "Idle") + ACF.SetClientData("Destiny") + + Menu:ClearTemporal() + Menu:StartTemporal() + + Node.Action(Menu) + + Menu:EndTemporal() + end + + PopulateTree(Tree) + end +end + +do -- Client and server settings + ACF.SettingsPanels = ACF.SettingsPanels or { + Client = {}, + Server = {}, + } + + local Settings = ACF.SettingsPanels + + --- Generates the following functions: + -- ACF.AddClientSettings(Index, Name, Function) + -- ACF.RemoveClientSettings(Name) + -- ACF.GenerateClientSettings(MenuPanel) + -- ACF.AddServerSettings(Index, Name, Function) + -- ACF.RemoveServerSettings(Name) + -- ACF.GenerateServerSettings(MenuPanel) + + for Realm, Destiny in pairs(Settings) do + local Hook = "ACF_On" .. Realm .. "SettingsLoaded" + local Message = "No %sside settings have been registered." + + ACF["Add" .. Realm .. "Settings"] = function(Index, Name, Function) + if not isnumber(Index) then return end + if not isstring(Name) then return end + if not isfunction(Function) then return end + + Destiny[Name] = { + Create = Function, + Index = Index, + } + end + + ACF["Remove" .. Realm .. "Settings"] = function(Name) + if not isstring(Name) then return end + + Destiny[Name] = nil + end + + ACF["Generate" .. Realm .. "Settings"] = function(Menu) + if not ispanel(Menu) then return end + + if not next(Destiny) then + Menu:AddTitle("Nothing to see here.") + Menu:AddLabel(Message:format(Realm)) + return + end + + for Name, Data in SortedPairsByMemberValue(Destiny, "Index") do + local Base = Menu:AddCollapsible(Name) + + Data.Create(Base) + + hook.Run(Hook, Name, Base) + end + end + end +end diff --git a/lua/acf/server/ballistics.lua b/lua/acf/server/ballistics.lua index 677c846c1..6501e72cf 100644 --- a/lua/acf/server/ballistics.lua +++ b/lua/acf/server/ballistics.lua @@ -12,7 +12,9 @@ local FlightTr = { start = true, endpos = true, filter = true, mask = true } local BackRes = {} local BackTrace = { start = true, endpos = true, filter = true, mask = true, output = BackRes } local GlobalFilter = ACF.GlobalFilter +local AmmoTypes = ACF.Classes.AmmoTypes local Gravity = Vector(0, 0, -GetConVar("sv_gravity"):GetInt()) +local HookRun = hook.Run cvars.AddChangeCallback("sv_gravity", function(_, _, Value) Gravity.z = -Value @@ -60,12 +62,13 @@ function ACF_CheckHitbox(Ent,RayStart,Ray) end -- This will create, or update, the tracer effect on the clientside -function ACF.BulletClient(Index, Bullet, Type, Hit, HitPos) +function ACF.BulletClient(Bullet, Type, Hit, HitPos) if Bullet.NoEffect then return end -- No clientside effect will be created for this bullet local Effect = EffectData() - Effect:SetHitBox(Index) + Effect:SetDamageType(Bullet.Index) Effect:SetStart(Bullet.Flight * 0.1) + Effect:SetAttachment(Bullet.Hide and 0 or 1) if Type == "Update" then if Hit > 0 then @@ -77,45 +80,53 @@ function ACF.BulletClient(Index, Bullet, Type, Hit, HitPos) Effect:SetScale(Hit) else Effect:SetOrigin(Bullet.Pos) - Effect:SetEntity(Entity(Bullet.Crate)) + Effect:SetEntIndex(Bullet.Crate) Effect:SetScale(0) end util.Effect("ACF_Bullet_Effect", Effect, true, true) end -function ACF.RemoveBullet(Index) - local Bullet = Bullets[Index] +function ACF.RemoveBullet(Bullet) + if Bullet.Removed then return end + + local Index = Bullet.Index Bullets[Index] = nil - Unused[Index] = true + Unused[Index] = true - if Bullet and Bullet.OnRemoved then + if Bullet.OnRemoved then Bullet:OnRemoved() end + Bullet.Removed = true + + ACF.BulletClient(Bullet, "Update", 1, Bullet.Pos) -- Kills the bullet on the clientside + if not next(Bullets) then hook.Remove("Tick", "IterateBullets") end end -function ACF.CalcBulletFlight(Index, Bullet) - if not Bullet.LastThink then return ACF.RemoveBullet(Index) end +function ACF.CalcBulletFlight(Bullet) + if Bullet.KillTime and ACF.CurTime > Bullet.KillTime then + return ACF.RemoveBullet(Bullet) + end if Bullet.PreCalcFlight then Bullet:PreCalcFlight() end local DeltaTime = ACF.CurTime - Bullet.LastThink - local Drag = Bullet.Flight:GetNormalized() * (Bullet.DragCoef * Bullet.Flight:LengthSqr()) / ACF.DragDiv - local Accel = Bullet.Accel or Gravity + local Drag = Bullet.Flight:GetNormalized() * (Bullet.DragCoef * Bullet.Flight:LengthSqr()) / ACF.DragDiv + local Accel = Bullet.Accel or Gravity - Bullet.Flight = Bullet.Flight + (Accel - Drag) * DeltaTime - Bullet.NextPos = Bullet.Pos + (Bullet.Flight * ACF.Scale * DeltaTime) + Bullet.NextPos = Bullet.Pos + (Bullet.Flight * ACF.Scale * DeltaTime) + Bullet.Flight = Bullet.Flight + (Accel - Drag) * DeltaTime Bullet.LastThink = ACF.CurTime Bullet.DeltaTime = DeltaTime - ACF.DoBulletsFlight(Index, Bullet) + ACF.DoBulletsFlight(Bullet) if Bullet.PostCalcFlight then Bullet:PostCalcFlight() @@ -143,10 +154,11 @@ local function GetBulletIndex() return Index end +local CalcFlight = ACF.CalcBulletFlight local function IterateBullets() - for Index, Bullet in pairs(Bullets) do + for _, Bullet in pairs(Bullets) do if not Bullet.HandlesOwnIteration then - ACF.CalcBulletFlight(Index, Bullet) + CalcFlight(Bullet) end end end @@ -168,6 +180,7 @@ function ACF.CreateBullet(BulletData) Bullet.Mask = MASK_SOLID -- Note: MASK_SHOT removed for smaller projectiles as it ignores armor Bullet.Ricochets = 0 Bullet.GroundRicos = 0 + Bullet.Color = ColorRand(100, 255) if not next(Bullets) then hook.Add("Tick", "IterateBullets", IterateBullets) @@ -175,32 +188,59 @@ function ACF.CreateBullet(BulletData) Bullets[Index] = Bullet - ACF.BulletClient(Index, Bullet, "Init", 0) - ACF.CalcBulletFlight(Index, Bullet) + ACF.BulletClient(Bullet, "Init", 0) + ACF.CalcBulletFlight(Bullet) return Bullet end -function ACF.DoBulletsFlight(Index, Bullet) - if hook.Run("ACF_BulletsFlight", Index, Bullet) == false then return end +local function OnImpact(Bullet, Trace, Ammo, Type) + local Func = Type == "World" and Ammo.WorldImpact or Ammo.PropImpact + local Retry = Func(Ammo, Bullet, Trace) + + if Retry == "Penetrated" then + if Bullet.OnPenetrated then + Bullet.OnPenetrated(Bullet, Trace) + end + + ACF.BulletClient(Bullet, "Update", 2, Trace.HitPos) + ACF.DoBulletsFlight(Bullet) + elseif Retry == "Ricochet" then + if Bullet.OnRicocheted then + Bullet.OnRicocheted(Bullet, Trace) + end + + ACF.BulletClient(Bullet, "Update", 3, Trace.HitPos) + else + if Bullet.OnEndFlight then + Bullet.OnEndFlight(Bullet, Trace) + end + + ACF.BulletClient(Bullet, "Update", 1, Trace.HitPos) + + Ammo:OnFlightEnd(Bullet, Trace) + end +end + +function ACF.DoBulletsFlight(Bullet) + if HookRun("ACF Bullet Flight", Bullet) == false then return end if Bullet.SkyLvL then if ACF.CurTime - Bullet.LifeTime > 30 then - return ACF.RemoveBullet(Index) + return ACF.RemoveBullet(Bullet) end if Bullet.NextPos.z + ACF.SkyboxGraceZone > Bullet.SkyLvL then if Bullet.Fuze and Bullet.Fuze <= ACF.CurTime then -- Fuze detonated outside map - ACF.RemoveBullet(Index) + ACF.RemoveBullet(Bullet) end return elseif not util.IsInWorld(Bullet.NextPos) then - return ACF.RemoveBullet(Index) + return ACF.RemoveBullet(Bullet) else Bullet.SkyLvL = nil Bullet.LifeTime = nil - Bullet.SkipNextHit = true return end @@ -213,6 +253,7 @@ function ACF.DoBulletsFlight(Index, Bullet) local FlightRes, Filter = ACF.TraceF(FlightTr) -- Does not modify the bullet's original filter + debugoverlay.Line(Bullet.Pos, FlightRes.HitPos, 15, Bullet.Color) -- Something was hit, let's make sure we're not phasing through armor if Bullet.LastPos and IsValid(FlightRes.Entity) and not GlobalFilter[FlightRes.Entity:GetClass()] then BackTrace.start = Bullet.LastPos @@ -239,98 +280,285 @@ function ACF.DoBulletsFlight(Index, Bullet) Bullet.Filter = Filter end - local RoundData = ACF.RoundTypes[Bullet.Type] + local Ammo = AmmoTypes[Bullet.Type] if Bullet.Fuze and Bullet.Fuze <= ACF.CurTime then if not util.IsInWorld(Bullet.Pos) then -- Outside world, just delete - return ACF.RemoveBullet(Index) + return ACF.RemoveBullet(Bullet) else - if Bullet.OnEndFlight then - Bullet.OnEndFlight(Index, Bullet, nil) - end - local DeltaTime = Bullet.DeltaTime local DeltaFuze = ACF.CurTime - Bullet.Fuze local Lerp = DeltaFuze / DeltaTime - --print(DeltaTime, DeltaFuze, Lerp) - if FlightRes.Hit and Lerp < FlightRes.Fraction or true then -- Fuze went off before running into something - local Pos = LerpVector(DeltaFuze / DeltaTime, Bullet.Pos, Bullet.NextPos) - debugoverlay.Line(Bullet.Pos, Bullet.NextPos, 5, Color( 0, 255, 0 )) + if not FlightRes.Hit or Lerp < FlightRes.Fraction then -- Fuze went off before running into something + local Pos = LerpVector(Lerp, Bullet.Pos, Bullet.NextPos) + + if Bullet.OnEndFlight then + Bullet.OnEndFlight(Bullet, FlightRes) + end + + ACF.BulletClient(Bullet, "Update", 1, Pos) - ACF.BulletClient(Index, Bullet, "Update", 1, Pos) + Ammo:OnFlightEnd(Bullet, FlightRes) - RoundData.endflight(Index, Bullet, Pos, Bullet.Flight:GetNormalized()) + return end end end - if Bullet.SkipNextHit then - if not FlightRes.StartSolid and not FlightRes.HitNoDraw then - Bullet.SkipNextHit = nil + if FlightRes.Hit then + if FlightRes.HitSky then + if FlightRes.HitNormal == Vector(0, 0, -1) then + Bullet.SkyLvL = FlightRes.HitPos.z + Bullet.LifeTime = ACF.CurTime + else + ACF.RemoveBullet(Bullet) + end + else + local Type = (FlightRes.HitWorld or FlightRes.Entity:CPPIGetOwner() == game.GetWorld()) and "World" or "Prop" + + OnImpact(Bullet, FlightRes, Ammo, Type) end - elseif FlightRes.HitNonWorld and not GlobalFilter[FlightRes.Entity:GetClass()] then - local Retry = RoundData.propimpact(Index, Bullet, FlightRes.Entity, FlightRes.HitNormal, FlightRes.HitPos, FlightRes.HitGroup) + end +end - if Retry == "Penetrated" then - if Bullet.OnPenetrated then - Bullet.OnPenetrated(Index, Bullet, FlightRes) - end +do -- Terminal ballistics -------------------------- + local function RicochetVector(Flight, HitNormal) + local Vec = Flight:GetNormalized() - ACF.BulletClient(Index, Bullet, "Update", 2, FlightRes.HitPos) - ACF.DoBulletsFlight(Index, Bullet) - elseif Retry == "Ricochet" then - if Bullet.OnRicocheted then - Bullet.OnRicocheted(Index, Bullet, FlightRes) - end + return Vec - (2 * Vec:Dot(HitNormal)) * HitNormal + end - ACF.BulletClient(Index, Bullet, "Update", 3, FlightRes.HitPos) - ACF.CalcBulletFlight(Index, Bullet) - else - if Bullet.OnEndFlight then - Bullet.OnEndFlight(Index, Bullet, FlightRes) + function ACF_RoundImpact(Bullet, Speed, Energy, Target, HitPos, HitNormal, Bone) + local HitAngle = ACF_GetHitAngle(HitNormal, Bullet.Flight) + + local HitRes = ACF_Damage( -- DAMAGE!! + Target, + Energy, + Bullet.PenArea, + HitAngle, + Bullet.Owner, + Bone, + Bullet.Gun, + Bullet.Type + ) + + local Ricochet = 0 + if HitRes.Loss == 1 then + -- Ricochet distribution center + local sigmoidCenter = Bullet.DetonatorAngle or (Bullet.Ricochet - math.abs(Speed / 39.37 - Bullet.LimitVel) / 100) + + -- Ricochet probability (sigmoid distribution); up to 5% minimal ricochet probability for projectiles with caliber < 20 mm + local ricoProb = math.Clamp(1 / (1 + math.exp((HitAngle - sigmoidCenter) / -4)), math.max(-0.05 * (Bullet.Caliber - 2) / 2, 0), 1) + + -- Checking for ricochet + if ricoProb > math.random() and HitAngle < 90 then + Ricochet = math.Clamp(HitAngle / 90, 0.05, 1) -- atleast 5% of energy is kept + HitRes.Loss = 0.25 - Ricochet + Energy.Kinetic = Energy.Kinetic * HitRes.Loss end + end + + if ACF.KEPush then + ACF.KEShove( + Target, + HitPos, + Bullet.Flight:GetNormalized(), + Energy.Kinetic * HitRes.Loss * 1000 * Bullet.ShovePower + ) + end - ACF.BulletClient(Index, Bullet, "Update", 1, FlightRes.HitPos) + if HitRes.Kill then + local Debris = ACF_APKill(Target, Bullet.Flight:GetNormalized() , Energy.Kinetic) - RoundData.endflight(Index, Bullet, FlightRes.HitPos, FlightRes.HitNormal) + table.insert(Bullet.Filter , Debris) end - elseif FlightRes.HitWorld then - if not FlightRes.HitSky then - local Retry = RoundData.worldimpact(Index, Bullet, FlightRes.HitPos, FlightRes.HitNormal) - if Retry == "Penetrated" then - if Bullet.OnPenetrated then - Bullet.OnPenetrated(Index, Bullet, FlightRes) - end + HitRes.Ricochet = false - ACF.BulletClient(Index, Bullet, "Update", 2, FlightRes.HitPos) - ACF.CalcBulletFlight(Index, Bullet) - elseif Retry == "Ricochet" then - if Bullet.OnRicocheted then - Bullet.OnRicocheted(Index, Bullet, FlightRes) - end + if Ricochet > 0 and Bullet.Ricochets < 3 then + Bullet.Ricochets = Bullet.Ricochets + 1 + Bullet.NextPos = HitPos + Bullet.Flight = (RicochetVector(Bullet.Flight, HitNormal) + VectorRand() * 0.025):GetNormalized() * Speed * Ricochet - ACF.BulletClient(Index, Bullet, "Update", 3, FlightRes.HitPos) - ACF.CalcBulletFlight(Index, Bullet) - else - if Bullet.OnEndFlight then - Bullet.OnEndFlight(Index, Bullet, FlightRes) - end + HitRes.Ricochet = true + end + + return HitRes + end + + function ACF_Ricochet(Bullet, Trace) + local Ricochet = 0 + local Speed = Bullet.Flight:Length() / ACF.Scale + local HitAngle = ACF_GetHitAngle(Trace.HitNormal, Bullet.Flight) + local MinAngle = math.min(Bullet.Ricochet - Speed / 39.37 / 30 + 20,89.9) --Making the chance of a ricochet get higher as the speeds increase - ACF.BulletClient(Index, Bullet, "Update", 1, FlightRes.HitPos) + if HitAngle > math.random(MinAngle,90) and HitAngle < 89.9 then --Checking for ricochet + Ricochet = HitAngle / 90 * 0.75 + end + + if Ricochet > 0 and Bullet.GroundRicos < 2 then + Bullet.GroundRicos = Bullet.GroundRicos + 1 + Bullet.NextPos = Trace.HitPos + Bullet.Flight = (RicochetVector(Bullet.Flight, Trace.HitNormal) + VectorRand() * 0.05):GetNormalized() * Speed * Ricochet + + return "Ricochet" + end + + return false + end - RoundData.endflight(Index, Bullet, FlightRes.HitPos, FlightRes.HitNormal) + local function DigTrace(From, To, Filter) + local Dig = util.TraceHull({ + start = From, + endpos = To, + mask = MASK_NPCSOLID_BRUSHONLY, -- Map and brushes only + mins = Vector(), + maxs = Vector() + }) + + debugoverlay.Line(From, Dig.StartPos, 30, ColorRand(100, 255), true) + + if Dig.StartSolid then -- Started inside solid map volume + if Dig.FractionLeftSolid == 0 then -- Trace could not move inside + local Displacement = To - From + local Normal = Displacement:GetNormalized() + local Length = Displacement:Length() + + local C = math.Round(Length / 12) + local N = Length / C + + for I = 1, C do + local P = From + Normal * I * N + + local Back = util.TraceHull({ -- Send a trace backwards to hit the other side + start = P, + endpos = From, -- Countering the initial offset position of the dig trace to handle things <1 inch thick + mask = MASK_NPCSOLID_BRUSHONLY, -- Map and brushes only + mins = Vector(), + maxs = Vector() + }) + + if Back.StartSolid or Back.HitNoDraw then continue end + + return true, Back.HitPos + end + + return false + elseif Dig.FractionLeftSolid == 1 then -- Non-penetration: too thick + return false + else -- Penetrated + if Dig.HitNoDraw then -- Hit a layer inside + return DigTrace(Dig.HitPos + (To - From):GetNormalized() * 0.1, To, Filter) -- Try again + else -- Complete penetration + local Back = util.TraceHull({ + start = Dig.StartPos, + endpos = From, + mask = MASK_NPCSOLID_BRUSHONLY, -- Map and brushes only + mins = Vector(), + maxs = Vector() + }) + + -- False positive, still inside the world + -- Typically occurs when two brushes meet + if Back.StartSolid or Back.HitNoDraw then + return DigTrace(Dig.StartPos + (To - From):GetNormalized() * 0.1, To, Filter) + end + + return true, Dig.StartPos + end end - else - if FlightRes.HitNormal == Vector(0, 0, -1) then - Bullet.SkyLvL = FlightRes.HitPos.z - Bullet.LifeTime = ACF.CurTime - else - ACF.RemoveBullet(Index) + else -- Started inside a brush + local Back = util.TraceHull({ -- Send a trace backwards to hit the other side + start = Dig.HitPos, + endpos = From + (From - Dig.HitPos):GetNormalized(), -- Countering the initial offset position of the dig trace to handle things <1 inch thick + mask = MASK_NPCSOLID_BRUSHONLY, -- Map and brushes only + mins = Vector(), + maxs = Vector() + }) + + if Back.StartSolid then -- object is too thick + return false + elseif not Back.Hit or Back.HitNoDraw then + -- Hit nothing on the way back + -- Map edge, going into the ground, whatever... + -- Effectively infinitely thick + + return false + else -- Penetration + return true, Back.HitPos end end end + + function ACF_PenetrateMapEntity(Bullet, Trace) + local Energy = ACF_Kinetic(Bullet.Flight:Length() / ACF.Scale, Bullet.ProjMass, Bullet.LimitVel) + local Surface = util.GetSurfaceData(Trace.SurfaceProps) + local Density = ((Surface and Surface.density * 0.5 or 500) * math.Rand(0.9, 1.1)) ^ 0.9 / 10000 + local Pen = Energy.Penetration / Bullet.PenArea * ACF.KEtoRHA -- Base RHA penetration of the projectile + local RHAe = math.max(Pen / Density, 1) -- RHA equivalent thickness of the target material + + local Enter = Trace.HitPos -- Impact point + local Fwd = Bullet.Flight:GetNormalized() + + local PassThrough = util.TraceHull({ + start = Enter, + endpos = Enter + Fwd * RHAe / 25.4, + filter = {Trace.Entity}, + mask = MASK_SOLID_BRUSHONLY + }) + + local Filt = {} + local Back + + repeat + Back = util.TraceHull({ + start = PassThrough.HitPos, + endpos = Enter, + filter = Filt + }) + + if Back.HitNonWorld and Back.Entity ~= Trace.Entity then + Filt[#Filt + 1] = Back.Entity + continue + end + + if Back.StartSolid then return ACF_Ricochet(Bullet, Trace) end + until Back.Entity == Trace.Entity + + local Thicc = (Back.HitPos - Enter):Length() * Density * 25.4 -- Obstacle thickness in RHA + + Bullet.Flight = Bullet.Flight * (1 - Thicc / Pen) + Bullet.Pos = Back.HitPos + Fwd * 0.25 + + return "Penetrated" + end + + function ACF_PenetrateGround(Bullet, Trace) + local Energy = ACF_Kinetic(Bullet.Flight:Length() / ACF.Scale, Bullet.ProjMass, Bullet.LimitVel) + local Surface = util.GetSurfaceData(Trace.SurfaceProps) + local Density = ((Surface and Surface.density * 0.5 or 500) * math.Rand(0.9, 1.1)) ^ 0.9 / 10000 + local Pen = Energy.Penetration / Bullet.PenArea * ACF.KEtoRHA -- Base RHA penetration of the projectile + local RHAe = math.max(Pen / Density, 1) -- RHA equivalent thickness of the target material + + local Enter = Trace.HitPos -- Impact point + local Fwd = Bullet.Flight:GetNormalized() + + local Penetrated, Exit = DigTrace(Enter + Fwd, Enter + Fwd * RHAe / 25.4) + + if Penetrated then + local Thicc = (Exit - Enter):Length() * Density * 25.4 -- RHAe of the material passed through + local DeltaTime = engine.TickInterval() + + Bullet.Flight = Bullet.Flight * (1 - Thicc / Pen) + Bullet.Pos = Exit + Fwd * 0.25 + Bullet.NextPos = Exit + Bullet.Flight * ACF.Scale * DeltaTime + + return "Penetrated" + else -- Ricochet + return ACF_Ricochet(Bullet, Trace) + end + end end -- Backwards compatibility diff --git a/lua/acf/server/damage.lua b/lua/acf/server/damage.lua index e5e0b90ab..44ec79623 100644 --- a/lua/acf/server/damage.lua +++ b/lua/acf/server/damage.lua @@ -1,25 +1,23 @@ -- Local Vars ----------------------------------- -local ACF_HEPUSH = GetConVar("acf_hepush") -local ACF_KEPUSH = GetConVar("acf_kepush") -local TimerCreate = timer.Create -local TraceRes = {} -local TraceData = { output = TraceRes, mask = MASK_SOLID, filter = false } -local Check = ACF_Check -local HookRun = hook.Run -local Trace = ACF.TraceF -local ValidDebris = ACF.ValidDebris -local ChildDebris = ACF.ChildDebris -local DragDiv = ACF.DragDiv -local GlobalFilter = ACF.GlobalFilter +local ACF = ACF +local TimerCreate = timer.Create +local TraceRes = {} +local TraceData = { output = TraceRes, mask = MASK_SOLID, filter = false } +local HookRun = hook.Run +local Trace = ACF.TraceF +local ValidDebris = ACF.ValidDebris +local ChildDebris = ACF.ChildDebris +local DragDiv = ACF.DragDiv + -- Local Funcs ---------------------------------- local function CalcDamage(Entity, Energy, FrArea, Angle) + local FinalAngle = math.Clamp(Angle, -90, 90) -- TODO: Why are we getting impact angles outside these bounds? local armor = Entity.ACF.Armour -- Armor - local losArmor = armor / math.abs(math.cos(math.rad(Angle)) ^ ACF.SlopeEffectFactor) -- LOS Armor + local losArmor = armor / math.abs(math.cos(math.rad(FinalAngle)) ^ ACF.SlopeEffectFactor) -- LOS Armor local maxPenetration = (Energy.Penetration / FrArea) * ACF.KEtoRHA --RHA Penetration local HitRes = {} - -- Projectile caliber. Messy, function signature local caliber = 20 * (FrArea ^ (1 / ACF.PenAreaMod) / 3.1416) ^ 0.5 -- Breach probability @@ -39,7 +37,7 @@ local function CalcDamage(Entity, Energy, FrArea, Angle) local Penetration = math.min(maxPenetration, losArmor) HitRes.Damage = (Penetration / losArmor) ^ 2 * FrArea HitRes.Overkill = (maxPenetration - Penetration) - HitRes.Loss = Penetration / maxPenetration + HitRes.Loss = Penetration / math.max(0.001, maxPenetration) return HitRes end @@ -72,703 +70,612 @@ local function Shove(Target, Pos, Vec, KE) end ACF.KEShove = Shove -------------------------------------------------- -do - do -- Squishy tracking - ACF.Squishies = ACF.Squishies or {} +------------------------------------------------- - local Squishies = ACF.Squishies +do -- Player syncronization + util.AddNetworkString("ACF_RenderDamage") - hook.Add("PlayerSpawnedNPC", "ACF Squishies", function(_, Ent) - Squishies[Ent] = true - end) + hook.Add("ACF_OnPlayerLoaded", "ACF Render Damage", function(ply) + local Table = {} - hook.Add("OnNPCKilled", "ACF Squishies", function(Ent) - Squishies[Ent] = nil - end) + for _, v in pairs(ents.GetAll()) do + if v.ACF and v.ACF.PrHealth then + table.insert(Table, { + ID = v:EntIndex(), + Health = v.ACF.Health, + MaxHealth = v.ACF.MaxHealth + }) + end + end - hook.Add("PlayerSpawn", "ACF Squishies", function(Ent) - Squishies[Ent] = true - end) + if next(Table) then + net.Start("ACF_RenderDamage") + net.WriteTable(Table) + net.Send(ply) + end + end) +end - hook.Add("PostPlayerDeath", "ACF Squishies", function(Ent) - Squishies[Ent] = nil - end) +do -- Explosions ---------------------------- + local function GetRandomPos(Entity, IsChar) + if IsChar then + local Mins, Maxs = Entity:OBBMins() * 0.65, Entity:OBBMaxs() * 0.65 -- Scale down the "hitbox" since most of the character is in the middle + local Rand = Vector(math.Rand(Mins[1], Maxs[1]), math.Rand(Mins[2], Maxs[2]), math.Rand(Mins[3], Maxs[3])) - hook.Add("EntityRemoved", "ACF Squishies", function(Ent) - Squishies[Ent] = nil - end) - end + return Entity:LocalToWorld(Rand) + else + local Mesh = Entity:GetPhysicsObject():GetMesh() - do -- Explosions ---------------------------- - local function GetRandomPos(Entity, IsChar) - if IsChar then - local Mins, Maxs = Entity:OBBMins() * 0.65, Entity:OBBMaxs() * 0.65 -- Scale down the "hitbox" since most of the character is in the middle + if not Mesh then -- Is Make-Sphericaled + local Mins, Maxs = Entity:OBBMins(), Entity:OBBMaxs() local Rand = Vector(math.Rand(Mins[1], Maxs[1]), math.Rand(Mins[2], Maxs[2]), math.Rand(Mins[3], Maxs[3])) - return Entity:LocalToWorld(Rand) + return Entity:LocalToWorld(Rand:GetNormalized() * math.Rand(1, Entity:BoundingRadius() * 0.5)) -- Attempt to a random point in the sphere else - local Mesh = Entity:GetPhysicsObject():GetMesh() + local Rand = math.random(3, #Mesh / 3) * 3 + local P = Vector(0, 0, 0) - if not Mesh then -- Is Make-Sphericaled - local Mins, Maxs = Entity:OBBMins(), Entity:OBBMaxs() - local Rand = Vector(math.Rand(Mins[1], Maxs[1]), math.Rand(Mins[2], Maxs[2]), math.Rand(Mins[3], Maxs[3])) - - return Entity:LocalToWorld(Rand:GetNormalized() * math.Rand(1, Entity:BoundingRadius() * 0.5)) -- Attempt to a random point in the sphere - else - local Rand = math.random(3, #Mesh / 3) * 3 - local P = Vector(0, 0, 0) + for I = Rand - 2, Rand do P = P + Mesh[I].pos end - for I = Rand - 2, Rand do P = P + Mesh[I].pos end - - return Entity:LocalToWorld(P / 3) -- Attempt to hit a point on a face of the mesh - end + return Entity:LocalToWorld(P / 3) -- Attempt to hit a point on a face of the mesh end end + end - local function BackCheck( MaxD, Last) -- Return the last entity hit - Trace(TraceData) - - -- Hit an entity going backwards thats between the origin and the original hitpos (phased through) - if TraceRes.HitNonWorld and IsValid(TraceRes.Entity) and not GlobalFilter[TraceRes.Entity:GetClass()] and TraceRes.HitPos:DistToSqr(TraceData.start) < MaxD then - Last = TraceRes.Entity + function ACF.HE(Origin, FillerMass, FragMass, Inflictor, Filter, Gun) + debugoverlay.Cross(Origin, 15, 15, Color( 255, 255, 255 ), true) + Filter = Filter or {} - TraceData.filter[#TraceData.filter + 1] = Last + local Power = FillerMass * ACF.HEPower --Power in KiloJoules of the filler mass of TNT + local Radius = FillerMass ^ 0.33 * 8 * 39.37 -- Scaling law found on the net, based on 1PSI overpressure from 1 kg of TNT at 15m + local MaxSphere = 4 * 3.1415 * (Radius * 2.54) ^ 2 --Surface Area of the sphere at maximum radius + local Amp = math.min(Power / 2000, 50) + local Fragments = math.max(math.floor((FillerMass / FragMass) * ACF.HEFrag), 2) + local FragWeight = FragMass / Fragments + local BaseFragV = (Power * 50000 / FragWeight / Fragments) ^ 0.5 + local FragArea = (FragWeight / 7.8) ^ 0.33 + local Damaged = {} + local Ents = ents.FindInSphere(Origin, Radius) + local Loop = true -- Find more props to damage whenever a prop dies - BackCheck(MaxD, Last) - end + TraceData.filter = Filter + TraceData.start = Origin - return Last - end + util.ScreenShake(Origin, Amp, Amp, Amp / 15, Radius * 10) - local function BackCheckInit() + while Loop and Power > 0 do + Loop = false - local OStart = TraceData.start - local OEnd = TraceData.endpos - local OEnt = TraceRes.Entity - local OFilter = {}; for K, V in pairs(TraceData.filter) do OFilter[K] = V end + local PowerSpent = 0 + local Damage = {} - TraceData.start = TraceRes.HitPos - TraceData.endpos = TraceRes.HitPos + (OStart - TraceRes.HitPos):GetNormalized() * 1000 + for K, Ent in ipairs(Ents) do -- Find entities to deal damage to + if not ACF.Check(Ent) then -- Entity is not valid to ACF - local R = BackCheck(TraceRes.HitPos:DistToSqr(OStart)) + Ents[K] = nil -- Remove from list + Filter[#Filter + 1] = Ent -- Filter from traces - if IsValid(R) then - TraceRes.Entity = R - --print("THANK YOU GARRY VERY COOL", OEnt, TraceRes.Entity) - else - TraceRes.Entity = OEnt - end + continue + end - TraceData.start = OStart - TraceData.endpos = OEnd - TraceData.filter = OFilter - end + if Damage[Ent] then continue end -- A trace sent towards another prop already hit this one instead, no need to check if we can see it - function ACF_HE(Origin, FillerMass, FragMass, Inflictor, Filter, Gun) - debugoverlay.Cross(Origin, 15, 15, Color( 255, 255, 255 ), true) - Filter = Filter or {} + if Ent.Exploding then -- Detonate explody things immediately if they're already cooking off + Ents[K] = nil + Filter[#Filter + 1] = Ent - local Power = FillerMass * ACF.HEPower --Power in KiloJoules of the filler mass of TNT - local Radius = FillerMass ^ 0.33 * 8 * 39.37 -- Scaling law found on the net, based on 1PSI overpressure from 1 kg of TNT at 15m - local MaxSphere = 4 * 3.1415 * (Radius * 2.54) ^ 2 --Surface Area of the sphere at maximum radius - local Amp = math.min(Power / 2000, 50) - local Fragments = math.max(math.floor((FillerMass / FragMass) * ACF.HEFrag), 2) - local FragWeight = FragMass / Fragments - local BaseFragV = (Power * 50000 / FragWeight / Fragments) ^ 0.5 - local FragArea = (FragWeight / 7.8) ^ 0.33 - local Damaged = {} - local Ents = ents.FindInSphere(Origin, Radius) - local Loop = true -- Find more props to damage whenever a prop dies + --Ent:Detonate() + continue + end - TraceData.filter = Filter - TraceData.start = Origin + local IsChar = Ent:IsPlayer() or Ent:IsNPC() + if IsChar and Ent:Health() <= 0 then + Ents[K] = nil + Filter[#Filter + 1] = Ent -- Shouldn't need to filter a dead player but we'll do it just in case - util.ScreenShake(Origin, Amp, Amp, Amp / 15, Radius * 10) + continue + end - while Loop and Power > 0 do - Loop = false + local Target = GetRandomPos(Ent, IsChar) -- Try to hit a random spot on the entity + local Displ = Target - Origin - local PowerSpent = 0 - local Damage = {} + TraceData.endpos = Origin + Displ:GetNormalized() * (Displ:Length() + 24) + Trace(TraceData) -- Outputs to TraceRes - for K, Ent in ipairs(Ents) do -- Find entities to deal damage to - if not Check(Ent) then -- Entity is not valid to ACF + if TraceRes.HitNonWorld then + Ent = TraceRes.Entity - Ents[K] = nil -- Remove from list - Filter[#Filter + 1] = Ent -- Filter from traces + if ACF.Check(Ent) then + if not Ent.Exploding and not Damage[Ent] and not Damaged[Ent] then -- Hit an entity that we haven't already damaged yet (Note: Damaged != Damage) + local Mul = IsChar and 0.65 or 1 -- Scale down boxes for players/NPCs because the bounding box is way bigger than they actually are - continue - end + debugoverlay.Line(Origin, TraceRes.HitPos, 30, Color(0, 255, 0), true) -- Green line for a hit trace + debugoverlay.BoxAngles(Ent:GetPos(), Ent:OBBMins() * Mul, Ent:OBBMaxs() * Mul, Ent:GetAngles(), 30, Color(255, 0, 0, 1)) - if Damage[Ent] then continue end -- A trace sent towards another prop already hit this one instead, no need to check if we can see it + local Pos = Ent:GetPos() + local Distance = Origin:Distance(Pos) + local Sphere = math.max(4 * 3.1415 * (Distance * 2.54) ^ 2, 1) -- Surface Area of the sphere at the range of that prop + local Area = math.min(Ent.ACF.Area / Sphere, 0.5) * MaxSphere -- Project the Area of the prop to the Area of the shadow it projects at the explosion max radius - if Ent.Exploding then -- Detonate explody things immediately if they're already cooking off + Damage[Ent] = { + Dist = Distance, + Vec = (Pos - Origin):GetNormalized(), + Area = Area, + Index = K + } - Ents[K] = nil - Filter[#Filter + 1] = Ent + Ents[K] = nil -- Removed from future damage searches (but may still block LOS) + end + else -- If check on new ent fails + --debugoverlay.Line(Origin, TraceRes.HitPos, 30, Color(255, 0, 0)) -- Red line for a invalid ent - --Ent:Detonate() - continue + Ents[K] = nil -- Remove from list + Filter[#Filter + 1] = Ent -- Filter from traces end + else + -- Not removed from future damage sweeps so as to provide multiple chances to be hit + debugoverlay.Line(Origin, TraceRes.HitPos, 30, Color(0, 0, 255)) -- Blue line for a miss + end + end - local IsChar = Ent:IsPlayer() or Ent:IsNPC() - if IsChar and Ent:Health() <= 0 then - Ents[K] = nil - Filter[#Filter + 1] = Ent -- Shouldn't need to filter a dead player but we'll do it just in case + for Ent, Table in pairs(Damage) do -- Deal damage to the entities we found + local Feathering = (1 - math.min(1, Table.Dist / Radius)) ^ ACF.HEFeatherExp + local AreaFraction = Table.Area / MaxSphere + local PowerFraction = Power * AreaFraction -- How much of the total power goes to that prop + local AreaAdjusted = (Ent.ACF.Area / ACF.Threshold) * Feathering + local Blast = { Penetration = PowerFraction ^ ACF.HEBlastPen * AreaAdjusted } + local BlastRes = ACF.Damage(Ent, Blast, AreaAdjusted, 0, Inflictor, 0, Gun, "HE") + local FragHit = math.floor(Fragments * AreaFraction) + local FragVel = math.max(BaseFragV - ((Table.Dist / BaseFragV) * BaseFragV ^ 2 * FragWeight ^ 0.33 / 10000) / DragDiv, 0) + local FragKE = ACF_Kinetic(FragVel, FragWeight * FragHit, 1500) + local Losses = BlastRes.Loss * 0.5 + local FragRes + + if FragHit > 0 then + FragRes = ACF.Damage(Ent, FragKE, FragArea * FragHit, 0, Inflictor, 0, Gun, "Frag") + Losses = Losses + FragRes.Loss * 0.5 + end - continue - end + if (BlastRes and BlastRes.Kill) or (FragRes and FragRes.Kill) then -- We killed something + Filter[#Filter + 1] = Ent -- Filter out the dead prop + Ents[Table.Index] = nil -- Don't bother looking for it in the future - local Target = GetRandomPos(Ent, IsChar) -- Try to hit a random spot on the entity - local Displ = Target - Origin + local Debris = ACF.HEKill(Ent, Table.Vec, PowerFraction, Origin) -- Make some debris - TraceData.endpos = Origin + Displ:GetNormalized() * (Displ:Length() + 24) - Trace(TraceData) -- Outputs to TraceRes + for Fireball in pairs(Debris) do + if IsValid(Fireball) then Filter[#Filter + 1] = Fireball end -- Filter that out too + end - if TraceRes.HitNonWorld then + Loop = true -- Check for new targets since something died, maybe we'll find something new + elseif ACF.HEPush then -- Just damaged, not killed, so push on it some + Shove(Ent, Origin, Table.Vec, PowerFraction * 33.3) -- Assuming about 1/30th of the explosive energy goes to propelling the target prop (Power in KJ * 1000 to get J then divided by 33) + end - BackCheckInit() + PowerSpent = PowerSpent + PowerFraction * Losses -- Removing the energy spent killing props + Damaged[Ent] = true -- This entity can no longer recieve damage from this explosion + end - Ent = TraceRes.Entity + Power = math.max(Power - PowerSpent, 0) + end + end - if Check(Ent) then - if not Ent.Exploding and not Damage[Ent] and not Damaged[Ent] then -- Hit an entity that we haven't already damaged yet (Note: Damaged != Damage) - local Mul = IsChar and 0.65 or 1 -- Scale down boxes for players/NPCs because the bounding box is way bigger than they actually are + ACF_HE = ACF.HE +end ----------------------------------------- - debugoverlay.Line(Origin, TraceRes.HitPos, 30, Color(0, 255, 0), true) -- Green line for a hit trace - debugoverlay.BoxAngles(Ent:GetPos(), Ent:OBBMins() * Mul, Ent:OBBMaxs() * Mul, Ent:GetAngles(), 30, Color(255, 0, 0, 1)) +do -- Overpressure -------------------------- + ACF.Squishies = ACF.Squishies or {} - local Pos = Ent:GetPos() - local Distance = Origin:Distance(Pos) - local Sphere = math.max(4 * 3.1415 * (Distance * 2.54) ^ 2, 1) -- Surface Area of the sphere at the range of that prop - local Area = math.min(Ent.ACF.Area / Sphere, 0.5) * MaxSphere -- Project the Area of the prop to the Area of the shadow it projects at the explosion max radius + local Squishies = ACF.Squishies - Damage[Ent] = { - Dist = Distance, - Vec = (Pos - Origin):GetNormalized(), - Area = Area, - Index = K - } + local function CanSee(Target, Data) + local R = Trace(Data) - Ents[K] = nil -- Removed from future damage searches (but may still block LOS) - end - else -- If check on new ent fails - --debugoverlay.Line(Origin, TraceRes.HitPos, 30, Color(255, 0, 0)) -- Red line for a invalid ent + return R.Entity == Target or not R.Hit or (Target:InVehicle() and R.Entity == Target:GetVehicle()) + end - Ents[K] = nil -- Remove from list - Filter[#Filter + 1] = Ent -- Filter from traces - end - else - -- Not removed from future damage sweeps so as to provide multiple chances to be hit - debugoverlay.Line(Origin, TraceRes.HitPos, 30, Color(0, 0, 255)) -- Blue line for a miss - end - end + hook.Add("PlayerSpawnedNPC", "ACF Squishies", function(_, Ent) + Squishies[Ent] = true + end) - for Ent, Table in pairs(Damage) do -- Deal damage to the entities we found - local Feathering = (1 - math.min(1, Table.Dist / Radius)) ^ ACF.HEFeatherExp - local AreaFraction = Table.Area / MaxSphere - local PowerFraction = Power * AreaFraction -- How much of the total power goes to that prop - local AreaAdjusted = (Ent.ACF.Area / ACF.Threshold) * Feathering - local Blast = { Penetration = PowerFraction ^ ACF.HEBlastPen * AreaAdjusted } - local BlastRes = ACF_Damage(Ent, Blast, AreaAdjusted, 0, Inflictor, 0, Gun, "HE") - local FragHit = math.floor(Fragments * AreaFraction) - local FragVel = math.max(BaseFragV - ((Table.Dist / BaseFragV) * BaseFragV ^ 2 * FragWeight ^ 0.33 / 10000) / DragDiv, 0) - local FragKE = ACF_Kinetic(FragVel, FragWeight * FragHit, 1500) - local Losses = BlastRes.Loss * 0.5 - local FragRes - - if FragHit > 0 then - FragRes = ACF_Damage(Ent, FragKE, FragArea * FragHit, 0, Inflictor, 0, Gun, "Frag") - Losses = Losses + FragRes.Loss * 0.5 - end + hook.Add("OnNPCKilled", "ACF Squishies", function(Ent) + Squishies[Ent] = nil + end) - if (BlastRes and BlastRes.Kill) or (FragRes and FragRes.Kill) then -- We killed something - Filter[#Filter + 1] = Ent -- Filter out the dead prop - Ents[Table.Index] = nil -- Don't bother looking for it in the future + hook.Add("PlayerSpawn", "ACF Squishies", function(Ent) + Squishies[Ent] = true + end) - local Debris = ACF_HEKill(Ent, Table.Vec, PowerFraction, Origin) -- Make some debris + hook.Add("PostPlayerDeath", "ACF Squishies", function(Ent) + Squishies[Ent] = nil + end) - if IsValid(Debris) then - Filter[#Filter + 1] = Debris -- Filter that out too - end + hook.Add("EntityRemoved", "ACF Squishies", function(Ent) + Squishies[Ent] = nil + end) - Loop = true -- Check for new targets since something died, maybe we'll find something new - elseif ACF_HEPUSH:GetBool() then -- Just damaged, not killed, so push on it some - Shove(Ent, Origin, Table.Vec, PowerFraction * 33.3) -- Assuming about 1/30th of the explosive energy goes to propelling the target prop (Power in KJ * 1000 to get J then divided by 33) - end + function ACF.Overpressure(Origin, Energy, Inflictor, Source, Forward, Angle) + local Radius = Energy ^ 0.33 * 0.025 * 39.37 -- Radius in meters (Completely arbitrary stuff, scaled to have 120s have a radius of about 20m) + local Data = { start = Origin, endpos = true, mask = MASK_SHOT } - PowerSpent = PowerSpent + PowerFraction * Losses -- Removing the energy spent killing props - Damaged[Ent] = true -- This entity can no longer recieve damage from this explosion - end + if Source then -- Filter out guns + if Source.BarrelFilter then + Data.filter = {} - Power = math.max(Power - PowerSpent, 0) + for K, V in pairs(Source.BarrelFilter) do Data.filter[K] = V end -- Quick copy of gun barrel filter + else + Data.filter = { Source } end end - local function CanSee(Target, Data) - local R = ACF.Trace(Data) + util.ScreenShake(Origin, Energy, 1, 0.25, Radius * 3 * 39.37 ) - return R.Entity == Target or not R.Hit or (Target:InVehicle() and R.Entity == Target:GetVehicle()) - end + if Forward and Angle then -- Blast direction and angle are specified + Angle = math.rad(Angle * 0.5) -- Convert deg to rads - function ACF.Overpressure(Origin, Energy, Inflictor, Source, Forward, Angle) - local Radius = Energy ^ 0.33 * 0.025 * 39.37 -- Radius in meters (Completely arbitrary stuff, scaled to have 120s have a radius of about 20m) - local Data = { start = Origin, endpos = true, mask = MASK_SHOT } + for V in pairs(ACF.Squishies) do + if math.acos(Forward:Dot((V:GetShootPos() - Origin):GetNormalized())) < Angle then + local D = V:GetShootPos():Distance(Origin) - if Source then -- Filter out guns - if Source.BarrelFilter then - Data.filter = {} + if D / 39.37 <= Radius then - for K, V in pairs(Source.BarrelFilter) do Data.filter[K] = V end -- Quick copy of gun barrel filter - else - Data.filter = { Source } - end - end - - util.ScreenShake(Origin, Energy, 1, 0.25, Radius * 3 * 39.37 ) - - if Forward and Angle then -- Blast direction and angle are specified - Angle = math.rad(Angle * 0.5) -- Convert deg to rads + Data.endpos = V:GetShootPos() + VectorRand() * 5 - for V in pairs(ACF.Squishies) do - if math.acos(Forward:Dot((V:GetShootPos() - Origin):GetNormalized())) < Angle then - local D = V:GetShootPos():Distance(Origin) + if CanSee(V, Data) then + local Damage = Energy * 175000 * (1 / D^3) - if D / 39.37 <= Radius then - - Data.endpos = V:GetShootPos() + VectorRand() * 5 - - if CanSee(V, Data) then - local Damage = Energy * 175000 * (1 / D^3) - - V:TakeDamage(Damage, Inflictor, Source) - end + V:TakeDamage(Damage, Inflictor, Source) end end end - else -- Spherical blast - for V in pairs(ACF.Squishies) do - if CanSee(Origin, V) then - local D = V:GetShootPos():Distance(Origin) + end + else -- Spherical blast + for V in pairs(ACF.Squishies) do + if CanSee(Origin, V) then + local D = V:GetShootPos():Distance(Origin) - if D / 39.37 <= Radius then + if D / 39.37 <= Radius then - Data.endpos = V:GetShootPos() + VectorRand() * 5 + Data.endpos = V:GetShootPos() + VectorRand() * 5 - if CanSee(V, Data) then - local Damage = Energy * 150000 * (1 / D^3) + if CanSee(V, Data) then + local Damage = Energy * 150000 * (1 / D^3) - V:TakeDamage(Damage, Inflictor, Source) - end + V:TakeDamage(Damage, Inflictor, Source) end end end end end end - - do -- Deal Damage --------------------------- - local function SquishyDamage(Entity, Energy, FrArea, Angle, Inflictor, Bone, Gun) - local Size = Entity:BoundingRadius() - local Mass = Entity:GetPhysicsObject():GetMass() - local HitRes = {} - local Damage = 0 - - --We create a dummy table to pass armour values to the calc function - local Target = { - ACF = { - Armour = 0.1 - } +end ----------------------------------------- + +do -- Deal Damage --------------------------- + local function SquishyDamage(Entity, Energy, FrArea, Angle, Inflictor, Bone, Gun) + local Size = Entity:BoundingRadius() + local Mass = Entity:GetPhysicsObject():GetMass() + local HitRes = {} + local Damage = 0 + + --We create a dummy table to pass armour values to the calc function + local Target = { + ACF = { + Armour = 0.1 } + } + + if Bone then + --This means we hit the head + if Bone == 1 then + Target.ACF.Armour = Mass * 0.02 --Set the skull thickness as a percentage of Squishy weight, this gives us 2mm for a player, about 22mm for an Antlion Guard. Seems about right + HitRes = CalcDamage(Target, Energy, FrArea, Angle) --This is hard bone, so still sensitive to impact angle + Damage = HitRes.Damage * 20 + + --If we manage to penetrate the skull, then MASSIVE DAMAGE + if HitRes.Overkill > 0 then + Target.ACF.Armour = Size * 0.25 * 0.01 --A quarter the bounding radius seems about right for most critters head size + HitRes = CalcDamage(Target, Energy, FrArea, 0) + Damage = Damage + HitRes.Damage * 100 + end - if (Bone) then - --This means we hit the head - if (Bone == 1) then - Target.ACF.Armour = Mass * 0.02 --Set the skull thickness as a percentage of Squishy weight, this gives us 2mm for a player, about 22mm for an Antlion Guard. Seems about right - HitRes = CalcDamage(Target, Energy, FrArea, Angle) --This is hard bone, so still sensitive to impact angle - Damage = HitRes.Damage * 20 - - --If we manage to penetrate the skull, then MASSIVE DAMAGE - if HitRes.Overkill > 0 then - Target.ACF.Armour = Size * 0.25 * 0.01 --A quarter the bounding radius seems about right for most critters head size - HitRes = CalcDamage(Target, Energy, FrArea, 0) - Damage = Damage + HitRes.Damage * 100 - end - - Target.ACF.Armour = Mass * 0.065 --Then to check if we can get out of the other side, 2x skull + 1x brains - HitRes = CalcDamage(Target, Energy, FrArea, Angle) - Damage = Damage + HitRes.Damage * 20 - elseif (Bone == 0 or Bone == 2 or Bone == 3) then - --This means we hit the torso. We are assuming body armour/tough exoskeleton/zombie don't give fuck here, so it's tough - Target.ACF.Armour = Mass * 0.08 --Set the armour thickness as a percentage of Squishy weight, this gives us 8mm for a player, about 90mm for an Antlion Guard. Seems about right - HitRes = CalcDamage(Target, Energy, FrArea, Angle) --Armour plate,, so sensitive to impact angle - Damage = HitRes.Damage * 5 - - if HitRes.Overkill > 0 then - Target.ACF.Armour = Size * 0.5 * 0.02 --Half the bounding radius seems about right for most critters torso size - HitRes = CalcDamage(Target, Energy, FrArea, 0) - Damage = Damage + HitRes.Damage * 50 --If we penetrate the armour then we get into the important bits inside, so DAMAGE - end - - Target.ACF.Armour = Mass * 0.185 --Then to check if we can get out of the other side, 2x armour + 1x guts - HitRes = CalcDamage(Target, Energy, FrArea, Angle) - elseif (Bone == 4 or Bone == 5) then - --This means we hit an arm or appendage, so ormal damage, no armour - Target.ACF.Armour = Size * 0.2 * 0.02 --A fitht the bounding radius seems about right for most critters appendages - HitRes = CalcDamage(Target, Energy, FrArea, 0) --This is flesh, angle doesn't matter - Damage = HitRes.Damage * 30 --Limbs are somewhat less important - elseif (Bone == 6 or Bone == 7) then - Target.ACF.Armour = Size * 0.2 * 0.02 --A fitht the bounding radius seems about right for most critters appendages - HitRes = CalcDamage(Target, Energy, FrArea, 0) --This is flesh, angle doesn't matter - Damage = HitRes.Damage * 30 --Limbs are somewhat less important - elseif (Bone == 10) then - --This means we hit a backpack or something - Target.ACF.Armour = Size * 0.1 * 0.02 --Arbitrary size, most of the gear carried is pretty small - HitRes = CalcDamage(Target, Energy, FrArea, 0) --This is random junk, angle doesn't matter - Damage = HitRes.Damage * 2 --Damage is going to be fright and shrapnel, nothing much - else --Just in case we hit something not standard - Target.ACF.Armour = Size * 0.2 * 0.02 + Target.ACF.Armour = Mass * 0.065 --Then to check if we can get out of the other side, 2x skull + 1x brains + HitRes = CalcDamage(Target, Energy, FrArea, Angle) + Damage = Damage + HitRes.Damage * 20 + elseif Bone == 0 or Bone == 2 or Bone == 3 then + --This means we hit the torso. We are assuming body armour/tough exoskeleton/zombie don't give fuck here, so it's tough + Target.ACF.Armour = Mass * 0.08 --Set the armour thickness as a percentage of Squishy weight, this gives us 8mm for a player, about 90mm for an Antlion Guard. Seems about right + HitRes = CalcDamage(Target, Energy, FrArea, Angle) --Armour plate,, so sensitive to impact angle + Damage = HitRes.Damage * 5 + + if HitRes.Overkill > 0 then + Target.ACF.Armour = Size * 0.5 * 0.02 --Half the bounding radius seems about right for most critters torso size HitRes = CalcDamage(Target, Energy, FrArea, 0) - Damage = HitRes.Damage * 30 + Damage = Damage + HitRes.Damage * 50 --If we penetrate the armour then we get into the important bits inside, so DAMAGE end + + Target.ACF.Armour = Mass * 0.185 --Then to check if we can get out of the other side, 2x armour + 1x guts + HitRes = CalcDamage(Target, Energy, FrArea, Angle) + elseif Bone == 4 or Bone == 5 then + --This means we hit an arm or appendage, so ormal damage, no armour + Target.ACF.Armour = Size * 0.2 * 0.02 --A fitht the bounding radius seems about right for most critters appendages + HitRes = CalcDamage(Target, Energy, FrArea, 0) --This is flesh, angle doesn't matter + Damage = HitRes.Damage * 30 --Limbs are somewhat less important + elseif Bone == 6 or Bone == 7 then + Target.ACF.Armour = Size * 0.2 * 0.02 --A fitht the bounding radius seems about right for most critters appendages + HitRes = CalcDamage(Target, Energy, FrArea, 0) --This is flesh, angle doesn't matter + Damage = HitRes.Damage * 30 --Limbs are somewhat less important + elseif (Bone == 10) then + --This means we hit a backpack or something + Target.ACF.Armour = Size * 0.1 * 0.02 --Arbitrary size, most of the gear carried is pretty small + HitRes = CalcDamage(Target, Energy, FrArea, 0) --This is random junk, angle doesn't matter + Damage = HitRes.Damage * 2 --Damage is going to be fright and shrapnel, nothing much else --Just in case we hit something not standard Target.ACF.Armour = Size * 0.2 * 0.02 HitRes = CalcDamage(Target, Energy, FrArea, 0) - Damage = HitRes.Damage * 10 + Damage = HitRes.Damage * 30 end - - --if Ammo == true then - -- Entity.KilledByAmmo = true - --end - Entity:TakeDamage(Damage, Inflictor, Gun) - --if Ammo == true then - -- Entity.KilledByAmmo = false - --end - HitRes.Kill = false - --print(Damage) - --print(Bone) - - return HitRes + else --Just in case we hit something not standard + Target.ACF.Armour = Size * 0.2 * 0.02 + HitRes = CalcDamage(Target, Energy, FrArea, 0) + Damage = HitRes.Damage * 10 end - local function VehicleDamage(Entity, Energy, FrArea, Angle, Inflictor, _, Gun) - local HitRes = CalcDamage(Entity, Energy, FrArea, Angle) - local Driver = Entity:GetDriver() + Entity:TakeDamage(Damage, Inflictor, Gun) - if IsValid(Driver) then - SquishyDamage(Driver, Energy, FrArea, Angle, Inflictor, math.Rand(0, 7), Gun) - end + HitRes.Kill = false - HitRes.Kill = false + return HitRes + end - if HitRes.Damage >= Entity.ACF.Health then - HitRes.Kill = true - else - Entity.ACF.Health = Entity.ACF.Health - HitRes.Damage - Entity.ACF.Armour = Entity.ACF.Armour * (0.5 + Entity.ACF.Health / Entity.ACF.MaxHealth / 2) --Simulating the plate weakening after a hit - end + local function VehicleDamage(Entity, Energy, FrArea, Angle, Inflictor, _, Gun) + local HitRes = CalcDamage(Entity, Energy, FrArea, Angle) + local Driver = Entity:GetDriver() - return HitRes + if IsValid(Driver) then + SquishyDamage(Driver, Energy, FrArea, Angle, Inflictor, math.Rand(0, 7), Gun) end - local function PropDamage(Entity, Energy, FrArea, Angle) - local HitRes = CalcDamage(Entity, Energy, FrArea, Angle) - HitRes.Kill = false + HitRes.Kill = false - if HitRes.Damage >= Entity.ACF.Health then - HitRes.Kill = true - else - Entity.ACF.Health = Entity.ACF.Health - HitRes.Damage - Entity.ACF.Armour = math.Clamp(Entity.ACF.MaxArmour * (0.5 + Entity.ACF.Health / Entity.ACF.MaxHealth / 2) ^ 1.7, Entity.ACF.MaxArmour * 0.25, Entity.ACF.MaxArmour) --Simulating the plate weakening after a hit - - --math.Clamp( Entity.ACF.Ductility, -0.8, 0.8 ) - if Entity.ACF.PrHealth and Entity.ACF.PrHealth ~= Entity.ACF.Health then - if not ACF_HealthUpdateList then - ACF_HealthUpdateList = {} - - -- We should send things slowly to not overload traffic. - TimerCreate("ACF_HealthUpdateList", 1, 1, function() - local Table = {} - - for _, v in pairs(ACF_HealthUpdateList) do - if IsValid(v) then - table.insert(Table, { - ID = v:EntIndex(), - Health = v.ACF.Health, - MaxHealth = v.ACF.MaxHealth - }) - end - end + if HitRes.Damage >= Entity.ACF.Health then + HitRes.Kill = true + else + Entity.ACF.Health = Entity.ACF.Health - HitRes.Damage + Entity.ACF.Armour = Entity.ACF.Armour * (0.5 + Entity.ACF.Health / Entity.ACF.MaxHealth / 2) --Simulating the plate weakening after a hit + end - net.Start("ACF_RenderDamage") - net.WriteTable(Table) - net.Broadcast() + return HitRes + end - ACF_HealthUpdateList = nil - end) - end + local function PropDamage(Entity, Energy, FrArea, Angle) + local HitRes = CalcDamage(Entity, Energy, FrArea, Angle) + + HitRes.Kill = false + + if HitRes.Damage >= Entity.ACF.Health then + HitRes.Kill = true + else + Entity.ACF.Health = Entity.ACF.Health - HitRes.Damage + Entity.ACF.Armour = math.Clamp(Entity.ACF.MaxArmour * (0.5 + Entity.ACF.Health / Entity.ACF.MaxHealth / 2) ^ 1.7, Entity.ACF.MaxArmour * 0.25, Entity.ACF.MaxArmour) --Simulating the plate weakening after a hit + + --math.Clamp( Entity.ACF.Ductility, -0.8, 0.8 ) + if Entity.ACF.PrHealth and Entity.ACF.PrHealth ~= Entity.ACF.Health then + if not ACF_HealthUpdateList then + ACF_HealthUpdateList = {} + + -- We should send things slowly to not overload traffic. + TimerCreate("ACF_HealthUpdateList", 1, 1, function() + local Table = {} + + for _, v in pairs(ACF_HealthUpdateList) do + if IsValid(v) then + table.insert(Table, { + ID = v:EntIndex(), + Health = v.ACF.Health, + MaxHealth = v.ACF.MaxHealth + }) + end + end - table.insert(ACF_HealthUpdateList, Entity) + net.Start("ACF_RenderDamage") + net.WriteTable(Table) + net.Broadcast() + + ACF_HealthUpdateList = nil + end) end - Entity.ACF.PrHealth = Entity.ACF.Health + table.insert(ACF_HealthUpdateList, Entity) end - return HitRes + Entity.ACF.PrHealth = Entity.ACF.Health end - ACF.PropDamage = PropDamage + return HitRes + end - function ACF_Damage(Entity, Energy, FrArea, Angle, Inflictor, Bone, Gun, Type) - local Activated = Check(Entity) + ACF.PropDamage = PropDamage - if HookRun("ACF_BulletDamage", Activated, Entity, Energy, FrArea, Angle, Inflictor, Bone, Gun) == false or Activated == false then - return { - Damage = 0, - Overkill = 0, - Loss = 0, - Kill = false - } - end + function ACF.Damage(Entity, Energy, FrArea, Angle, Inflictor, Bone, Gun, Type) + local Activated = ACF.Check(Entity) - if Entity.ACF_OnDamage then -- Use special damage function if target entity has one - return Entity:ACF_OnDamage(Entity, Energy, FrArea, Angle, Inflictor, Bone, Type) - elseif Activated == "Prop" then - return PropDamage(Entity, Energy, FrArea, Angle, Inflictor, Bone) - elseif Activated == "Vehicle" then - return VehicleDamage(Entity, Energy, FrArea, Angle, Inflictor, Bone, Gun) - elseif Activated == "Squishy" then - return SquishyDamage(Entity, Energy, FrArea, Angle, Inflictor, Bone, Gun) - end + if HookRun("ACF_BulletDamage", Activated, Entity, Energy, FrArea, Angle, Inflictor, Bone, Gun) == false or Activated == false then + return { + Damage = 0, + Overkill = 0, + Loss = 0, + Kill = false + } end - end ----------------------------------------- - - do -- Remove Props ------------------------------ - local function KillChildProps( Entity, BlastPos, Energy ) - local Explosives = {} - local Children = ACF_GetAllChildren(Entity) - local Count = 0 + if Entity.ACF_OnDamage then -- Use special damage function if target entity has one + return Entity:ACF_OnDamage(Energy, FrArea, Angle, Inflictor, Bone, Type) + elseif Activated == "Prop" then + return PropDamage(Entity, Energy, FrArea, Angle, Inflictor, Bone) + elseif Activated == "Vehicle" then + return VehicleDamage(Entity, Energy, FrArea, Angle, Inflictor, Bone, Gun) + elseif Activated == "Squishy" then + return SquishyDamage(Entity, Energy, FrArea, Angle, Inflictor, Bone, Gun) + end + end - -- do an initial processing pass on children, separating out explodey things to handle last - for Ent in pairs( Children ) do - Ent.ACF_Killed = true -- mark that it's already processed + ACF_Damage = ACF.Damage +end ----------------------------------------- - if not ValidDebris[Ent:GetClass()] then - Children[Ent] = nil -- ignoring stuff like holos, wiremod components, etc. - else - Ent:SetParent(nil) +do -- Remove Props ------------------------------ + util.AddNetworkString("ACF_Debris") - if Ent.IsExplosive and not Ent.Exploding then - Explosives[Ent] = true - Children[Ent] = nil - else - Count = Count + 1 - end - end - end + local Queue = {} - -- HE kill the children of this ent, instead of disappearing them by removing parent - if next(Children) then - local DebrisChance = math.Clamp(ChildDebris / Count, 0, 1) - local Power = Energy / math.min(Count,3) - - for Ent in pairs( Children ) do - if math.random() < DebrisChance then - ACF_HEKill(Ent, (Ent:GetPos() - BlastPos):GetNormalized(), Power) - else - constraint.RemoveAll(Ent) - Ent:Remove() - end - end - end + local function SendQueue() + for Entity, Data in pairs(Queue) do + local JSON = util.TableToJSON(Data) - -- explode stuff last, so we don't re-process all that junk again in a new explosion - if next(Explosives) then - for Ent in pairs(Explosives) do - if Ent.Exploding then continue end + net.Start("ACF_Debris") + net.WriteString(JSON) + net.SendPVS(Data.Position) - Ent.Exploding = true - Ent.Inflictor = Entity.Inflictor - Ent:Detonate() - end - end + Queue[Entity] = nil end - ACF_KillChildProps = KillChildProps + end - function ACF_HEKill(Entity, HitVector, Energy, BlastPos) -- blast pos is an optional world-pos input for flinging away children props more realistically - -- if it hasn't been processed yet, check for children - if not Entity.ACF_Killed then KillChildProps(Entity, BlastPos or Entity:GetPos(), Energy) end + local function DebrisNetter(Entity, Normal, Power, CanGib, Ignite) + if not ACF.GetServerBool("CreateDebris") then return end + if Queue[Entity] then return end - local Obj = Entity:GetPhysicsObject() - local Mass = IsValid(Obj) and Obj:GetMass() or 50 + local Current = Entity:GetColor() + local New = Vector(Current.r, Current.g, Current.b) * math.Rand(0.3, 0.6) - constraint.RemoveAll(Entity) - Entity:Remove() + if not next(Queue) then + timer.Create("ACF_DebrisQueue", 0, 1, SendQueue) + end - if Entity:BoundingRadius() < ACF.DebrisScale then return nil end + Queue[Entity] = { + Position = Entity:GetPos(), + Angles = Entity:GetAngles(), + Material = Entity:GetMaterial(), + Model = Entity:GetModel(), + Color = Color(New.x, New.y, New.z, Current.a), + Normal = Normal, + Power = Power, + CanGib = CanGib or nil, + Ignite = Ignite or nil, + } + end - local Debris = ents.Create("acf_debris") - Debris:SetModel(Entity:GetModel()) - Debris:SetAngles(Entity:GetAngles()) - Debris:SetPos(Entity:GetPos()) - Debris:SetMaterial("models/props_wasteland/metal_tram001a") - Debris:Spawn() - Debris:Activate() + function ACF.KillChildProps(Entity, BlastPos, Energy) + local Explosives = {} + local Children = ACF_GetAllChildren(Entity) + local Count = 0 - local Phys = Debris:GetPhysicsObject() - if IsValid(Phys) then - Phys:SetMass(math.Clamp(Mass,5,50000)) - Phys:ApplyForceOffset(HitVector:GetNormalized() * Energy * 15, Debris:GetPos() + VectorRand() * 10) -- previously energy*350 - end + -- do an initial processing pass on children, separating out explodey things to handle last + for Ent in pairs(Children) do + Ent.ACF_Killed = true -- mark that it's already processed - if math.random() < ACF.DebrisIgniteChance then - Debris:Ignite(math.Rand(5, 45), 0) - end + if not ValidDebris[Ent:GetClass()] then + Children[Ent] = nil -- ignoring stuff like holos, wiremod components, etc. + else + Ent:SetParent() - return Debris + if Ent.IsExplosive and not Ent.Exploding then + Explosives[Ent] = true + Children[Ent] = nil + else + Count = Count + 1 + end + end end - function ACF_APKill(Entity, HitVector, Power) + -- HE kill the children of this ent, instead of disappearing them by removing parent + if next(Children) then + local DebrisChance = math.Clamp(ChildDebris / Count, 0, 1) + local Power = Energy / math.min(Count,3) - KillChildProps(Entity, Entity:GetPos(), Power) -- kill the children of this ent, instead of disappearing them from removing parent + for Ent in pairs( Children ) do + if math.random() < DebrisChance then + ACF.HEKill(Ent, (Ent:GetPos() - BlastPos):GetNormalized(), Power) + else + constraint.RemoveAll(Ent) + Ent:Remove() + end + end + end - local Obj = Entity:GetPhysicsObject() - local Mass = 25 + -- explode stuff last, so we don't re-process all that junk again in a new explosion + if next(Explosives) then + for Ent in pairs(Explosives) do + if Ent.Exploding then continue end - if IsValid(Obj) then Mass = Obj:GetMass() end + Ent.Exploding = true + Ent.Inflictor = Entity.Inflictor + Ent:Detonate() + end + end + end - constraint.RemoveAll(Entity) - Entity:Remove() + function ACF.HEKill(Entity, Normal, Energy, BlastPos) -- blast pos is an optional world-pos input for flinging away children props more realistically + -- if it hasn't been processed yet, check for children + if not Entity.ACF_Killed then + ACF.KillChildProps(Entity, BlastPos or Entity:GetPos(), Energy) + end - if Entity:BoundingRadius() < ACF.DebrisScale then return end + local Radius = Entity:BoundingRadius() + local Debris = {} - local Debris = ents.Create("acf_debris") - Debris:SetModel(Entity:GetModel()) - Debris:SetAngles(Entity:GetAngles()) - Debris:SetPos(Entity:GetPos()) - Debris:SetMaterial(Entity:GetMaterial()) - Debris:SetColor(Color(120, 120, 120, 255)) - Debris:Spawn() - Debris:Activate() + DebrisNetter(Entity, Normal, Energy, false, true) - local Phys = Debris:GetPhysicsObject() - if IsValid(Phys) then - Phys:SetMass(math.Clamp(Mass,5,50000)) - Phys:ApplyForceOffset(HitVector:GetNormalized() * Power * 350, Debris:GetPos() + VectorRand() * 20) - end + if ACF.GetServerBool("CreateFireballs") then + local Fireballs = math.Clamp(Radius * 0.01, 1, math.max(10 * ACF.GetServerNumber("FireballMult", 1), 1)) + local Min, Max = Entity:OBBMins(), Entity:OBBMaxs() + local Pos = Entity:GetPos() + local Ang = Entity:GetAngles() - local BreakEffect = EffectData() - BreakEffect:SetOrigin(Entity:GetPos()) - BreakEffect:SetScale(20) - util.Effect("WheelDust", BreakEffect) + for _ = 1, Fireballs do -- should we base this on prop volume? + local Fireball = ents.Create("acf_debris") - return Debris - end - end + if not IsValid(Fireball) then break end -- we probably hit edict limit, stop looping - do -- Round Impact -------------------------- - local function RicochetVector(Flight, HitNormal) - local Vec = Flight:GetNormalized() + local Lifetime = math.Rand(5, 15) + local Offset = ACF.RandomVector(Min, Max) - return Vec - ( 2 * Vec:Dot(HitNormal) ) * HitNormal - end + Offset:Rotate(Ang) - function ACF_RoundImpact( Bullet, Speed, Energy, Target, HitPos, HitNormal , Bone ) - local Angle = ACF_GetHitAngle( HitNormal , Bullet.Flight ) - - local HitRes = ACF_Damage ( --DAMAGE !! - Target, - Energy, - Bullet.PenArea, - Angle, - Bullet.Owner, - Bone, - Bullet.Gun, - Bullet.Type - ) - - local Ricochet = 0 - if HitRes.Loss == 1 then - -- Ricochet distribution center - local sigmoidCenter = Bullet.DetonatorAngle or ( Bullet.Ricochet - math.abs(Speed / 39.37 - Bullet.LimitVel) / 100 ) - - -- Ricochet probability (sigmoid distribution); up to 5% minimal ricochet probability for projectiles with caliber < 20 mm - local ricoProb = math.Clamp( 1 / (1 + math.exp( (Angle - sigmoidCenter) / -4) ), math.max(-0.05 * (Bullet.Caliber - 2) / 2, 0), 1 ) - - -- Checking for ricochet - if ricoProb > math.random() and Angle < 90 then - Ricochet = math.Clamp(Angle / 90, 0.05, 1) -- atleast 5% of energy is kept - HitRes.Loss = 0.25 - Ricochet - Energy.Kinetic = Energy.Kinetic * HitRes.Loss - end - end + Fireball:SetPos(Pos + Offset) + Fireball:Spawn() + Fireball:Ignite(Lifetime) - if ACF_KEPUSH:GetBool() then - Shove( - Target, - HitPos, - Bullet.Flight:GetNormalized(), - Energy.Kinetic * HitRes.Loss * 1000 * Bullet.ShovePower - ) - end + timer.Simple(Lifetime, function() + if not IsValid(Fireball) then return end - if HitRes.Kill then - local Debris = ACF_APKill( Target , (Bullet.Flight):GetNormalized() , Energy.Kinetic ) - table.insert( Bullet.Filter , Debris ) - end + Fireball:Remove() + end) - HitRes.Ricochet = false + local Phys = Fireball:GetPhysicsObject() - if Ricochet > 0 and Bullet.Ricochets < 3 then - Bullet.Ricochets = Bullet.Ricochets + 1 - Bullet.NextPos = HitPos - Bullet.Flight = (RicochetVector(Bullet.Flight, HitNormal) + VectorRand() * 0.025):GetNormalized() * Speed * Ricochet + if IsValid(Phys) then + Phys:ApplyForceOffset(Normal * Energy / Fireballs, Fireball:GetPos() + VectorRand()) + end - HitRes.Ricochet = true + Debris[Fireball] = true end - - return HitRes end - function ACF_PenetrateGround( Bullet, Energy, HitPos, HitNormal ) - local MaxDig = ((Energy.Penetration / Bullet.PenArea) * ACF.KEtoRHA / ACF.GroundtoRHA) / 25.4 - local HitRes = {Penetrated = false, Ricochet = false} - - local DigTr = { } - DigTr.start = HitPos + Bullet.Flight:GetNormalized() * 0.1 - DigTr.endpos = HitPos + Bullet.Flight:GetNormalized() * (MaxDig + 0.1) - DigTr.filter = Bullet.Filter - DigTr.mask = MASK_SOLID_BRUSHONLY - local DigRes = util.TraceLine(DigTr) - --print(util.GetSurfacePropName(DigRes.SurfaceProps)) - - local loss = DigRes.FractionLeftSolid - - if loss == 1 or loss == 0 then --couldn't penetrate - local Ricochet = 0 - local Speed = Bullet.Flight:Length() / ACF.Scale - local Angle = ACF_GetHitAngle( HitNormal, Bullet.Flight ) - local MinAngle = math.min(Bullet.Ricochet - Speed / 39.37 / 30 + 20,89.9) --Making the chance of a ricochet get higher as the speeds increase - if Angle > math.random(MinAngle,90) and Angle < 89.9 then --Checking for ricochet - Ricochet = Angle / 90 * 0.75 - end + constraint.RemoveAll(Entity) + Entity:Remove() - if Ricochet > 0 and Bullet.GroundRicos < 2 then - Bullet.GroundRicos = Bullet.GroundRicos + 1 - Bullet.NextPos = HitPos - Bullet.Flight = (RicochetVector(Bullet.Flight, HitNormal) + VectorRand() * 0.05):GetNormalized() * Speed * Ricochet - HitRes.Ricochet = true - end - else --penetrated - Bullet.Flight = Bullet.Flight * (1 - loss) - Bullet.NextPos = DigRes.StartPos + Bullet.Flight:GetNormalized() * 0.25 --this is actually where trace left brush - HitRes.Penetrated = true - end + return Debris + end - return HitRes - end + function ACF.APKill(Entity, Normal, Power) + ACF.KillChildProps(Entity, Entity:GetPos(), Power) -- kill the children of this ent, instead of disappearing them from removing parent + + DebrisNetter(Entity, Normal, Power, true, false) + + constraint.RemoveAll(Entity) + Entity:Remove() end + + ACF_KillChildProps = ACF.KillChildProps + ACF_HEKill = ACF.HEKill + ACF_APKill = ACF.APKill end diff --git a/lua/acf/server/permissionmodes/pmode_battle.lua b/lua/acf/server/permissionmodes/pmode_battle.lua index 090538cda..76ad46f73 100644 --- a/lua/acf/server/permissionmodes/pmode_battle.lua +++ b/lua/acf/server/permissionmodes/pmode_battle.lua @@ -17,7 +17,7 @@ end local perms = ACF.Permissions -- a short description of what the mode does -local modedescription = "Enables safe-zones and battlefield. No ACF damage can occur in a safe-zone." +local modedescription = "Enables safe-zones and battlefield. No ACF damage can occur in a safe-zone." -- battle-mode specifics: how much hp/armour should the players have? local MAX_HP = 100 local MAX_Armour = 50 diff --git a/lua/acf/server/permissionmodes/pmode_build.lua b/lua/acf/server/permissionmodes/pmode_build.lua index 18b35bf2a..4576345ba 100644 --- a/lua/acf/server/permissionmodes/pmode_build.lua +++ b/lua/acf/server/permissionmodes/pmode_build.lua @@ -2,7 +2,7 @@ ACF Permission mode: Build This mode blocks all damage to entities without the owner's permission. Owners can permit damage from specific players. - Players and NPCs remain vulnerable to damage. This is what admin mods are for. + Players and NPCs remain vulnerable to damage. This is what admin mods are for. This mode requires a CPPI-compatible prop-protector to function properly. //]] @@ -16,7 +16,7 @@ end local perms = ACF.Permissions -- a short description of what the mode does local modedescription = "Disables all ACF damage unless the owner permits it. PvP is allowed." --- if the attacker or victim can't be identified, what should we do? true allows damage, false blocks it. +-- if the attacker or victim can't be identified, what should we do? true allows damage, false blocks it. local DefaultPermission = false --[[ diff --git a/lua/acf/server/permissionmodes/pmode_strictbuild.lua b/lua/acf/server/permissionmodes/pmode_strictbuild.lua index dc081a5f5..0ba965690 100644 --- a/lua/acf/server/permissionmodes/pmode_strictbuild.lua +++ b/lua/acf/server/permissionmodes/pmode_strictbuild.lua @@ -15,7 +15,7 @@ end local perms = ACF.Permissions -- a short description of what the mode does local modedescription = "Disables all ACF damage unless the owner permits it. PvP is disallowed." --- if the attacker or victim can't be identified, what should we do? true allows damage, false blocks it. +-- if the attacker or victim can't be identified, what should we do? true allows damage, false blocks it. local DefaultPermission = false --[[ diff --git a/lua/acf/server/persisted_vars.lua b/lua/acf/server/persisted_vars.lua new file mode 100644 index 000000000..d3bb0c38a --- /dev/null +++ b/lua/acf/server/persisted_vars.lua @@ -0,0 +1,25 @@ +-- Variables that should be persisted between server restarts + +-- Settings +ACF.PersistServerData("Gamemode", 2) +ACF.PersistServerData("ServerDataAllowAdmin", false) +ACF.PersistServerData("RestrictInfo", true) +ACF.PersistServerData("GunfireEnabled", true) +ACF.PersistServerData("HealthFactor", 1) +ACF.PersistServerData("ArmorFactor", 1) +ACF.PersistServerData("FuelFactor", 1) +ACF.PersistServerData("CompFuelFactor", 1) +ACF.PersistServerData("HEPush", true) +ACF.PersistServerData("KEPush", true) +ACF.PersistServerData("RecoilPush", true) +ACF.PersistServerData("Volume", 1) +ACF.PersistServerData("AllowFunEnts", true) +ACF.PersistServerData("UseKillicons", true) +ACF.PersistServerData("ShowFunMenu", true) +ACF.PersistServerData("WorkshopContent", true) +ACF.PersistServerData("WorkshopExtras", false) + +-- Debris +ACF.PersistServerData("CreateDebris", true) +ACF.PersistServerData("CreateFireballs", false) +ACF.PersistServerData("FireballMult", 1) diff --git a/lua/acf/shared/acf_userconfig_example.lua b/lua/acf/shared/acf_userconfig_example.lua index 93f0c8bb0..96480c9c6 100644 --- a/lua/acf/shared/acf_userconfig_example.lua +++ b/lua/acf/shared/acf_userconfig_example.lua @@ -8,10 +8,10 @@ ]]-- --- Some example settings are below. They enable damage protection, double gun accuracy, and make shots more likely to be accurate. --- There are more settings like this. Find them all in lua/autorun/shared/acf_globals.lua +-- Some example settings are below. They enable damage protection, double gun accuracy, and make shots more likely to be accurate. +-- There are more settings like this. Find them all in lua/autorun/shared/acf_globals.lua -ACF.EnableDefaultDP = true -- Enable the inbuilt damage protection system. -ACF.GunInaccuracyScale = 0.5 -- Make guns 2x more accurate by halving the spread scale. -ACF.GunInaccuracyBias = 1.2 -- Shots are more likely to be accurate with bias < 2 \ No newline at end of file +--ACF.EnableDefaultDP = true -- Enable the inbuilt damage protection system. +--ACF.GunInaccuracyScale = 1 -- Make guns 2x less accurate by increasing the spread scale. +--ACF.GunInaccuracyBias = 1.2 -- Shots are more likely to be accurate with bias < 2 \ No newline at end of file diff --git a/lua/acf/shared/acfcratelist.lua b/lua/acf/shared/acfcratelist.lua deleted file mode 100644 index 9240d452d..000000000 --- a/lua/acf/shared/acfcratelist.lua +++ /dev/null @@ -1,399 +0,0 @@ -local AmmoTable = {} --Start ammo containers listing - -AmmoTable["AmmoSmall"] = { - id = "AmmoSmall", - ent = "acf_ammo", - type = "Ammo", - name = "Small Ammo Crate", - desc = "Small ammo crate\n", - model = "models/ammocrate_small.mdl", - Size = Vector(20.44, 7.93, 13.77), - Offset = Vector(-0.36, -0.01, 7.01) -} - -AmmoTable["AmmoMedCube"] = { - id = "AmmoMedCube", - ent = "acf_ammo", - type = "Ammo", - name = "Medium cubic ammo crate", - desc = "Medium cubic ammo crate\n", - model = "models/ammocrate_medium_small.mdl", - Size = Vector(26.27, 25.9, 26.3), - Offset = Vector(-0.03, 0.42, 13.1) -} - -AmmoTable["AmmoMedium"] = { - id = "AmmoMedium", - ent = "acf_ammo", - type = "Ammo", - name = "Medium Ammo Crate", - desc = "Medium ammo crate\n", - model = "models/ammocrate_medium.mdl", - Size = Vector(51.9, 26.27, 25.9), - Offset = Vector(-0.12, 0.42, 13.1) -} - -AmmoTable["AmmoLarge"] = { - id = "AmmoLarge", - ent = "acf_ammo", - type = "Ammo", - name = "Large Ammo Crate", - desc = "Large ammo crate\n", - model = "models/ammocrate_large.mdl", - Size = Vector(52.17, 52.06, 51.92), - Offset = Vector(-0.05, -0.38, 26.06) -} - -AmmoTable["Ammo1x1x8"] = { - id = "Ammo1x1x8", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 1x1x8 Size\n", - model = "models/ammocrates/ammo_1x1x8.mdl", - Size = Vector(11.09, 89.15, 11.13), - Offset = Vector(0, -0.02, -0.12) -} - -AmmoTable["Ammo1x1x6"] = { - id = "Ammo1x1x6", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 1x1x6 Size\n", - model = "models/ammocrates/ammo_1x1x6.mdl", - Size = Vector(11.2, 66.51, 11.14), - Offset = Vector(0, 0.02, -0.14) -} - -AmmoTable["Ammo1x1x4"] = { - id = "Ammo1x1x4", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 1x1x4 Size\n", - model = "models/ammocrates/ammo_1x1x4.mdl", - Size = Vector(11.16, 45.06, 11.11), - Offset = Vector(0, 0.16, -0.17) -} - -AmmoTable["Ammo1x1x2"] = { - id = "Ammo1x1x2", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 1x1x2 Size\n", - model = "models/ammocrates/ammo_1x1x2.mdl", - Size = Vector(11.16, 22.43, 11.11), - Offset = Vector(0, 0.05, -0.17) -} - -AmmoTable["Ammo2x2x1"] = { - id = "Ammo2x2x1", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 2x2x1 Size\n", - model = "models/ammocrates/ammocrate_2x2x1.mdl", - Size = Vector(20.06, 8.06, 20.06), - Offset = Vector(-0.52, 0, 10.19) -} - -AmmoTable["Ammo2x2x2"] = { - id = "Ammo2x2x2", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 2x2x2 Size\n", - model = "models/ammocrates/ammocrate_2x2x2.mdl", - Size = Vector(20.06, 20.06, 20.06), - Offset = Vector(-0.09, 0.51, 10.51) -} - -AmmoTable["Ammo2x2x4"] = { - id = "Ammo2x2x4", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 2x2x4 Size\n", - model = "models/ammocrates/ammocrate_2x2x4.mdl", - Size = Vector(20.06, 45.06, 20.06), - Offset = Vector(-0.71, 0, 10.18) -} - -AmmoTable["Ammo2x2x6"] = { - id = "Ammo2x2x6", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 2x2x6 Size\n", - model = "models/ammocrates/ammo_2x2x6.mdl", - Size = Vector(22.45, 66.59, 22.33), - Offset = Vector(0, 0, -0.1) -} - -AmmoTable["Ammo2x2x8"] = { - id = "Ammo2x2x8", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 2x2x8 Size\n", - model = "models/ammocrates/ammo_2x2x8.mdl", - Size = Vector(22.46, 90.1, 22.82), - Offset = Vector(0, 0, -0.14) -} - -AmmoTable["Ammo2x3x1"] = { - id = "Ammo2x3x1", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 2x3x1 Size\n", - model = "models/ammocrates/ammocrate_2x3x1.mdl", - Size = Vector(32.06, 8.06, 20.06), - Offset = Vector(-0.64, 0, 10.19) -} - -AmmoTable["Ammo2x3x2"] = { - id = "Ammo2x3x2", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 2x3x2 Size\n", - model = "models/ammocrates/ammocrate_2x3x2.mdl", - Size = Vector(32.06, 20.06, 20.06), - Offset = Vector(-0.64, 0, 10.19) -} - -AmmoTable["Ammo2x3x4"] = { - id = "Ammo2x3x4", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 2x3x4 Size\n", - model = "models/ammocrates/ammocrate_2x3x4.mdl", - Size = Vector(32.06, 45.06, 20.06), - Offset = Vector(-0.79, 0, 10) -} - -AmmoTable["Ammo2x3x6"] = { - id = "Ammo2x3x6", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 2x3x6 Size\n", - model = "models/ammocrates/ammocrate_2x3x6.mdl", - Size = Vector(31.94, 68.04, 20.1), - Offset = Vector(-0.79, -0.04, 10.02) -} - -AmmoTable["Ammo2x3x8"] = { - id = "Ammo2x3x8", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 2x3x8 Size\n", - model = "models/ammocrates/ammocrate_2x3x8.mdl", - Size = Vector(31.94, 90.1, 20.1), - Offset = Vector(-0.79, 0, 10.02) -} - -AmmoTable["Ammo2x4x1"] = { - id = "Ammo2x4x1", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 2x4x1 Size\n", - model = "models/ammocrates/ammocrate_2x4x1.mdl", - Size = Vector(45.06, 8.06, 20.06), - Offset = Vector(-0.64, 0, 10.19) -} - -AmmoTable["Ammo2x4x2"] = { - id = "Ammo2x4x2", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 2x4x2 Size\n", - model = "models/ammocrates/ammocrate_2x4x2.mdl", - Size = Vector(45.06, 20.06, 20.06), - Offset = Vector(-0.2, 0.71, 10.18) -} - -AmmoTable["Ammo2x4x4"] = { - id = "Ammo2x4x4", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 2x4x4 Size\n", - model = "models/ammocrates/ammocrate_2x4x4.mdl", - Size = Vector(45.06, 45.06, 20.06), - Offset = Vector(-0.79, 0, 10) -} - -AmmoTable["Ammo2x4x6"] = { - id = "Ammo2x4x6", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 2x4x6 Size\n", - model = "models/ammocrates/ammocrate_2x4x6.mdl", - Size = Vector(45.06, 68.06, 20.06), - Offset = Vector(-0.79, 0, 10) -} - -AmmoTable["Ammo2x4x8"] = { - id = "Ammo2x4x8", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 2x4x8 Size\n", - model = "models/ammocrates/ammocrate_2x4x8.mdl", - Size = Vector(45.06, 90.06, 20.06), - Offset = Vector(-0.79, 0, 10) -} - -AmmoTable["Ammo3x4x1"] = { - id = "Ammo3x4x1", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 3x4x1 Size\n", - model = "models/ammocrates/ammocrate_3x4x1.mdl", - Size = Vector(45.06, 8.06, 32.06), - Offset = Vector(-0.64, 0, 16.25) -} - -AmmoTable["Ammo3x4x2"] = { - id = "Ammo3x4x2", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 3x4x2 Size\n", - model = "models/ammocrates/ammocrate_3x4x2.mdl", - Size = Vector(45.06, 20.06, 32.06), - Offset = Vector(-0.2, 0.71, 16.26) -} - -AmmoTable["Ammo3x4x4"] = { - id = "Ammo3x4x4", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 3x4x4 Size\n", - model = "models/ammocrates/ammocrate_3x4x4.mdl", - Size = Vector(45.06, 45.06, 32.06), - Offset = Vector(-0.79, 0, 16) -} - -AmmoTable["Ammo3x4x6"] = { - id = "Ammo3x4x6", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 3x4x6 Size\n", - model = "models/ammocrates/ammocrate_3x4x6.mdl", - Size = Vector(45.06, 68.06, 32.06), - Offset = Vector(-0.79, 0, 16) -} - -AmmoTable["Ammo3x4x8"] = { - id = "Ammo3x4x8", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 3x4x8 Size\n", - model = "models/ammocrates/ammocrate_3x4x8.mdl", - Size = Vector(45.06, 90.06, 32.06), - Offset = Vector(0.15, 0, 16) -} - -AmmoTable["Ammo4x4x1"] = { - id = "Ammo4x4x1", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 4x4x1 Size\n", - model = "models/ammocrates/ammo_4x4x1.mdl", - Size = Vector(45.06, 45.06, 11.51), - Offset = Vector(0, 0, -0.06) -} - -AmmoTable["Ammo4x4x2"] = { - id = "Ammo4x4x2", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 4x4x2 Size\n", - model = "models/ammocrates/ammocrate_4x4x2.mdl", - Size = Vector(45.06, 20.06, 45.06), - Offset = Vector(-0.14, 0.71, 22.5) -} - -AmmoTable["Ammo4x4x4"] = { - id = "Ammo4x4x4", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 4x4x4 Size\n", - model = "models/ammocrates/ammocrate_4x4x4.mdl", - Size = Vector(45.06, 45.06, 45.06), - Offset = Vector(0.15, 0, 22.5) -} - -AmmoTable["Ammo4x4x6"] = { - id = "Ammo4x4x6", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 4x4x6 Size\n", - model = "models/ammocrates/ammocrate_4x4x6.mdl", - Size = Vector(45.06, 68.06, 45.06), - Offset = Vector(0.15, 0.06, 22.5) -} - -AmmoTable["Ammo4x4x8"] = { - id = "Ammo4x4x8", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 4x4x8 Size\n", - model = "models/ammocrates/ammocrate_4x4x8.mdl", - Size = Vector(45.06, 90.06, 45.06), - Offset = Vector(0.15, -0.01, 22.5) -} - -AmmoTable["Ammo4x6x8"] = { - id = "Ammo4x6x8", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 4x6x8 Size\n", - model = "models/ammocrates/ammo_4x6x8.mdl", - Size = Vector(67.56, 89.98, 44.99), - Offset = Vector(0, 0, -0.07) -} - -AmmoTable["Ammo4x6x6"] = { - id = "Ammo4x6x6", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 4x6x6 Size\n", - model = "models/ammocrates/ammo_4x6x6.mdl", - Size = Vector(67.56, 67.35, 45), - Offset = Vector(0, 0, -0.02) -} - -AmmoTable["Ammo4x8x8"] = { - id = "Ammo4x8x8", - ent = "acf_ammo", - type = "Ammo", - name = "Modular Ammo Crate", - desc = "Modular Ammo Crate 4x8x8 Size\n", - model = "models/ammocrates/ammo_4x8x8.mdl", - Size = Vector(90.21, 90.13, 45.19), - Offset = Vector(0, 0, -0.15) -} - -ACF.Weapons.Ammo = AmmoTable --end ammo containers listing diff --git a/lua/acf/shared/ammo_crates/crates.lua b/lua/acf/shared/ammo_crates/crates.lua new file mode 100644 index 000000000..f80659fcb --- /dev/null +++ b/lua/acf/shared/ammo_crates/crates.lua @@ -0,0 +1,179 @@ +ACF.RegisterCrate("AmmoSmall", { + Size = Vector(20.44, 7.93, 13.77), + Offset = Vector(-0.36, -0.01, 7.01) +}) + +ACF.RegisterCrate("AmmoMedCube", { + Size = Vector(26.27, 25.9, 26.3), + Offset = Vector(-0.03, 0.42, 13.1) +}) + +ACF.RegisterCrate("AmmoMedium", { + Size = Vector(51.9, 26.27, 25.9), + Offset = Vector(-0.12, 0.42, 13.1) +}) + +ACF.RegisterCrate("AmmoLarge", { + Size = Vector(52.17, 52.06, 51.92), + Offset = Vector(-0.05, -0.38, 26.06) +}) + +ACF.RegisterCrate("Ammo1x1x8", { + Size = Vector(11.09, 89.15, 11.13), + Offset = Vector(0, -0.02, -0.12) +}) + +ACF.RegisterCrate("Ammo1x1x6", { + Size = Vector(11.2, 66.51, 11.14), + Offset = Vector(0, 0.02, -0.14) +}) + +ACF.RegisterCrate("Ammo1x1x4", { + Size = Vector(11.16, 45.06, 11.11), + Offset = Vector(0, 0.16, -0.17) +}) + +ACF.RegisterCrate("Ammo1x1x2", { + Size = Vector(11.16, 22.43, 11.11), + Offset = Vector(0, 0.05, -0.17) +}) + +ACF.RegisterCrate("Ammo2x2x1", { + Size = Vector(20.06, 8.06, 20.06), + Offset = Vector(-0.52, 0, 10.19) +}) + +ACF.RegisterCrate("Ammo2x2x2", { + Size = Vector(20.06, 20.06, 20.06), + Offset = Vector(-0.09, 0.51, 10.51) +}) + +ACF.RegisterCrate("Ammo2x2x4", { + Size = Vector(20.06, 45.06, 20.06), + Offset = Vector(-0.71, 0, 10.18) +}) + +ACF.RegisterCrate("Ammo2x2x6", { + Size = Vector(22.45, 66.59, 22.33), + Offset = Vector(0, 0, -0.1) +}) + +ACF.RegisterCrate("Ammo2x2x8", { + Size = Vector(22.46, 90.1, 22.82), + Offset = Vector(0, 0, -0.14) +}) + +ACF.RegisterCrate("Ammo2x3x1", { + Size = Vector(32.06, 8.06, 20.06), + Offset = Vector(-0.64, 0, 10.19) +}) + +ACF.RegisterCrate("Ammo2x3x2", { + Size = Vector(32.06, 20.06, 20.06), + Offset = Vector(-0.64, 0, 10.19) +}) + +ACF.RegisterCrate("Ammo2x3x4", { + Size = Vector(32.06, 45.06, 20.06), + Offset = Vector(-0.79, 0, 10) +}) + +ACF.RegisterCrate("Ammo2x3x6", { + Size = Vector(31.94, 68.04, 20.1), + Offset = Vector(-0.79, -0.04, 10.02) +}) + +ACF.RegisterCrate("Ammo2x3x8", { + Size = Vector(31.94, 90.1, 20.1), + Offset = Vector(-0.79, 0, 10.02) +}) + +ACF.RegisterCrate("Ammo2x4x1", { + Size = Vector(45.06, 8.06, 20.06), + Offset = Vector(-0.64, 0, 10.19) +}) + +ACF.RegisterCrate("Ammo2x4x2", { + Size = Vector(45.06, 20.06, 20.06), + Offset = Vector(-0.2, 0.71, 10.18) +}) + +ACF.RegisterCrate("Ammo2x4x4", { + Size = Vector(45.06, 45.06, 20.06), + Offset = Vector(-0.79, 0, 10) +}) + +ACF.RegisterCrate("Ammo2x4x6", { + Size = Vector(45.06, 68.06, 20.06), + Offset = Vector(-0.79, 0, 10) +}) + +ACF.RegisterCrate("Ammo2x4x8", { + Size = Vector(45.06, 90.06, 20.06), + Offset = Vector(-0.79, 0, 10) +}) + +ACF.RegisterCrate("Ammo3x4x1", { + Size = Vector(45.06, 8.06, 32.06), + Offset = Vector(-0.64, 0, 16.25) +}) + +ACF.RegisterCrate("Ammo3x4x2", { + Size = Vector(45.06, 20.06, 32.06), + Offset = Vector(-0.2, 0.71, 16.26) +}) + +ACF.RegisterCrate("Ammo3x4x4", { + Size = Vector(45.06, 45.06, 32.06), + Offset = Vector(-0.79, 0, 16) +}) + +ACF.RegisterCrate("Ammo3x4x6", { + Size = Vector(45.06, 68.06, 32.06), + Offset = Vector(-0.79, 0, 16) +}) + +ACF.RegisterCrate("Ammo3x4x8", { + Size = Vector(45.06, 90.06, 32.06), + Offset = Vector(0.15, 0, 16) +}) + +ACF.RegisterCrate("Ammo4x4x1", { + Size = Vector(45.06, 45.06, 11.51), + Offset = Vector(0, 0, -0.06) +}) + +ACF.RegisterCrate("Ammo4x4x2", { + Size = Vector(45.06, 20.06, 45.06), + Offset = Vector(-0.14, 0.71, 22.5) +}) + +ACF.RegisterCrate("Ammo4x4x4", { + Size = Vector(45.06, 45.06, 45.06), + Offset = Vector(0.15, 0, 22.5) +}) + +ACF.RegisterCrate("Ammo4x4x6", { + Size = Vector(45.06, 68.06, 45.06), + Offset = Vector(0.15, 0.06, 22.5) +}) + +ACF.RegisterCrate("Ammo4x4x8", { + Size = Vector(45.06, 90.06, 45.06), + Offset = Vector(0.15, -0.01, 22.5) +}) + +ACF.RegisterCrate("Ammo4x6x6", { + Size = Vector(67.56, 89.98, 44.99), + Offset = Vector(0, 0, -0.07) +}) + +ACF.RegisterCrate("Ammo4x6x8", { + Size = Vector(67.56, 67.35, 45), + Offset = Vector(0, 0, -0.02) +}) + +ACF.RegisterCrate("Ammo4x8x8", { + Size = Vector(90.21, 90.13, 45.19), + Offset = Vector(0, 0, -0.15) +}) diff --git a/lua/acf/shared/ammo_types/ap.lua b/lua/acf/shared/ammo_types/ap.lua new file mode 100644 index 000000000..651adb9b5 --- /dev/null +++ b/lua/acf/shared/ammo_types/ap.lua @@ -0,0 +1,255 @@ +local Ammo = ACF.RegisterAmmoType("AP") + +function Ammo:OnLoaded() + self.Name = "Armor Piercing" + self.Model = "models/munitions/round_100mm_shot.mdl" + self.Description = "A shell made out of a solid piece of steel, meant to penetrate armor." + self.Blacklist = { + GL = true, + MO = true, + SB = true, + SL = true, + } +end + +function Ammo:GetDisplayData(Data) + local Energy = ACF_Kinetic(Data.MuzzleVel * 39.37, Data.ProjMass, Data.LimitVel) + local Display = { + MaxPen = (Energy.Penetration / Data.PenArea) * ACF.KEtoRHA + } + + hook.Run("ACF_GetDisplayData", self, Data, Display) + + return Display +end + +function Ammo:UpdateRoundData(ToolData, Data, GUIData) + GUIData = GUIData or Data + + ACF.UpdateRoundSpecs(ToolData, Data, GUIData) + + Data.ProjMass = Data.FrArea * Data.ProjLength * 0.0079 --Volume of the projectile as a cylinder * density of steel + Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, Data.ProjMass) + Data.DragCoef = Data.FrArea * 0.0001 / Data.ProjMass + Data.CartMass = Data.PropMass + Data.ProjMass + + hook.Run("ACF_UpdateRoundData", self, ToolData, Data, GUIData) + + for K, V in pairs(self:GetDisplayData(Data)) do + GUIData[K] = V + end +end + +function Ammo:BaseConvert(ToolData) + local Data, GUIData = ACF.RoundBaseGunpowder(ToolData, {}) + + Data.ShovePower = 0.2 + Data.PenArea = Data.FrArea ^ ACF.PenAreaMod + Data.LimitVel = 800 --Most efficient penetration speed in m/s + Data.KETransfert = 0.1 --Kinetic energy transfert to the target for movement purposes + Data.Ricochet = 60 --Base ricochet angle + + self:UpdateRoundData(ToolData, Data, GUIData) + + return Data, GUIData +end + +function Ammo:VerifyData(ToolData) + if not ToolData.Projectile then + local Projectile = ToolData.RoundProjectile + + ToolData.Projectile = Projectile and tonumber(Projectile) or 0 + end + + if not ToolData.Propellant then + local Propellant = ToolData.RoundPropellant + + ToolData.Propellant = Propellant and tonumber(Propellant) or 0 + end + + if ToolData.Tracer == nil then + local Data10 = ToolData.RoundData10 + + ToolData.Tracer = Data10 and tobool(tonumber(Data10)) or false -- Haha "0.00" is true but 0 isn't + end +end + +if SERVER then + ACF.AddEntityArguments("acf_ammo", "Projectile", "Propellant", "Tracer") -- Adding extra info to ammo crates + + function Ammo:OnLast(Entity) + Entity.Projectile = nil + Entity.Propellant = nil + Entity.Tracer = nil + + -- Cleanup the leftovers aswell + Entity.RoundProjectile = nil + Entity.RoundPropellant = nil + Entity.RoundData10 = nil + end + + function Ammo:Create(_, BulletData) + ACF.CreateBullet(BulletData) + end + + function Ammo:ServerConvert(ToolData) + self:VerifyData(ToolData) + + local Data = self:BaseConvert(ToolData) + + Data.Id = ToolData.Weapon + Data.Type = ToolData.AmmoType + + return Data + end + + function Ammo:Network(Entity, BulletData) + Entity:SetNW2String("AmmoType", "AP") + Entity:SetNW2Float("Caliber", BulletData.Caliber) + Entity:SetNW2Float("ProjMass", BulletData.ProjMass) + Entity:SetNW2Float("PropMass", BulletData.PropMass) + Entity:SetNW2Float("DragCoef", BulletData.DragCoef) + Entity:SetNW2Float("Tracer", BulletData.Tracer) + end + + function Ammo:GetCrateName() + end + + function Ammo:GetCrateText(BulletData) + local Data = self:GetDisplayData(BulletData) + local Text = "Muzzle Velocity: %s m/s\nMax Penetration: %s mm" + + return Text:format(math.Round(BulletData.MuzzleVel, 2), math.Round(Data.MaxPen, 2)) + end + + function Ammo:PropImpact(Bullet, Trace) + local Target = Trace.Entity + + if ACF.Check(Target) then + local Speed = Bullet.Flight:Length() / ACF.Scale + local Energy = ACF_Kinetic(Speed, Bullet.ProjMass, Bullet.LimitVel) + local HitRes = ACF_RoundImpact(Bullet, Speed, Energy, Target, Trace.HitPos, Trace.HitNormal, Trace.HitGroup) + + if HitRes.Overkill > 0 then + table.insert(Bullet.Filter, Target) --"Penetrate" (Ingoring the prop for the retry trace) + + Bullet.Flight = Bullet.Flight:GetNormalized() * (Energy.Kinetic * (1 - HitRes.Loss) * 2000 / Bullet.ProjMass) ^ 0.5 * 39.37 + + return "Penetrated" + elseif HitRes.Ricochet then + return "Ricochet" + else + return false + end + else + table.insert(Bullet.Filter, Target) + + return "Penetrated" + end + end + + function Ammo:WorldImpact(Bullet, Trace) + if IsValid(Trace.Entity) then + return ACF_PenetrateMapEntity(Bullet, Trace) + else + return ACF_PenetrateGround(Bullet, Trace) + end + end + + function Ammo:OnFlightEnd(Bullet) + ACF_RemoveBullet(Bullet) + end +else + ACF.RegisterAmmoDecal("AP", "damage/ap_pen", "damage/ap_rico") + + local DecalIndex = ACF.GetAmmoDecalIndex + + function Ammo:ClientConvert(ToolData) + self:VerifyData(ToolData) + + local Data, GUIData = self:BaseConvert(ToolData) + + if GUIData then + for K, V in pairs(GUIData) do + Data[K] = V + end + end + + return Data + end + + function Ammo:AddAmmoPreview(Preview) + Preview:SetModel(self.Model) + end + + function Ammo:ImpactEffect(_, Bullet) + local Effect = EffectData() + Effect:SetOrigin(Bullet.SimPos) + Effect:SetNormal(Bullet.SimFlight:GetNormalized()) + Effect:SetRadius(Bullet.Caliber) + Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) + + util.Effect("ACF_Impact", Effect) + end + + function Ammo:PenetrationEffect(_, Bullet) + local Effect = EffectData() + Effect:SetOrigin(Bullet.SimPos) + Effect:SetNormal(Bullet.SimFlight:GetNormalized()) + Effect:SetScale(Bullet.SimFlight:Length()) + Effect:SetMagnitude(Bullet.RoundMass) + Effect:SetRadius(Bullet.Caliber) + Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) + + util.Effect("ACF_Penetration", Effect) + end + + function Ammo:RicochetEffect(_, Bullet) + local Effect = EffectData() + Effect:SetOrigin(Bullet.SimPos) + Effect:SetNormal(Bullet.SimFlight:GetNormalized()) + Effect:SetScale(Bullet.SimFlight:Length()) + Effect:SetMagnitude(Bullet.RoundMass) + Effect:SetRadius(Bullet.Caliber) + Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) + + util.Effect("ACF_Ricochet", Effect) + end + + function Ammo:AddCrateDataTrackers(Trackers) + Trackers.Projectile = true + Trackers.Propellant = true + end + + function Ammo:AddAmmoInformation(Base, ToolData, BulletData) + local RoundStats = Base:AddLabel() + RoundStats:TrackClientData("Projectile", "SetText") + RoundStats:TrackClientData("Propellant") + RoundStats:DefineSetter(function() + self:UpdateRoundData(ToolData, BulletData) + + local Text = "Muzzle Velocity : %s m/s\nProjectile Mass : %s\nPropellant Mass : %s" + local MuzzleVel = math.Round(BulletData.MuzzleVel * ACF.Scale, 2) + local ProjMass = ACF.GetProperMass(BulletData.ProjMass) + local PropMass = ACF.GetProperMass(BulletData.PropMass) + + return Text:format(MuzzleVel, ProjMass, PropMass) + end) + + local PenStats = Base:AddLabel() + PenStats:TrackClientData("Projectile", "SetText") + PenStats:TrackClientData("Propellant") + PenStats:DefineSetter(function() + self:UpdateRoundData(ToolData, BulletData) + + local Text = "Penetration : %s mm RHA\nAt 300m : %s mm RHA @ %s m/s\nAt 800m : %s mm RHA @ %s m/s" + local MaxPen = math.Round(BulletData.MaxPen, 2) + local R1V, R1P = ACF.PenRanging(BulletData.MuzzleVel, BulletData.DragCoef, BulletData.ProjMass, BulletData.PenArea, BulletData.LimitVel, 300) + local R2V, R2P = ACF.PenRanging(BulletData.MuzzleVel, BulletData.DragCoef, BulletData.ProjMass, BulletData.PenArea, BulletData.LimitVel, 800) + + return Text:format(MaxPen, R1P, R1V, R2P, R2V) + end) + + Base:AddLabel("Note: The penetration range data is an approximation and may not be entirely accurate.") + end +end diff --git a/lua/acf/shared/ammo_types/apcr.lua b/lua/acf/shared/ammo_types/apcr.lua new file mode 100644 index 000000000..4d5d91af0 --- /dev/null +++ b/lua/acf/shared/ammo_types/apcr.lua @@ -0,0 +1,58 @@ +local Ammo = ACF.RegisterAmmoType("APCR", "AP") + +function Ammo:OnLoaded() + Ammo.BaseClass.OnLoaded(self) + + self.Name = "Armor Piercing Composite Rigid" + self.Description = "A hardened core munition designed for weapons in the 1940s." + self.Blacklist = ACF.GetWeaponBlacklist({ + C = true, + AL = true, + AC = true, + SA = true, + SC = true, + HMG = true, + RAC = true, + }) +end + +function Ammo:UpdateRoundData(ToolData, Data, GUIData) + GUIData = GUIData or Data + + ACF.UpdateRoundSpecs(ToolData, Data, GUIData) + + Data.ProjMass = (Data.FrArea * 1.1111) * (Data.ProjLength * 0.0079) * 0.75 --Volume of the projectile as a cylinder * density of steel + Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, Data.ProjMass) + Data.DragCoef = (Data.FrArea * 0.000125) / Data.ProjMass -- Worse drag (Manually fudged to make a meaningful difference) + Data.CartMass = Data.PropMass + Data.ProjMass + + hook.Run("ACF_UpdateRoundData", self, ToolData, Data, GUIData) + + for K, V in pairs(self:GetDisplayData(Data)) do + GUIData[K] = V + end +end + +function Ammo:BaseConvert(ToolData) + local Data, GUIData = ACF.RoundBaseGunpowder(ToolData, {}) + + Data.ShovePower = 0.2 + Data.PenArea = (Data.FrArea * 0.7) ^ ACF.PenAreaMod -- APCR has a smaller penetrator + Data.LimitVel = 900 --Most efficient penetration speed in m/s + Data.KETransfert = 0.1 --Kinetic energy transfert to the target for movement purposes + Data.Ricochet = 55 --Base ricochet angle + + self:UpdateRoundData(ToolData, Data, GUIData) + + return Data, GUIData +end + +if SERVER then + function Ammo:Network(Entity, BulletData) + Ammo.BaseClass.Network(self, Entity, BulletData) + + Entity:SetNW2String("AmmoType", "APCR") + end +else + ACF.RegisterAmmoDecal("APCR", "damage/apcr_pen", "damage/apcr_rico") +end diff --git a/lua/acf/shared/ammo_types/apds.lua b/lua/acf/shared/ammo_types/apds.lua new file mode 100644 index 000000000..9308ede4b --- /dev/null +++ b/lua/acf/shared/ammo_types/apds.lua @@ -0,0 +1,63 @@ +local Ammo = ACF.RegisterAmmoType("APDS", "AP") + +function Ammo:OnLoaded() + Ammo.BaseClass.OnLoaded(self) + + self.Name = "Armor Piercing Discarging Sabot" + self.Description = "A subcaliber munition designed to trade damage for penetration. Loses energy quickly over distance." + self.Blacklist = ACF.GetWeaponBlacklist({ + C = true, + AL = true, + AC = true, + SA = true, + RAC = true, + }) +end + +function Ammo:UpdateRoundData(ToolData, Data, GUIData) + GUIData = GUIData or Data + + ACF.UpdateRoundSpecs(ToolData, Data, GUIData) + + local Cylinder = (3.1416 * (Data.Caliber * 0.5) ^ 2) * Data.ProjLength * 0.5 -- A cylinder 1/2 the length of the projectile + local Hole = Data.RoundArea * Data.ProjLength * 0.25 -- Volume removed by the hole the dart passes through + local SabotMass = (Cylinder - Hole) * 2.7 * 0.65 * 0.001 -- Aluminum sabot + + Data.ProjMass = (Data.RoundArea * 0.6666) * (Data.ProjLength * 0.0079) -- Volume of the projectile as a cylinder * density of steel + Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, Data.ProjMass + SabotMass) + Data.DragCoef = Data.RoundArea * 0.0001 / Data.ProjMass + Data.CartMass = Data.PropMass + Data.ProjMass + SabotMass + + hook.Run("ACF_UpdateRoundData", self, ToolData, Data, GUIData) + + for K, V in pairs(self:GetDisplayData(Data)) do + GUIData[K] = V + end +end + +function Ammo:BaseConvert(ToolData) + local Data, GUIData = ACF.RoundBaseGunpowder(ToolData, {}) + local SubCaliberRatio = 0.375 -- Ratio of projectile to gun caliber + local Area = 3.1416 * (Data.Caliber * 0.5 * SubCaliberRatio) ^ 2 + + Data.RoundArea = Area + Data.ShovePower = 0.2 + Data.PenArea = Area ^ ACF.PenAreaMod + Data.LimitVel = 950 --Most efficient penetration speed in m/s + Data.KETransfert = 0.1 --Kinetic energy transfert to the target for movement purposes + Data.Ricochet = 80 --Base ricochet angle + + self:UpdateRoundData(ToolData, Data, GUIData) + + return Data, GUIData +end + +if SERVER then + function Ammo:Network(Entity, BulletData) + Ammo.BaseClass.Network(self, Entity, BulletData) + + Entity:SetNW2String("AmmoType", "APDS") + end +else + ACF.RegisterAmmoDecal("APDS", "damage/apcr_pen", "damage/apcr_rico") +end diff --git a/lua/acf/shared/ammo_types/apfsds.lua b/lua/acf/shared/ammo_types/apfsds.lua new file mode 100644 index 000000000..567fa4f06 --- /dev/null +++ b/lua/acf/shared/ammo_types/apfsds.lua @@ -0,0 +1,60 @@ +local Ammo = ACF.RegisterAmmoType("APFSDS", "AP") + +function Ammo:OnLoaded() + Ammo.BaseClass.OnLoaded(self) + + self.Name = "Armor Piercing Fin Stabilized" + self.Model = "models/munitions/dart_100mm.mdl" + self.Description = "A fin stabilized sabot munition designed to trade damage for superior penetration and long range effectiveness." + self.Blacklist = ACF.GetWeaponBlacklist({ + SB = true, + }) +end + +function Ammo:UpdateRoundData(ToolData, Data, GUIData) + GUIData = GUIData or Data + + ACF.UpdateRoundSpecs(ToolData, Data, GUIData) + + local Cylinder = (3.1416 * (Data.Caliber * 0.5) ^ 2) * Data.ProjLength * 0.25 -- A cylinder 1/4 the length of the projectile + local Hole = Data.RoundArea * Data.ProjLength * 0.25 -- Volume removed by the hole the dart passes through + local SabotMass = (Cylinder - Hole) * 2.7 * 0.25 * 0.001 -- A cylinder with a hole the size of the dart in it and im no math wizard so we're just going to take off 3/4 of the mass for the cutout since sabots are shaped like this: ][ + + Data.ProjMass = (Data.RoundArea * 0.6666) * (Data.ProjLength * 0.0079) -- Volume of the projectile as a cylinder * density of steel + Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, Data.ProjMass + SabotMass) + Data.DragCoef = Data.FrArea * 0.0001 / Data.ProjMass + Data.CartMass = Data.PropMass + Data.ProjMass + SabotMass + + hook.Run("ACF_UpdateRoundData", self, ToolData, Data, GUIData) + + for K, V in pairs(self:GetDisplayData(Data)) do + GUIData[K] = V + end +end + +function Ammo:BaseConvert(ToolData) + local Data, GUIData = ACF.RoundBaseGunpowder(ToolData, {}) + local SubCaliberRatio = 0.29 -- Ratio of projectile to gun caliber + local Area = 3.1416 * (Data.Caliber * 0.5 * SubCaliberRatio) ^ 2 + + Data.RoundArea = Area + Data.ShovePower = 0.2 + Data.PenArea = Area ^ ACF.PenAreaMod + Data.LimitVel = 1000 --Most efficient penetration speed in m/s + Data.KETransfert = 0.1 --Kinetic energy transfert to the target for movement purposes + Data.Ricochet = 80 --Base ricochet angle + + self:UpdateRoundData(ToolData, Data, GUIData) + + return Data, GUIData +end + +if SERVER then + function Ammo:Network(Entity, BulletData) + Ammo.BaseClass.Network(self, Entity, BulletData) + + Entity:SetNW2String("AmmoType", "APFSDS") + end +else + ACF.RegisterAmmoDecal("APFSDS", "damage/apcr_pen", "damage/apcr_rico") +end diff --git a/lua/acf/shared/ammo_types/aphe.lua b/lua/acf/shared/ammo_types/aphe.lua new file mode 100644 index 000000000..3960b655f --- /dev/null +++ b/lua/acf/shared/ammo_types/aphe.lua @@ -0,0 +1,202 @@ +local Ammo = ACF.RegisterAmmoType("APHE", "AP") + +function Ammo:OnLoaded() + Ammo.BaseClass.OnLoaded(self) + + self.Name = "Armor Piercing High Explosive" + self.Description = "Less capable armor piercing round with an explosive charge inside." + self.Blacklist = { + GL = true, + MG = true, + MO = true, + SB = true, + SL = true, + RAC = true, + } +end + +function Ammo:GetDisplayData(Data) + local Display = Ammo.BaseClass.GetDisplayData(self, Data) + local FragMass = Data.ProjMass - Data.FillerMass + + Display.BlastRadius = Data.FillerMass ^ 0.33 * 8 + Display.Fragments = math.max(math.floor((Data.FillerMass / FragMass) * ACF.HEFrag), 2) + Display.FragMass = FragMass / Display.Fragments + Display.FragVel = (Data.FillerMass * ACF.HEPower * 1000 / Display.FragMass / Display.Fragments) ^ 0.5 + + hook.Run("ACF_GetDisplayData", self, Data, Display) + + return Display +end + +function Ammo:UpdateRoundData(ToolData, Data, GUIData) + GUIData = GUIData or Data + + ACF.UpdateRoundSpecs(ToolData, Data, GUIData) + + local HEDensity = ACF.HEDensity * 0.001 + --Volume of the projectile as a cylinder - Volume of the filler * density of steel + Volume of the filler * density of TNT + local ProjMass = math.max(GUIData.ProjVolume - ToolData.FillerMass, 0) * 0.0079 + math.min(ToolData.FillerMass, GUIData.ProjVolume) * HEDensity + local MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, ProjMass) + local Energy = ACF_Kinetic(MuzzleVel * 39.37, ProjMass, Data.LimitVel) + local MaxVol = ACF.RoundShellCapacity(Energy.Momentum, Data.FrArea, Data.Caliber, Data.ProjLength) + + GUIData.MaxFillerVol = math.Round(math.min(GUIData.ProjVolume, MaxVol * 0.9), 2) + GUIData.FillerVol = math.min(ToolData.FillerMass, GUIData.MaxFillerVol) + + Data.FillerMass = GUIData.FillerVol * HEDensity + Data.ProjMass = math.max(GUIData.ProjVolume - GUIData.FillerVol, 0) * 0.0079 + Data.FillerMass + Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, Data.ProjMass) + Data.DragCoef = Data.FrArea * 0.0001 / Data.ProjMass + Data.CartMass = Data.PropMass + Data.ProjMass + + hook.Run("ACF_UpdateRoundData", self, ToolData, Data, GUIData) + + for K, V in pairs(self:GetDisplayData(Data)) do + GUIData[K] = V + end +end + +function Ammo:BaseConvert(ToolData) + local Data, GUIData = ACF.RoundBaseGunpowder(ToolData, {}) + + GUIData.MinFillerVol = 0 + Data.ShovePower = 0.1 + Data.PenArea = Data.FrArea ^ ACF.PenAreaMod + Data.LimitVel = 700 --Most efficient penetration speed in m/s + Data.KETransfert = 0.1 --Kinetic energy transfert to the target for movement purposes + Data.Ricochet = 65 --Base ricochet angle + Data.CanFuze = Data.Caliber * 10 > ACF.MinFuzeCaliber -- Can fuze on calibers > 20mm + + self:UpdateRoundData(ToolData, Data, GUIData) + + return Data, GUIData +end + +function Ammo:VerifyData(ToolData) + Ammo.BaseClass.VerifyData(self, ToolData) + + if not ToolData.FillerMass then + local Data5 = ToolData.RoundData5 + + ToolData.FillerMass = Data5 and tonumber(Data5) or 0 + end +end + +if SERVER then + ACF.AddEntityArguments("acf_ammo", "FillerMass") -- Adding extra info to ammo crates + + function Ammo:OnLast(Entity) + Ammo.BaseClass.OnLast(self, Entity) + + Entity.FillerMass = nil + Entity.RoundData5 = nil -- Cleanup the leftovers aswell + + Entity:SetNW2Float("FillerMass", 0) + end + + function Ammo:Network(Entity, BulletData) + Ammo.BaseClass.Network(self, Entity, BulletData) + + Entity:SetNW2String("AmmoType", "APHE") + Entity:SetNW2Float("FillerMass", BulletData.FillerMass) + end + + function Ammo:GetCrateText(BulletData) + local BaseText = Ammo.BaseClass.GetCrateText(self, BulletData) + local Text = BaseText .. "\nBlast Radius: %s m\nBlast Energy: %s KJ" + local Data = self:GetDisplayData(BulletData) + + return Text:format(math.Round(Data.BlastRadius, 2), math.Round(BulletData.FillerMass * ACF.HEPower, 2)) + end + + function Ammo:OnFlightEnd(Bullet, Trace) + ACF_HE(Trace.HitPos, Bullet.FillerMass, Bullet.ProjMass - Bullet.FillerMass, Bullet.Owner, nil, Bullet.Gun) + + Ammo.BaseClass.OnFlightEnd(self, Bullet, Trace) + end +else + ACF.RegisterAmmoDecal("APHE", "damage/ap_pen", "damage/ap_rico") + + function Ammo:ImpactEffect(_, Bullet) + local Effect = EffectData() + Effect:SetOrigin(Bullet.SimPos) + Effect:SetNormal(Bullet.SimFlight:GetNormalized()) + Effect:SetScale(math.max(Bullet.FillerMass ^ 0.33 * 8 * 39.37, 1)) + Effect:SetRadius(Bullet.Caliber) + + util.Effect("ACF_Explosion", Effect) + end + + function Ammo:AddAmmoControls(Base, ToolData, BulletData) + local FillerMass = Base:AddSlider("Filler Volume", 0, BulletData.MaxFillerVol, 2) + FillerMass:SetClientData("FillerMass", "OnValueChanged") + FillerMass:TrackClientData("Projectile") + FillerMass:DefineSetter(function(Panel, _, Key, Value) + if Key == "FillerMass" then + ToolData.FillerMass = math.Round(Value, 2) + end + + self:UpdateRoundData(ToolData, BulletData) + + Panel:SetMax(BulletData.MaxFillerVol) + Panel:SetValue(BulletData.FillerVol) + + return BulletData.FillerVol + end) + end + + function Ammo:AddCrateDataTrackers(Trackers, ...) + Ammo.BaseClass.AddCrateDataTrackers(self, Trackers, ...) + + Trackers.FillerMass = true + end + + function Ammo:AddAmmoInformation(Base, ToolData, BulletData) + local RoundStats = Base:AddLabel() + RoundStats:TrackClientData("Projectile", "SetText") + RoundStats:TrackClientData("Propellant") + RoundStats:TrackClientData("FillerMass") + RoundStats:DefineSetter(function() + self:UpdateRoundData(ToolData, BulletData) + + local Text = "Muzzle Velocity : %s m/s\nProjectile Mass : %s\nPropellant Mass : %s\nExplosive Mass : %s" + local MuzzleVel = math.Round(BulletData.MuzzleVel * ACF.Scale, 2) + local ProjMass = ACF.GetProperMass(BulletData.ProjMass) + local PropMass = ACF.GetProperMass(BulletData.PropMass) + local Filler = ACF.GetProperMass(BulletData.FillerMass) + + return Text:format(MuzzleVel, ProjMass, PropMass, Filler) + end) + + local FillerStats = Base:AddLabel() + FillerStats:TrackClientData("FillerMass", "SetText") + FillerStats:DefineSetter(function() + self:UpdateRoundData(ToolData, BulletData) + + local Text = "Blast Radius : %s m\nFragments : %s\nFragment Mass : %s\nFragment Velocity : %s m/s" + local Blast = math.Round(BulletData.BlastRadius, 2) + local FragMass = ACF.GetProperMass(BulletData.FragMass) + local FragVel = math.Round(BulletData.FragVel, 2) + + return Text:format(Blast, BulletData.Fragments, FragMass, FragVel) + end) + + local PenStats = Base:AddLabel() + PenStats:TrackClientData("Projectile", "SetText") + PenStats:TrackClientData("Propellant") + PenStats:TrackClientData("FillerMass") + PenStats:DefineSetter(function() + self:UpdateRoundData(ToolData, BulletData) + + local Text = "Penetration : %s mm RHA\nAt 300m : %s mm RHA @ %s m/s\nAt 800m : %s mm RHA @ %s m/s" + local MaxPen = math.Round(BulletData.MaxPen, 2) + local R1V, R1P = ACF.PenRanging(BulletData.MuzzleVel, BulletData.DragCoef, BulletData.ProjMass, BulletData.PenArea, BulletData.LimitVel, 300) + local R2V, R2P = ACF.PenRanging(BulletData.MuzzleVel, BulletData.DragCoef, BulletData.ProjMass, BulletData.PenArea, BulletData.LimitVel, 800) + + return Text:format(MaxPen, R1P, R1V, R2P, R2V) + end) + + Base:AddLabel("Note: The penetration range data is an approximation and may not be entirely accurate.") + end +end \ No newline at end of file diff --git a/lua/acf/shared/ammo_types/fl.lua b/lua/acf/shared/ammo_types/fl.lua new file mode 100644 index 000000000..0d23c5741 --- /dev/null +++ b/lua/acf/shared/ammo_types/fl.lua @@ -0,0 +1,249 @@ +local Ammo = ACF.RegisterAmmoType("FL", "AP") + +function Ammo:OnLoaded() + Ammo.BaseClass.OnLoaded(self) + + self.Name = "Flechette" + self.Model = "models/munitions/dart_100mm.mdl" + self.Description = "Flechette shells contain several steel darts inside, functioning as a large shotgun round." + self.Blacklist = { + AC = true, + GL = true, + MG = true, + MO = true, + SA = true, + SB = true, + SL = true, + HMG = true, + RAC = true, + } +end + +function Ammo:GetDisplayData(Data) + local Energy = ACF_Kinetic(Data.MuzzleVel * 39.37, Data.FlechetteMass, Data.LimitVel) + local Display = { + MaxPen = (Energy.Penetration / Data.FlechettePenArea) * ACF.KEtoRHA + } + + hook.Run("ACF_GetDisplayData", self, Data, Display) + + return Display +end + +function Ammo:UpdateRoundData(ToolData, Data, GUIData) + GUIData = GUIData or Data + + ACF.UpdateRoundSpecs(ToolData, Data, GUIData) + + local PenAdj = 0.8 --higher means lower pen, but more structure (hp) damage (old: 2.35, 2.85) + local RadiusAdj = 1.0 -- lower means less structure (hp) damage, but higher pen (old: 1.0, 0.8) + local Flechettes = math.Clamp(ToolData.Flechettes, Data.MinFlechettes, Data.MaxFlechettes) + local PackRatio = 0.0025 * Flechettes + 0.69 --how efficiently flechettes are packed into shell + + Data.Flechettes = Flechettes + Data.FlechetteSpread = math.Clamp(ToolData.Spread, Data.MinSpread, Data.MaxSpread) + Data.FlechetteRadius = (((PackRatio * RadiusAdj * Data.Caliber * 0.5) ^ 2) / Data.Flechettes) ^ 0.5 + Data.FlechetteArea = 3.1416 * Data.FlechetteRadius ^ 2 -- area of a single flechette + Data.FlechetteMass = Data.FlechetteArea * (Data.ProjLength * 7.9 / 1000) -- volume of single flechette * density of steel + Data.FlechettePenArea = (PenAdj * Data.FlechetteArea) ^ ACF.PenAreaMod + Data.FlechetteDragCoef = Data.FlechetteArea * 0.0001 / Data.FlechetteMass + Data.ProjMass = Data.Flechettes * Data.FlechetteMass -- total mass of all flechettes + Data.DragCoef = Data.FrArea * 0.0001 / Data.ProjMass + Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, Data.ProjMass) + Data.CartMass = Data.PropMass + Data.ProjMass + + hook.Run("ACF_UpdateRoundData", self, ToolData, Data, GUIData) + + for K, V in pairs(self:GetDisplayData(Data)) do + GUIData[K] = V + end +end + +function Ammo:BaseConvert(ToolData) + local Data, GUIData = ACF.RoundBaseGunpowder(ToolData, { LengthAdj = 0.5 }) + + Data.MaxFlechettes = math.Clamp(math.floor(Data.Caliber * 4) - 8, 1, 32) + Data.MinFlechettes = math.min(6, Data.MaxFlechettes) --force bigger guns to have higher min count + Data.MinSpread = 0.25 + Data.MaxSpread = 30 + Data.ShovePower = 0.2 + Data.PenArea = Data.FrArea ^ ACF.PenAreaMod + Data.LimitVel = 500 --Most efficient penetration speed in m/s + Data.KETransfert = 0.1 --Kinetic energy transfert to the target for movement purposes + Data.Ricochet = 75 --Base ricochet angle + + self:UpdateRoundData(ToolData, Data, GUIData) + + return Data, GUIData +end + +function Ammo:VerifyData(ToolData) + Ammo.BaseClass.VerifyData(self, ToolData) + + if not ToolData.Flechettes then + local Data5 = ToolData.RoundData5 + + ToolData.Flechettes = Data5 and tonumber(Data5) or 0 + end + + if not ToolData.Spread then + local Data6 = ToolData.RoundData6 + + ToolData.Spread = Data6 and tonumber(Data6) or 0 + end +end + +if SERVER then + ACF.AddEntityArguments("acf_ammo", "Flechettes", "Spread") -- Adding extra info to ammo crates + + function Ammo:OnLast(Entity) + Ammo.BaseClass.OnLast(self, Entity) + + Entity.Flechettes = nil + Entity.Spread = nil + + -- Cleanup the leftovers aswell + Entity.RoundData5 = nil + Entity.RoundData6 = nil + end + + function Ammo:Create(Gun, BulletData) + local FlechetteData = { + Caliber = math.Round(BulletData.FlechetteRadius * 0.2, 2), + Id = BulletData.Id, + Type = "AP", + Owner = BulletData.Owner, + Entity = BulletData.Entity, + Crate = BulletData.Crate, + Gun = BulletData.Gun, + Pos = BulletData.Pos, + FrArea = BulletData.FlechetteArea, + ProjMass = BulletData.FlechetteMass, + DragCoef = BulletData.FlechetteDragCoef, + Tracer = BulletData.Tracer, + LimitVel = BulletData.LimitVel, + Ricochet = BulletData.Ricochet, + PenArea = BulletData.FlechettePenArea, + ShovePower = BulletData.ShovePower, + KETransfert = BulletData.KETransfert, + } + + --if ammo is cooking off, shoot in random direction + if Gun:GetClass() == "acf_ammo" then + local MuzzleVec = VectorRand() + + for _ = 1, BulletData.Flechettes do + local Inaccuracy = VectorRand() / 360 * ((Gun.Spread or 0) + BulletData.FlechetteSpread) + + FlechetteData.Flight = (MuzzleVec + Inaccuracy):GetNormalized() * BulletData.MuzzleVel * 39.37 + Gun:GetVelocity() + + ACF.CreateBullet(FlechetteData) + end + else + local BaseInaccuracy = math.tan(math.rad(Gun:GetSpread())) + local AddInaccuracy = math.tan(math.rad(BulletData.FlechetteSpread)) + local MuzzleVec = Gun:GetForward() + + for _ = 1, BulletData.Flechettes do + local GunUp, GunRight = Gun:GetUp(), Gun:GetRight() + local BaseInaccuracyMult = math.random() ^ (1 / math.Clamp(ACF.GunInaccuracyBias, 0.5, 4)) * (GunUp * (2 * math.random() - 1) + GunRight * (2 * math.random() - 1)):GetNormalized() + local AddSpreadMult = math.random() ^ (1 / math.Clamp(ACF.GunInaccuracyBias, 0.5, 4)) * (GunUp * (2 * math.random() - 1) + GunRight * (2 * math.random() - 1)):GetNormalized() + + BaseSpread = BaseInaccuracy * BaseInaccuracyMult + AddSpread = AddInaccuracy * AddSpreadMult + + FlechetteData.Flight = (MuzzleVec + BaseSpread + AddSpread):GetNormalized() * BulletData.MuzzleVel * 39.37 + Gun:GetVelocity() + + ACF.CreateBullet(FlechetteData) + end + end + end + + function Ammo:Network(Entity, BulletData) + Ammo.BaseClass.Network(self, Entity, BulletData) + + Entity:SetNW2String("AmmoType", "FL") + Entity:SetNW2Float("Caliber", math.Round(BulletData.FlechetteRadius * 0.2, 2)) + Entity:SetNW2Float("ProjMass", BulletData.FlechetteMass) + Entity:SetNW2Float("DragCoef", BulletData.FlechetteDragCoef) + end + + function Ammo:GetCrateText(BulletData) + local Text = "Muzzle Velocity: %s m/s\nMax Penetration: %s mm\nMax Spread: %s degrees" + local Data = self:GetDisplayData(BulletData) + local Destiny = ACF.FindWeaponrySource(BulletData.Id) + local Class = ACF.GetClassGroup(Destiny, BulletData.Id) + local Spread = Class and Class.Spread * ACF.GunInaccuracyScale or 0 + + return Text:format(math.Round(BulletData.MuzzleVel, 2), math.Round(Data.MaxPen, 2), math.Round(BulletData.FlechetteSpread + Spread, 2)) + end +else + ACF.RegisterAmmoDecal("FL", "damage/ap_pen", "damage/ap_rico") + + function Ammo:AddAmmoControls(Base, ToolData, BulletData) + local Flechettes = Base:AddSlider("Flechette Amount", BulletData.MinFlechettes, BulletData.MaxFlechettes) + Flechettes:SetClientData("Flechettes", "OnValueChanged") + Flechettes:DefineSetter(function(Panel, _, _, Value) + ToolData.Flechettes = math.floor(Value) + + Ammo:UpdateRoundData(ToolData, BulletData) + + Panel:SetValue(BulletData.Flechettes) + + return BulletData.Flechettes + end) + + local Spread = Base:AddSlider("Flechette Spread", BulletData.MinSpread, BulletData.MaxSpread, 2) + Spread:SetClientData("Spread", "OnValueChanged") + Spread:DefineSetter(function(Panel, _, _, Value) + ToolData.Spread = Value + + Ammo:UpdateRoundData(ToolData, BulletData) + + Panel:SetValue(BulletData.FlechetteSpread) + + return BulletData.FlechetteSpread + end) + end + + function Ammo:AddCrateDataTrackers(Trackers, ...) + Ammo.BaseClass.AddCrateDataTrackers(self, Trackers, ...) + + Trackers.Flechettes = true + end + + function Ammo:AddAmmoInformation(Menu, ToolData, BulletData) + local RoundStats = Menu:AddLabel() + RoundStats:TrackClientData("Projectile", "SetText") + RoundStats:TrackClientData("Propellant") + RoundStats:TrackClientData("Flechettes") + RoundStats:DefineSetter(function() + self:UpdateRoundData(ToolData, BulletData) + + local Text = "Muzzle Velocity : %s m/s\nProjectile Mass : %s\nPropellant Mass : %s\nFlechette Mass : %s" + local MuzzleVel = math.Round(BulletData.MuzzleVel * ACF.Scale, 2) + local ProjMass = ACF.GetProperMass(BulletData.ProjMass) + local PropMass = ACF.GetProperMass(BulletData.PropMass) + local FLMass = ACF.GetProperMass(BulletData.FlechetteMass) + + return Text:format(MuzzleVel, ProjMass, PropMass, FLMass) + end) + + local PenStats = Menu:AddLabel() + PenStats:TrackClientData("Projectile", "SetText") + PenStats:TrackClientData("Propellant") + PenStats:TrackClientData("Flechettes") + PenStats:DefineSetter(function() + self:UpdateRoundData(ToolData, BulletData) + + local Text = "Penetration : %s mm RHA\nAt 300m : %s mm RHA @ %s m/s\nAt 800m : %s mm RHA @ %s m/s" + local MaxPen = math.Round(BulletData.MaxPen, 2) + local R1V, R1P = ACF.PenRanging(BulletData.MuzzleVel, BulletData.FlechetteDragCoef, BulletData.FlechetteMass, BulletData.FlechettePenArea, BulletData.LimitVel, 300) + local R2V, R2P = ACF.PenRanging(BulletData.MuzzleVel, BulletData.FlechetteDragCoef, BulletData.FlechetteMass, BulletData.FlechettePenArea, BulletData.LimitVel, 800) + + return Text:format(MaxPen, R1P, R1V, R2P, R2V) + end) + + Menu:AddLabel("Note: The penetration range data is an approximation and may not be entirely accurate.") + end +end \ No newline at end of file diff --git a/lua/acf/shared/ammo_types/he.lua b/lua/acf/shared/ammo_types/he.lua new file mode 100644 index 000000000..7c8e65f81 --- /dev/null +++ b/lua/acf/shared/ammo_types/he.lua @@ -0,0 +1,141 @@ +local Ammo = ACF.RegisterAmmoType("HE", "APHE") + +function Ammo:OnLoaded() + Ammo.BaseClass.OnLoaded(self) + + self.Name = "High Explosive" + self.Description = "A shell filled with explosives, detonates on impact." + self.Blacklist = { + MG = true, + SB = true, + SL = true, + RAC = true, + } +end + +function Ammo:GetDisplayData(Data) + local FragMass = Data.ProjMass - Data.FillerMass + local Fragments = math.max(math.floor((Data.FillerMass / FragMass) * ACF.HEFrag), 2) + local Display = { + BlastRadius = Data.FillerMass ^ 0.33 * 8, + Fragments = Fragments, + FragMass = FragMass / Fragments, + FragVel = (Data.FillerMass * ACF.HEPower * 1000 / FragMass / Fragments / Fragments) ^ 0.5, + } + + hook.Run("ACF_GetDisplayData", self, Data, Display) + + return Display +end + +function Ammo:UpdateRoundData(ToolData, Data, GUIData) + GUIData = GUIData or Data + + ACF.UpdateRoundSpecs(ToolData, Data, GUIData) + + local HEDensity = ACF.HEDensity * 0.001 + -- Volume of the projectile as a cylinder - Volume of the filler * density of steel + Volume of the filler * density of TNT + local ProjMass = math.max(GUIData.ProjVolume - ToolData.FillerMass, 0) * 0.0079 + math.min(ToolData.FillerMass, GUIData.ProjVolume) * HEDensity + local MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, ProjMass) + local Energy = ACF_Kinetic(MuzzleVel * 39.37, ProjMass, Data.LimitVel) + local MaxVol = ACF.RoundShellCapacity(Energy.Momentum, Data.FrArea, Data.Caliber, Data.ProjLength) + + GUIData.MaxFillerVol = math.min(GUIData.ProjVolume, MaxVol) + GUIData.FillerVol = math.min(ToolData.FillerMass, GUIData.MaxFillerVol) + + Data.FillerMass = GUIData.FillerVol * HEDensity + Data.ProjMass = math.max(GUIData.ProjVolume - GUIData.FillerVol, 0) * 0.0079 + Data.FillerMass + Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, Data.ProjMass) + Data.DragCoef = Data.FrArea * 0.0001 / Data.ProjMass + Data.CartMass = Data.PropMass + Data.ProjMass + + hook.Run("ACF_UpdateRoundData", self, ToolData, Data, GUIData) + + for K, V in pairs(self:GetDisplayData(Data)) do + GUIData[K] = V + end +end + +function Ammo:BaseConvert(ToolData) + local Data, GUIData = ACF.RoundBaseGunpowder(ToolData, {}) + + GUIData.MinFillerVol = 0 + + Data.ShovePower = 0.1 + Data.PenArea = Data.FrArea ^ ACF.PenAreaMod + Data.LimitVel = 100 --Most efficient penetration speed in m/s + Data.KETransfert = 0.1 --Kinetic energy transfert to the target for movement purposes + Data.Ricochet = 60 --Base ricochet angle + Data.DetonatorAngle = 80 + Data.CanFuze = Data.Caliber * 10 > ACF.MinFuzeCaliber -- Can fuze on calibers > 20mm + + self:UpdateRoundData(ToolData, Data, GUIData) + + return Data, GUIData +end + +if SERVER then + function Ammo:Network(Entity, BulletData) + Ammo.BaseClass.Network(self, Entity, BulletData) + + Entity:SetNW2String("AmmoType", "HE") + end + + function Ammo:GetCrateText(BulletData) + local Text = "Muzzle Velocity: %s m/s\nBlast Radius: %s m\nBlast Energy: %s KJ" + local Data = self:GetDisplayData(BulletData) + + return Text:format(math.Round(BulletData.MuzzleVel, 2), math.Round(Data.BlastRadius, 2), math.Round(BulletData.FillerMass * ACF.HEPower, 2)) + end + + function Ammo:PropImpact(Bullet, Trace) + local Target = Trace.Entity + + if ACF.Check(Target) then + local Speed = Bullet.Flight:Length() / ACF.Scale + local Energy = ACF_Kinetic(Speed, Bullet.ProjMass - Bullet.FillerMass, Bullet.LimitVel) + local HitRes = ACF_RoundImpact(Bullet, Speed, Energy, Target, Trace.HitPos, Trace.HitNormal, Trace.HitGroup) + + if HitRes.Ricochet then return "Ricochet" end + end + + return false + end + + function Ammo:WorldImpact() + return false + end +else + ACF.RegisterAmmoDecal("HE", "damage/he_pen", "damage/he_rico") + + function Ammo:AddAmmoInformation(Base, ToolData, BulletData) + local RoundStats = Base:AddLabel() + RoundStats:TrackClientData("Projectile", "SetText") + RoundStats:TrackClientData("Propellant") + RoundStats:TrackClientData("FillerMass") + RoundStats:DefineSetter(function() + self:UpdateRoundData(ToolData, BulletData) + + local Text = "Muzzle Velocity : %s m/s\nProjectile Mass : %s\nPropellant Mass : %s\nExplosive Mass : %s" + local MuzzleVel = math.Round(BulletData.MuzzleVel * ACF.Scale, 2) + local ProjMass = ACF.GetProperMass(BulletData.ProjMass) + local PropMass = ACF.GetProperMass(BulletData.PropMass) + local Filler = ACF.GetProperMass(BulletData.FillerMass) + + return Text:format(MuzzleVel, ProjMass, PropMass, Filler) + end) + + local FillerStats = Base:AddLabel() + FillerStats:TrackClientData("FillerMass", "SetText") + FillerStats:DefineSetter(function() + self:UpdateRoundData(ToolData, BulletData) + + local Text = "Blast Radius : %s m\nFragments : %s\nFragment Mass : %s\nFragment Velocity : %s m/s" + local Blast = math.Round(BulletData.BlastRadius, 2) + local FragMass = ACF.GetProperMass(BulletData.FragMass) + local FragVel = math.Round(BulletData.FragVel, 2) + + return Text:format(Blast, BulletData.Fragments, FragMass, FragVel) + end) + end +end \ No newline at end of file diff --git a/lua/acf/shared/ammo_types/heat.lua b/lua/acf/shared/ammo_types/heat.lua new file mode 100644 index 000000000..d3079a253 --- /dev/null +++ b/lua/acf/shared/ammo_types/heat.lua @@ -0,0 +1,421 @@ +local Ammo = ACF.RegisterAmmoType("HEAT", "AP") + +function Ammo:OnLoaded() + Ammo.BaseClass.OnLoaded(self) + + self.Name = "High Explosive Anti-Tank" + self.Description = "A round with a shaped charge inside. Fires a high-velocity jet on detonation." + self.Blacklist = { + AC = true, + MG = true, + MO = true, + SB = true, + SL = true, + HMG = true, + RAC = true, + } +end + +function Ammo:ConeCalc(ConeAngle, Radius) + local ConeLength = math.tan(math.rad(ConeAngle)) * Radius + local ConeArea = 3.1416 * Radius * (Radius ^ 2 + ConeLength ^ 2) ^ 0.5 + local ConeVol = (3.1416 * Radius ^ 2 * ConeLength) * 0.33 + + return ConeLength, ConeArea, ConeVol +end + +-- calculates conversion of filler from powering HEAT jet to raw HE based on crush vel +-- above a threshold vel, HEAT jet doesn't have time to form properly, converting to raw HE proportionally +-- Vel needs to be in m/s (gmu*0.0254) +function Ammo:CrushCalc(Vel, FillerMass) + local Crushed = math.Clamp((Vel - ACF.HEATMinCrush) / (ACF.HEATMaxCrush - ACF.HEATMinCrush), 0, 1) + local HE_Filler = Lerp(Crushed, FillerMass * ACF.HEATBoomConvert, FillerMass) + local HEAT_Filler = Lerp(Crushed, FillerMass, 0) + + return Crushed, HEAT_Filler, HE_Filler +end + +-- coneang now required for slug recalculation at detonation, defaults to 55 if not present +function Ammo:CalcSlugMV(Data, HEATFillerMass) + --keep fillermass/2 so that penetrator stays the same. + return (HEATFillerMass * 0.5 * ACF.HEPower * math.sin(math.rad(10 + Data.ConeAng) * 0.5) / Data.SlugMass) ^ ACF.HEATMVScale +end + +function Ammo:GetDisplayData(Data) + local Crushed, HEATFiller, BoomFiller = self:CrushCalc(Data.MuzzleVel, Data.FillerMass) + local SlugMV = self:CalcSlugMV(Data, HEATFiller) * (Data.SlugPenMul or 1) + local MassUsed = Data.SlugMass * (1 - Crushed) + local Energy = ACF_Kinetic(SlugMV * 39.37, MassUsed, 999999) + local FragMass = Data.CasingMass + Data.SlugMass * Crushed + local Fragments = math.max(math.floor((BoomFiller / FragMass) * ACF.HEFrag), 2) + local Display = { + Crushed = Crushed, + HEATFillerMass = HEATFiller, + BoomFillerMass = BoomFiller, + SlugMV = SlugMV, + SlugMassUsed = MassUsed, + MaxPen = (Energy.Penetration / Data.SlugPenArea) * ACF.KEtoRHA, + TotalFragMass = FragMass, + BlastRadius = BoomFiller ^ 0.33 * 8, + Fragments = Fragments, + FragMass = FragMass / Fragments, + FragVel = (BoomFiller * ACF.HEPower * 1000 / FragMass) ^ 0.5, + } + + hook.Run("ACF_GetDisplayData", self, Data, Display) + + return Display +end + +function Ammo:UpdateRoundData(ToolData, Data, GUIData) + GUIData = GUIData or Data + + ACF.UpdateRoundSpecs(ToolData, Data, GUIData) + + local MaxConeAng = math.deg(math.atan((Data.ProjLength - Data.Caliber * 0.02) / (Data.Caliber * 0.5))) + local LinerAngle = math.Clamp(ToolData.LinerAngle, GUIData.MinConeAng, MaxConeAng) + local _, ConeArea, AirVol = self:ConeCalc(LinerAngle, Data.Caliber * 0.5) + + local LinerRad = math.rad(LinerAngle * 0.5) + local SlugCaliber = Data.Caliber - Data.Caliber * (math.sin(LinerRad) * 0.5 + math.cos(LinerRad) * 1.5) / 2 + local SlugFrArea = 3.1416 * (SlugCaliber * 0.5) ^ 2 + local ConeVol = ConeArea * Data.Caliber * 0.02 + local ProjMass = math.max(GUIData.ProjVolume - ToolData.FillerMass, 0) * 0.0079 + math.min(ToolData.FillerMass, GUIData.ProjVolume) * ACF.HEDensity * 0.001 + ConeVol * 0.0079 --Volume of the projectile as a cylinder - Volume of the filler - Volume of the crush cone * density of steel + Volume of the filler * density of TNT + Area of the cone * thickness * density of steel + local MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, ProjMass) + local Energy = ACF_Kinetic(MuzzleVel * 39.37, ProjMass, Data.LimitVel) + local MaxVol = ACF.RoundShellCapacity(Energy.Momentum, Data.FrArea, Data.Caliber, Data.ProjLength) + + GUIData.MaxConeAng = MaxConeAng + GUIData.MaxFillerVol = math.max(math.Round(MaxVol - AirVol - ConeVol, 2), GUIData.MinFillerVol) + GUIData.FillerVol = math.Clamp(ToolData.FillerMass, GUIData.MinFillerVol, GUIData.MaxFillerVol) + + Data.ConeAng = LinerAngle + Data.FillerMass = GUIData.FillerVol * ACF.HEDensity * 0.00069 + Data.ProjMass = math.max(GUIData.ProjVolume - GUIData.FillerVol - AirVol - ConeVol, 0) * 0.0079 + Data.FillerMass + ConeVol * 0.0079 + Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, Data.ProjMass) + Data.SlugMass = ConeVol * 0.0079 + Data.SlugCaliber = SlugCaliber + Data.SlugPenArea = SlugFrArea ^ ACF.PenAreaMod + Data.SlugDragCoef = SlugFrArea * 0.0001 / Data.SlugMass + + local _, HEATFiller, BoomFiller = self:CrushCalc(Data.MuzzleVel, Data.FillerMass) + + Data.BoomFillerMass = BoomFiller + Data.SlugMV = self:CalcSlugMV(Data, HEATFiller) + Data.CasingMass = Data.ProjMass - Data.FillerMass - ConeVol * 0.0079 + Data.DragCoef = Data.FrArea * 0.0001 / Data.ProjMass + Data.CartMass = Data.PropMass + Data.ProjMass + + hook.Run("ACF_UpdateRoundData", self, ToolData, Data, GUIData) + + for K, V in pairs(self:GetDisplayData(Data)) do + GUIData[K] = V + end +end + +function Ammo:BaseConvert(ToolData) + local Data, GUIData = ACF.RoundBaseGunpowder(ToolData, {}) + + GUIData.MinConeAng = 0 + GUIData.MinFillerVol = 0 + + Data.SlugRicochet = 500 -- Base ricochet angle (The HEAT slug shouldn't ricochet at all) + Data.ShovePower = 0.1 + Data.PenArea = Data.FrArea ^ ACF.PenAreaMod + Data.LimitVel = 100 -- Most efficient penetration speed in m/s + Data.KETransfert = 0.1 -- Kinetic energy transfert to the target for movement purposes + Data.Ricochet = 60 -- Base ricochet angle + Data.DetonatorAngle = 75 + Data.Detonated = false + Data.NotFirstPen = false + Data.CanFuze = Data.Caliber * 10 > ACF.MinFuzeCaliber -- Can fuze on calibers > 20mm + + self:UpdateRoundData(ToolData, Data, GUIData) + + return Data, GUIData +end + +function Ammo:VerifyData(ToolData) + Ammo.BaseClass.VerifyData(self, ToolData) + + if not ToolData.FillerMass then + local Data5 = ToolData.RoundData5 + + ToolData.FillerMass = Data5 and tonumber(Data5) or 0 + end + + if not ToolData.LinerAngle then + local Data6 = ToolData.RoundData6 + + ToolData.LinerAngle = Data6 and tonumber(Data6) or 0 + end +end + +if SERVER then + ACF.AddEntityArguments("acf_ammo", "LinerAngle") -- Adding extra info to ammo crates + + function Ammo:OnLast(Entity) + Ammo.BaseClass.OnLast(self, Entity) + + Entity.FillerMass = nil + Entity.LinerAngle = nil + + -- Cleanup the leftovers aswell + Entity.RoundData5 = nil + Entity.RoundData6 = nil + + Entity:SetNW2Float("FillerMass", 0) + end + + function Ammo:Network(Entity, BulletData) + Ammo.BaseClass.Network(self, Entity, BulletData) + + Entity:SetNW2String("AmmoType", "HEAT") + Entity:SetNW2Float("FillerMass", BulletData.FillerMass) + end + + function Ammo:GetCrateText(BulletData) + local Text = "Muzzle Velocity: %s m/s\nMax Penetration: %s mm\nBlast Radius: %s m\n", "Blast Energy: %s KJ" + local Data = self:GetDisplayData(BulletData) + + return Text:format(math.Round(BulletData.MuzzleVel, 2), math.Round(Data.MaxPen, 2), math.Round(Data.BlastRadius, 2), math.Round(Data.BoomFillerMass * ACF.HEPower, 2)) + end + + function Ammo:Detonate(Bullet, HitPos) + local Crushed, HEATFillerMass, BoomFillerMass = self:CrushCalc(Bullet.Flight:Length() * 0.0254, Bullet.FillerMass) + + ACF_HE(HitPos, BoomFillerMass, Bullet.CasingMass + Bullet.SlugMass * Crushed, Bullet.Owner, Bullet.Filter, Bullet.Gun) + + if Crushed == 1 then return false end -- no HEAT jet to fire off, it was all converted to HE + + local SlugMV = self:CalcSlugMV(Bullet, HEATFillerMass) * 39.37 * (Bullet.SlugPenMul or 1) + + Bullet.Detonated = true + Bullet.Flight = Bullet.Flight:GetNormalized() * SlugMV + Bullet.NextPos = HitPos + Bullet.DragCoef = Bullet.SlugDragCoef + Bullet.ProjMass = Bullet.SlugMass * (1 - Crushed) + Bullet.Caliber = Bullet.SlugCaliber + Bullet.PenArea = Bullet.SlugPenArea + Bullet.Ricochet = Bullet.SlugRicochet + Bullet.LimitVel = 999999 + + return true + end + + function Ammo:PropImpact(Bullet, Trace) + local Target = Trace.Entity + + if ACF.Check(Target) then + local Speed = Bullet.Flight:Length() / ACF.Scale + local HitPos = Trace.HitPos + local HitNormal = Trace.HitNormal + local Bone = Trace.HitGroup + + -- TODO: Figure out why bullets are missing 10% of their penetration + if Bullet.Detonated then + local Multiplier = Bullet.NotFirstPen and ACF.HEATPenLayerMul or 1 + local Energy = ACF_Kinetic(Speed, Bullet.ProjMass, Bullet.LimitVel) + local HitRes = ACF_RoundImpact(Bullet, Speed, Energy, Target, HitPos, HitNormal, Bone) + + Bullet.NotFirstPen = true + + if HitRes.Overkill > 0 then + table.insert(Bullet.Filter, Target) --"Penetrate" (Ingoring the prop for the retry trace) + + Bullet.Flight = Bullet.Flight:GetNormalized() * math.sqrt(Energy.Kinetic * (1 - HitRes.Loss) * Multiplier * 2000 / Bullet.ProjMass) * 39.37 + + return "Penetrated" + else + return false + end + else + local Energy = ACF_Kinetic(Speed, Bullet.ProjMass - Bullet.FillerMass, Bullet.LimitVel) + local HitRes = ACF_RoundImpact(Bullet, Speed, Energy, Target, HitPos, HitNormal, Bone) + + if HitRes.Ricochet then + return "Ricochet" + else + if self:Detonate(Bullet, HitPos) then + return "Penetrated" + else + return false + end + end + end + else + table.insert(Bullet.Filter, Target) + + return "Penetrated" + end + + return false + end + + function Ammo:WorldImpact(Bullet, Trace) + if not Bullet.Detonated then + if self:Detonate(Bullet, Trace.HitPos) then + return "Penetrated" + else + return false + end + end + + local Function = IsValid(Trace.Entity) and ACF_PenetrateMapEntity or ACF_PenetrateGround + + return Function(Bullet, Trace) + end +else + ACF.RegisterAmmoDecal("HEAT", "damage/heat_pen", "damage/heat_rico", function(Caliber) return Caliber * 0.1667 end) + + local DecalIndex = ACF.GetAmmoDecalIndex + + function Ammo:PenetrationEffect(Effect, Bullet) + if Bullet.Detonated then + local Data = EffectData() + Data:SetOrigin(Bullet.SimPos) + Data:SetNormal(Bullet.SimFlight:GetNormalized()) + Data:SetScale(Bullet.SimFlight:Length()) + Data:SetMagnitude(Bullet.RoundMass) + Data:SetRadius(Bullet.Caliber) + Data:SetDamageType(DecalIndex(Bullet.AmmoType)) + + util.Effect("ACF_Penetration", Data) + else + local _, _, BoomFillerMass = self:CrushCalc(Bullet.SimFlight:Length() * 0.0254, Bullet.FillerMass) + local Data = EffectData() + Data:SetOrigin(Bullet.SimPos) + Data:SetNormal(Bullet.SimFlight:GetNormalized()) + Data:SetRadius(math.max(BoomFillerMass ^ 0.33 * 8 * 39.37, 1)) + + util.Effect("ACF_HEAT_Explosion", Data) + + Bullet.Detonated = true + Bullet.LimitVel = 999999 + + Effect:SetModel("models/Gibs/wood_gib01e.mdl") + end + end + + function Ammo:RicochetEffect(_, Bullet) + local Detonated = Bullet.Detonated + local Effect = EffectData() + Effect:SetOrigin(Bullet.SimPos) + Effect:SetNormal(Bullet.SimFlight:GetNormalized()) + Effect:SetScale(Bullet.SimFlight:Length()) + Effect:SetMagnitude(Bullet.RoundMass) + Effect:SetRadius(Bullet.Caliber) + Effect:SetDamageType(DecalIndex(Detonated and Bullet.AmmoType or "AP")) + + util.Effect("ACF_Ricochet", Effect) + end + + function Ammo:AddAmmoControls(Base, ToolData, BulletData) + local LinerAngle = Base:AddSlider("Liner Angle", BulletData.MinConeAng, BulletData.MaxConeAng, 2) + LinerAngle:SetClientData("LinerAngle", "OnValueChanged") + LinerAngle:TrackClientData("Projectile") + LinerAngle:DefineSetter(function(Panel, _, Key, Value) + if Key == "LinerAngle" then + ToolData.LinerAngle = math.Round(Value, 2) + end + + self:UpdateRoundData(ToolData, BulletData) + + Panel:SetMax(BulletData.MaxConeAng) + Panel:SetValue(BulletData.ConeAng) + + return BulletData.ConeAng + end) + + local FillerMass = Base:AddSlider("Filler Volume", 0, BulletData.MaxFillerVol, 2) + FillerMass:SetClientData("FillerMass", "OnValueChanged") + FillerMass:TrackClientData("Projectile") + FillerMass:TrackClientData("LinerAngle") + FillerMass:DefineSetter(function(Panel, _, Key, Value) + if Key == "FillerMass" then + ToolData.FillerMass = math.Round(Value, 2) + end + + self:UpdateRoundData(ToolData, BulletData) + + Panel:SetMax(BulletData.MaxFillerVol) + Panel:SetValue(BulletData.FillerVol) + + return BulletData.FillerVol + end) + end + + function Ammo:AddCrateDataTrackers(Trackers, ...) + Ammo.BaseClass.AddCrateDataTrackers(self, Trackers, ...) + + Trackers.FillerMass = true + Trackers.LinerAngle = true + end + + function Ammo:AddAmmoInformation(Base, ToolData, BulletData) + local RoundStats = Base:AddLabel() + RoundStats:TrackClientData("Projectile", "SetText") + RoundStats:TrackClientData("Propellant") + RoundStats:TrackClientData("FillerMass") + RoundStats:TrackClientData("LinerAngle") + RoundStats:DefineSetter(function() + self:UpdateRoundData(ToolData, BulletData) + + local Text = "Muzzle Velocity : %s m/s\nProjectile Mass : %s\nPropellant Mass : %s\nExplosive Mass : %s" + local MuzzleVel = math.Round(BulletData.MuzzleVel * ACF.Scale, 2) + local ProjMass = ACF.GetProperMass(BulletData.ProjMass) + local PropMass = ACF.GetProperMass(BulletData.PropMass) + local Filler = ACF.GetProperMass(BulletData.FillerMass) + + return Text:format(MuzzleVel, ProjMass, PropMass, Filler) + end) + + local FillerStats = Base:AddLabel() + FillerStats:TrackClientData("FillerMass", "SetText") + FillerStats:TrackClientData("LinerAngle") + FillerStats:DefineSetter(function() + self:UpdateRoundData(ToolData, BulletData) + + local Text = "Blast Radius : %s m\nFragments : %s\nFragment Mass : %s\nFragment Velocity : %s m/s" + local Blast = math.Round(BulletData.BlastRadius, 2) + local FragMass = ACF.GetProperMass(BulletData.FragMass) + local FragVel = math.Round(BulletData.FragVel, 2) + + return Text:format(Blast, BulletData.Fragments, FragMass, FragVel) + end) + + local Penetrator = Base:AddLabel() + Penetrator:TrackClientData("Projectile", "SetText") + Penetrator:TrackClientData("Propellant") + Penetrator:TrackClientData("FillerMass") + Penetrator:TrackClientData("LinerAngle") + Penetrator:DefineSetter(function() + self:UpdateRoundData(ToolData, BulletData) + + local Text = "Penetrator Caliber : %s mm\nPenetrator Mass : %s\nPenetrator Velocity : %s m/s" + local Caliber = math.Round(BulletData.SlugCaliber * 10, 2) + local Mass = ACF.GetProperMass(BulletData.SlugMassUsed) + local Velocity = math.Round(BulletData.SlugMV, 2) + + return Text:format(Caliber, Mass, Velocity) + end) + + local PenStats = Base:AddLabel() + PenStats:TrackClientData("Projectile", "SetText") + PenStats:TrackClientData("Propellant") + PenStats:TrackClientData("FillerMass") + PenStats:TrackClientData("LinerAngle") + PenStats:DefineSetter(function() + self:UpdateRoundData(ToolData, BulletData) + + local Text = "Penetration : %s mm RHA\nAt 300m : %s mm RHA @ %s m/s\nAt 800m : %s mm RHA @ %s m/s" + local MaxPen = math.Round(BulletData.MaxPen, 2) + local R1V = ACF.PenRanging(BulletData.MuzzleVel, BulletData.DragCoef, BulletData.ProjMass, BulletData.PenArea, BulletData.LimitVel, 300) + local R2V = ACF.PenRanging(BulletData.MuzzleVel, BulletData.DragCoef, BulletData.ProjMass, BulletData.PenArea, BulletData.LimitVel, 800) + + return Text:format(MaxPen, MaxPen, R1V, MaxPen, R2V) + end) + + Base:AddLabel("Note: The penetration range data is an approximation and may not be entirely accurate.") + end +end \ No newline at end of file diff --git a/lua/acf/shared/ammo_types/heatfs.lua b/lua/acf/shared/ammo_types/heatfs.lua new file mode 100644 index 000000000..05651e4e7 --- /dev/null +++ b/lua/acf/shared/ammo_types/heatfs.lua @@ -0,0 +1,67 @@ +local Ammo = ACF.RegisterAmmoType("HEATFS", "HEAT") + +function Ammo:OnLoaded() + Ammo.BaseClass.OnLoaded(self) + + self.Name = "High Explosive Anti-Tank Fin Stabilized" + self.Description = "An improved HEAT round with higher penetration and muzzle velocity." + self.Blacklist = ACF.GetWeaponBlacklist({ + SB = true, + }) +end + +function Ammo:UpdateRoundData(ToolData, Data, GUIData) + GUIData = GUIData or Data + + ACF.UpdateRoundSpecs(ToolData, Data, GUIData) + + local MaxConeAng = math.deg(math.atan((Data.ProjLength - Data.Caliber * 0.02) / (Data.Caliber * 0.5))) + local LinerAngle = math.Clamp(ToolData.LinerAngle, GUIData.MinConeAng, MaxConeAng) + local _, ConeArea, AirVol = self:ConeCalc(LinerAngle, Data.Caliber * 0.5) + + local LinerRad = math.rad(LinerAngle * 0.5) + local SlugCaliber = Data.Caliber - Data.Caliber * (math.sin(LinerRad) * 0.5 + math.cos(LinerRad) * 1.5) * 0.5 + local SlugFrArea = 3.1416 * (SlugCaliber * 0.5) ^ 2 + local ConeVol = ConeArea * Data.Caliber * 0.02 + local ProjMass = math.max(GUIData.ProjVolume - ToolData.FillerMass, 0) * 0.0079 + math.min(ToolData.FillerMass, GUIData.ProjVolume) * ACF.HEDensity * 0.001 + ConeVol * 0.0079 --Volume of the projectile as a cylinder - Volume of the filler - Volume of the crush cone * density of steel + Volume of the filler * density of TNT + Area of the cone * thickness * density of steel + local MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, ProjMass) + local Energy = ACF_Kinetic(MuzzleVel * 39.37, ProjMass, Data.LimitVel) + local MaxVol = ACF.RoundShellCapacity(Energy.Momentum, Data.FrArea, Data.Caliber, Data.ProjLength) + + GUIData.MaxConeAng = MaxConeAng + GUIData.MaxFillerVol = math.max(math.Round(MaxVol - AirVol - ConeVol, 2), GUIData.MinFillerVol) + GUIData.FillerVol = math.Clamp(ToolData.FillerMass, GUIData.MinFillerVol, GUIData.MaxFillerVol) + + Data.ConeAng = LinerAngle + Data.FillerMass = GUIData.FillerVol * ACF.HEDensity * 0.00069 + Data.ProjMass = math.max(GUIData.ProjVolume - GUIData.FillerVol - AirVol - ConeVol, 0) * 0.0079 + Data.FillerMass + ConeVol * 0.0079 + Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, Data.ProjMass) * 1.25 + Data.SlugMass = ConeVol * 0.0079 + Data.SlugCaliber = SlugCaliber + Data.SlugPenArea = (SlugFrArea ^ ACF.PenAreaMod) * 0.6666 + Data.SlugDragCoef = SlugFrArea * 0.0001 / Data.SlugMass + + local _, HEATFiller, BoomFiller = self:CrushCalc(Data.MuzzleVel, Data.FillerMass) + + Data.BoomFillerMass = BoomFiller + Data.SlugMV = self:CalcSlugMV(Data, HEATFiller) + Data.CasingMass = Data.ProjMass - Data.FillerMass - ConeVol * 0.0079 + Data.DragCoef = Data.FrArea * 0.0001 / Data.ProjMass + Data.CartMass = Data.PropMass + Data.ProjMass + + hook.Run("ACF_UpdateRoundData", self, ToolData, Data, GUIData) + + for K, V in pairs(self:GetDisplayData(Data)) do + GUIData[K] = V + end +end + +if SERVER then + function Ammo:Network(Entity, BulletData) + Ammo.BaseClass.Network(self, Entity, BulletData) + + Entity:SetNW2String("AmmoType", "HEATFS") + end +else + ACF.RegisterAmmoDecal("HEATFS", "damage/heat_pen", "damage/heat_rico", function(Caliber) return Caliber * 0.1667 end) +end diff --git a/lua/acf/shared/ammo_types/hp.lua b/lua/acf/shared/ammo_types/hp.lua new file mode 100644 index 000000000..40ad4c3f4 --- /dev/null +++ b/lua/acf/shared/ammo_types/hp.lua @@ -0,0 +1,178 @@ +local Ammo = ACF.RegisterAmmoType("HP", "AP") + +function Ammo:OnLoaded() + Ammo.BaseClass.OnLoaded(self) + + self.Name = "Hollow Point" + self.Description = "A round with a hollow cavity, meant to flatten against surfaces on impact." + self.Blacklist = ACF.GetWeaponBlacklist({ + MG = true, + }) +end + +function Ammo:GetDisplayData(Data) + local Display = Ammo.BaseClass.GetDisplayData(self, Data) + local Energy = ACF_Kinetic(Data.MuzzleVel * 39.37, Data.ProjMass, Data.LimitVel) + + Display.MaxKETransfert = Energy.Kinetic * Data.ShovePower + + hook.Run("ACF_GetDisplayData", self, Data, Display) + + return Display +end + +function Ammo:UpdateRoundData(ToolData, Data, GUIData) + GUIData = GUIData or Data + + ACF.UpdateRoundSpecs(ToolData, Data, GUIData) + + local ProjMass = math.max(GUIData.ProjVolume * 0.5, 0) * 0.0079 --(Volume of the projectile as a cylinder - Volume of the cavity) * density of steel + local MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, ProjMass) + local Energy = ACF_Kinetic(MuzzleVel * 39.37, ProjMass, Data.LimitVel) + local MaxVol = ACF.RoundShellCapacity(Energy.Momentum, Data.FrArea, Data.Caliber, Data.ProjLength) + local MaxCavity = math.min(GUIData.ProjVolume, MaxVol) + local HollowCavity = math.Clamp(ToolData.HollowCavity, GUIData.MinCavVol, MaxCavity) + local ExpRatio = HollowCavity / GUIData.ProjVolume + + GUIData.MaxCavVol = MaxCavity + + Data.CavVol = HollowCavity + Data.ProjMass = (Data.FrArea * Data.ProjLength - HollowCavity) * 0.0079 --Volume of the projectile as a cylinder * fraction missing due to hollow point (Data5) * density of steel + Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, Data.ProjMass) + Data.ShovePower = 0.2 + ExpRatio * 0.5 + Data.ExpCaliber = Data.Caliber + ExpRatio * Data.ProjLength + Data.PenArea = (3.1416 * Data.ExpCaliber * 0.5) ^ 2 ^ ACF.PenAreaMod + Data.DragCoef = Data.FrArea * 0.0001 / Data.ProjMass + Data.CartMass = Data.PropMass + Data.ProjMass + + hook.Run("ACF_UpdateRoundData", self, ToolData, Data, GUIData) + + for K, V in pairs(self:GetDisplayData(Data)) do + GUIData[K] = V + end +end + +function Ammo:BaseConvert(ToolData) + local Data, GUIData = ACF.RoundBaseGunpowder(ToolData, {}) + + GUIData.MinCavVol = 0 + + Data.LimitVel = 400 --Most efficient penetration speed in m/s + Data.KETransfert = 0.1 --Kinetic energy transfert to the target for movement purposes + Data.Ricochet = 90 --Base ricochet angle + + self:UpdateRoundData(ToolData, Data, GUIData) + + return Data, GUIData +end + +function Ammo:VerifyData(ToolData) + Ammo.BaseClass.VerifyData(self, ToolData) + + if not ToolData.HollowCavity then + local Data5 = ToolData.RoundData5 + + ToolData.HollowCavity = Data5 and tonumber(Data5) or 0 + end +end + +if SERVER then + ACF.AddEntityArguments("acf_ammo", "HollowCavity") -- Adding extra info to ammo crates + + function Ammo:OnLast(Entity) + Ammo.BaseClass.OnLast(self, Entity) + + Entity.HollowCavity = nil + + -- Cleanup the leftovers aswell + Entity.RoundData5 = nil + end + + function Ammo:Network(Entity, BulletData) + Ammo.BaseClass.Network(self, Entity, BulletData) + + Entity:SetNW2String("AmmoType", "HP") + end + + function Ammo:GetCrateText(BulletData) + local BaseText = Ammo.BaseClass.GetCrateText(self, BulletData) + local Data = self:GetDisplayData(BulletData) + local Text = BaseText .. "\nExpanded Caliber: %s mm\nImparted Energy: %s KJ" + + return Text:format(math.Round(BulletData.ExpCaliber * 10, 2), math.Round(Data.MaxKETransfert, 2)) + end +else + ACF.RegisterAmmoDecal("HP", "damage/ap_pen", "damage/ap_rico") + + function Ammo:AddAmmoControls(Base, ToolData, BulletData) + local HollowCavity = Base:AddSlider("Cavity Volume", BulletData.MinCavVol, BulletData.MaxCavVol, 2) + HollowCavity:SetClientData("HollowCavity", "OnValueChanged") + HollowCavity:TrackClientData("Projectile") + HollowCavity:DefineSetter(function(Panel, _, Key, Value) + if Key == "HollowCavity" then + ToolData.HollowCavity = math.Round(Value, 2) + end + + self:UpdateRoundData(ToolData, BulletData) + + Panel:SetMax(BulletData.MaxCavVol) + Panel:SetValue(BulletData.CavVol) + + return BulletData.CavVol + end) + end + + function Ammo:AddCrateDataTrackers(Trackers, ...) + Ammo.BaseClass.AddCrateDataTrackers(self, Trackers, ...) + + Trackers.HollowCavity = true + end + + function Ammo:AddAmmoInformation(Base, ToolData, BulletData) + local RoundStats = Base:AddLabel() + RoundStats:TrackClientData("Projectile", "SetText") + RoundStats:TrackClientData("Propellant") + RoundStats:TrackClientData("HollowCavity") + RoundStats:DefineSetter(function() + self:UpdateRoundData(ToolData, BulletData) + + local Text = "Muzzle Velocity : %s m/s\nProjectile Mass : %s\nPropellant Mass : %s" + local MuzzleVel = math.Round(BulletData.MuzzleVel * ACF.Scale, 2) + local ProjMass = ACF.GetProperMass(BulletData.ProjMass) + local PropMass = ACF.GetProperMass(BulletData.PropMass) + + return Text:format(MuzzleVel, ProjMass, PropMass) + end) + + local HollowStats = Base:AddLabel() + HollowStats:TrackClientData("Projectile", "SetText") + HollowStats:TrackClientData("Propellant") + HollowStats:TrackClientData("HollowCavity") + HollowStats:DefineSetter(function() + self:UpdateRoundData(ToolData, BulletData) + + local Text = "Expanded Caliber : %s mm\nTransfered Energy : %s KJ" + local Caliber = math.Round(BulletData.ExpCaliber * 10, 2) + local Energy = math.Round(BulletData.MaxKETransfert, 2) + + return Text:format(Caliber, Energy) + end) + + local PenStats = Base:AddLabel() + PenStats:TrackClientData("Projectile", "SetText") + PenStats:TrackClientData("Propellant") + PenStats:TrackClientData("HollowCavity") + PenStats:DefineSetter(function() + self:UpdateRoundData(ToolData, BulletData) + + local Text = "Penetration : %s mm RHA\nAt 300m : %s mm RHA @ %s m/s\nAt 800m : %s mm RHA @ %s m/s" + local MaxPen = math.Round(BulletData.MaxPen, 2) + local R1V, R1P = ACF.PenRanging(BulletData.MuzzleVel, BulletData.DragCoef, BulletData.ProjMass, BulletData.PenArea, BulletData.LimitVel, 300) + local R2V, R2P = ACF.PenRanging(BulletData.MuzzleVel, BulletData.DragCoef, BulletData.ProjMass, BulletData.PenArea, BulletData.LimitVel, 800) + + return Text:format(MaxPen, R1P, R1V, R2P, R2V) + end) + + Base:AddLabel("Note: The penetration range data is an approximation and may not be entirely accurate.") + end +end \ No newline at end of file diff --git a/lua/acf/shared/ammo_types/refill.lua b/lua/acf/shared/ammo_types/refill.lua new file mode 100644 index 000000000..3c637bb56 --- /dev/null +++ b/lua/acf/shared/ammo_types/refill.lua @@ -0,0 +1,168 @@ +local Ammo = ACF.RegisterAmmoType("Refill", "AP") + +function Ammo:OnLoaded() + self.Name = "Refill" + self.Model = "models/Items/BoxSRounds.mdl" + self.Description = "Provides supplies to other ammo crates." + self.Blacklist = {} +end + +function Ammo:GetDisplayData() + return {} +end + +function Ammo:BaseConvert(ToolData) + local ProjMass = 5.5 * 0.079 + local PropMass = 0.001 + + return { + Id = "12.7mmMG", + Type = ToolData.AmmoType, + Caliber = 12.7, + ProjMass = ProjMass, + PropMass = PropMass, + CartMass = ProjMass + PropMass, + FillerMass = 0.001, + DragCoef = 0, + Tracer = 0, + MuzzleVel = 0, + RoundVolume = 35, + } +end + +function Ammo:VerifyData() +end + +if SERVER then + util.AddNetworkString("ACF_RefillEffect") + util.AddNetworkString("ACF_StopRefillEffect") + + local MaxDistance = ACF.RefillDistance * ACF.RefillDistance + local ActiveCrates = ACF.AmmoCrates + + local function CanRefillCrate(Refill, Crate, Distance) + if Refill == Crate then return false end + if not Refill:CanConsume() then return false end + if Crate.IsRefill then return false end + if Crate.Ammo >= Crate.Capacity then return false end + if Crate.Disabled then return false end + if Crate.Damaged then return false end + + return Distance <= MaxDistance + end + + local function RefillEffect(Entity) + net.Start("ACF_RefillEffect") + net.WriteEntity(Entity) + net.Broadcast() + end + + local function StopRefillEffect(Entity) + net.Start("ACF_StopRefillEffect") + net.WriteEntity(Entity) + net.Broadcast() + end + + local function RefillCrates(Refill) + local Position = Refill:GetPos() + + for Crate in pairs(ActiveCrates) do + local Distance = Position:DistToSqr(Crate:GetPos()) + + if CanRefillCrate(Refill, Crate, Distance) then + local Supply = math.ceil(ACF.RefillSpeed / Crate.BulletData.CartMass / Distance ^ 0.5) + local Transfer = math.min(Supply, Refill.Ammo, Crate.Capacity - Crate.Ammo) + + if hook.Run("ACF_CanRefill", Refill, Crate, Transfer) == false then continue end + + if not next(Refill.SupplyingTo) then + RefillEffect(Refill) + end + + if not Refill.SupplyingTo[Crate] then + Refill.SupplyingTo[Crate] = true + + Crate:CallOnRemove("ACF Refill " .. Refill:EntIndex(), function() + Refill.SupplyingTo[Crate] = nil + end) + end + + Crate:Consume(-Transfer) + Refill:Consume(Transfer) + + Crate:EmitSound("items/ammo_pickup.wav", 70, 100, 0.5 * ACF.Volume) + Refill:EmitSound("items/ammo_pickup.wav", 70, 100, 0.5 * ACF.Volume) + + elseif Refill.SupplyingTo[Crate] then + Refill.SupplyingTo[Crate] = nil + + Crate:RemoveCallOnRemove("ACF Refill " .. Refill:EntIndex()) + + if not next(Refill.SupplyingTo) then + StopRefillEffect(Refill) + end + end + end + end + + function Ammo:OnFirst(Entity) + if not Entity.IsAmmoCrate then return end + + if not Entity.SupplyingTo then + Entity.SupplyingTo = {} + end + + Entity.IsRefill = true + Entity.Unlinkable = true + + timer.Create("ACF Refill " .. Entity:EntIndex(), 1, 0, function() + if not IsValid(Entity) then return end + + RefillCrates(Entity) + end) + end + + function Ammo:OnLast(Entity) + if not Entity.IsRefill then return end + + local CallName = "ACF Refill " .. Entity:EntIndex() + + for Crate in pairs(Entity.SupplyingTo) do + Crate:RemoveCallOnRemove(CallName) + end + + Entity.SupplyingTo = nil + Entity.IsRefill = nil + Entity.Unlinkable = nil + + Entity:SetNW2Float("FillerMass", 0) + + StopRefillEffect(Refill) + + timer.Remove(CallName) + end + + function Ammo:Create() + print("Someone is trying to fire Refill bullets") + end + + function Ammo:Network(Entity, BulletData) + Ammo.BaseClass.Network(self, Entity, BulletData) + + Entity:SetNW2String("AmmoType", "Refill") + Entity:SetNW2Float("FillerMass", BulletData.FillerMass) + end + + function Ammo:GetCrateName() + return "Ammo Refill", "Refill", "Ammo Refill Crate" + end + + function Ammo:GetCrateText() + return "" + end +else + function Ammo:SetupAmmoMenuSettings(Settings) + Settings.SuppressControls = true + Settings.SuppressInformation = true + end +end diff --git a/lua/acf/shared/ammo_types/smoke.lua b/lua/acf/shared/ammo_types/smoke.lua new file mode 100644 index 000000000..62fbd0b9d --- /dev/null +++ b/lua/acf/shared/ammo_types/smoke.lua @@ -0,0 +1,286 @@ +local Ammo = ACF.RegisterAmmoType("SM", "AP") + +function Ammo:OnLoaded() + self.Name = "Smoke" + self.Description = "A shell filled white phosporous, detonating on impact. Smoke filler produces a long lasting cloud but takes a while to be effective, whereas WP filler quickly creates a cloud that also dissipates quickly." + self.Blacklist = { + AC = true, + AL = true, + GL = true, + MG = true, + SA = true, + HMG = true, + RAC = true, + } +end + +function Ammo:GetDisplayData(Data) + local SMFiller = math.min(math.log(1 + Data.FillerMass * 8 * 39.37) * 43.4216, 350) + local WPFiller = math.min(math.log(1 + Data.WPMass * 8 * 39.37) * 43.4216, 350) + local Display = { + SMFiller = SMFiller, + SMLife = math.Round(20 + SMFiller * 0.25, 2), + SMRadiusMin = math.Round(SMFiller * 1.25 * 0.15 * 0.0254, 2), + SMRadiusMax = math.Round(SMFiller * 1.25 * 2 * 0.0254, 2), + WPFiller = WPFiller, + WPLife = math.Round(6 + WPFiller * 0.1, 2), + WPRadiusMin = math.Round(WPFiller * 1.25 * 0.0254, 2), + WPRadiusMax = math.Round(WPFiller * 1.25 * 2 * 0.0254, 2), + } + + hook.Run("ACF_GetDisplayData", self, Data, Display) + + return Display +end + +function Ammo:UpdateRoundData(ToolData, Data, GUIData) + GUIData = GUIData or Data + + ACF.UpdateRoundSpecs(ToolData, Data, GUIData) + + Data.FillerPriority = Data.FillerPriority or "Smoke" + + -- Volume of the projectile as a cylinder - Volume of the filler * density of steel + Volume of the filler * density of TNT + local ProjMass = math.max(GUIData.ProjVolume - ToolData.SmokeFiller, 0) * 0.0079 + math.min(ToolData.SmokeFiller, GUIData.ProjVolume) * ACF.HEDensity * 0.0005 + local MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, ProjMass) + local Energy = ACF_Kinetic(MuzzleVel * 39.37, ProjMass, Data.LimitVel) + local MaxCapacity = ACF.RoundShellCapacity(Energy.Momentum, Data.FrArea, Data.Caliber, Data.ProjLength) + local MaxVolume = math.Round(math.min(GUIData.ProjVolume, MaxCapacity), 2) + local SmokeFiller = math.Clamp(ToolData.SmokeFiller, GUIData.MinFillerVol, MaxVolume) + local WPFiller = math.Clamp(ToolData.WPFiller, GUIData.MinFillerVol, MaxVolume) + + if Data.FillerPriority == "Smoke" then + WPFiller = math.Clamp(WPFiller, 0, MaxVolume - SmokeFiller) + elseif Data.FillerPriority == "WP" then + SmokeFiller = math.Clamp(SmokeFiller, 0, MaxVolume - WPFiller) + end + + GUIData.MaxFillerVol = MaxVolume + GUIData.FillerVol = math.Round(SmokeFiller, 2) + GUIData.WPVol = math.Round(WPFiller, 2) + + Data.FillerMass = GUIData.FillerVol * ACF.HEDensity * 0.0005 + Data.WPMass = GUIData.WPVol * ACF.HEDensity * 0.0005 + Data.ProjMass = math.max(GUIData.ProjVolume - (GUIData.FillerVol + GUIData.WPVol), 0) * 0.0079 + Data.FillerMass + Data.WPMass + Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, Data.ProjMass) + Data.DragCoef = Data.FrArea * 0.0001 / Data.ProjMass + Data.CartMass = Data.PropMass + Data.ProjMass + + hook.Run("ACF_UpdateRoundData", self, ToolData, Data, GUIData) + + for K, V in pairs(self:GetDisplayData(Data)) do + GUIData[K] = V + end +end + +function Ammo:BaseConvert(ToolData) + local Data, GUIData = ACF.RoundBaseGunpowder(ToolData, {}) + + GUIData.MinFillerVol = 0 + + Data.ShovePower = 0.1 + Data.PenArea = Data.FrArea ^ ACF.PenAreaMod + Data.LimitVel = 100 --Most efficient penetration speed in m/s + Data.KETransfert = 0.1 --Kinetic energy transfert to the target for movement purposes + Data.Ricochet = 60 --Base ricochet angle + Data.DetonatorAngle = 80 + Data.CanFuze = Data.Caliber * 10 > ACF.MinFuzeCaliber -- Can fuze on calibers > 20mm + + self:UpdateRoundData(ToolData, Data, GUIData) + + return Data, GUIData +end + +function Ammo:VerifyData(ToolData) + Ammo.BaseClass.VerifyData(self, ToolData) + + if not ToolData.SmokeFiller then + local Data5 = ToolData.RoundData5 + + ToolData.SmokeFiller = Data5 and tonumber(Data5) or 0 + end + + if not ToolData.WPFiller then + local Data6 = ToolData.RoundData6 + + ToolData.WPFiller = Data6 and tonumber(Data6) or 0 + end +end + +if SERVER then + ACF.AddEntityArguments("acf_ammo", "SmokeFiller", "WPFiller") -- Adding extra info to ammo crates + + function Ammo:OnLast(Entity) + Ammo.BaseClass.OnLast(self, Entity) + + Entity.SmokeFiller = nil + Entity.WPFiller = nil + + -- Cleanup the leftovers aswell + Entity.RoundData5 = nil + Entity.RoundData6 = nil + + Entity:SetNW2Float("FillerMass", 0) + Entity:SetNW2Float("WPMass", 0) + end + + function Ammo:Network(Entity, BulletData) + Ammo.BaseClass.Network(self, Entity, BulletData) + + Entity:SetNW2String("AmmoType", "SM") + Entity:SetNW2Float("FillerMass", BulletData.FillerMass) + Entity:SetNW2Float("WPMass", BulletData.WPMass) + end + + function Ammo:GetCrateText(BulletData) + local Text = "Muzzle Velocity: %s m/s%s%s" + local Data = self:GetDisplayData(BulletData) + local WPText, SMText = "", "" + + + if Data.WPFiller > 0 then + local Template = "\nWP Radius: %s m to %s m\nWP Lifetime: %s s" + + WPText = Template:format(Data.WPRadiusMin, Data.WPRadiusMax, Data.WPLife) + end + + if Data.SMFiller > 0 then + local Template = "\nSM Radius: %s m to %s m\nSM Lifetime: %s s" + + SMText = Template:format(Data.SMRadiusMin, Data.SMRadiusMax, Data.SMLife) + end + + return Text:format(math.Round(BulletData.MuzzleVel, 2), WPText, SMText) + end + + function Ammo:PropImpact(Bullet, Trace) + local Target = Trace.Entity + + if ACF.Check(Target) then + local Speed = Bullet.Flight:Length() / ACF.Scale + local Energy = ACF_Kinetic(Speed, Bullet.ProjMass - (Bullet.FillerMass + Bullet.WPMass), Bullet.LimitVel) + local HitRes = ACF_RoundImpact(Bullet, Speed, Energy, Target, Trace.HitPos, Trace.HitNormal, Trace.HitGroup) + + if HitRes.Ricochet then return "Ricochet" end + end + + return false + end + + function Ammo:WorldImpact() + return false + end +else + ACF.RegisterAmmoDecal("SM", "damage/he_pen", "damage/he_rico") + + function Ammo:ImpactEffect(_, Bullet) + local Crate = Bullet.Crate + local Color = IsValid(Crate) and Crate:GetColor() or Color(255, 255, 255) + + local Effect = EffectData() + Effect:SetOrigin(Bullet.SimPos) + Effect:SetNormal(Bullet.SimFlight:GetNormalized()) + Effect:SetScale(math.max(Bullet.FillerMass * 8 * 39.37, 0)) + Effect:SetMagnitude(math.max(Bullet.WPMass * 8 * 39.37, 0)) + Effect:SetStart(Vector(Color.r, Color.g, Color.b)) + Effect:SetRadius(Bullet.Caliber) + + util.Effect("ACF_Smoke", Effect) + end + + function Ammo:AddAmmoControls(Base, ToolData, BulletData) + local SmokeFiller = Base:AddSlider("Smoke Filler", BulletData.MinFillerVol, BulletData.MaxFillerVol, 2) + SmokeFiller:SetClientData("SmokeFiller", "OnValueChanged") + SmokeFiller:TrackClientData("Projectile") + SmokeFiller:TrackClientData("WPFiller") + SmokeFiller:DefineSetter(function(Panel, _, Key, Value, IsTracked) + if Key == "SmokeFiller" then + ToolData.SmokeFiller = math.Round(Value, 2) + + if not IsTracked then + BulletData.FillerPriority = "Smoke" + end + end + + self:UpdateRoundData(ToolData, BulletData) + + Panel:SetMax(BulletData.MaxFillerVol) + Panel:SetValue(BulletData.FillerVol) + + return BulletData.FillerVol + end) + + local WPFiller = Base:AddSlider("WP Filler", BulletData.MinFillerVol, BulletData.MaxFillerVol, 2) + WPFiller:SetClientData("WPFiller", "OnValueChanged") + WPFiller:TrackClientData("SmokeFiller") + WPFiller:TrackClientData("Projectile") + WPFiller:DefineSetter(function(Panel, _, Key, Value, IsTracked) + if Key == "WPFiller" then + ToolData.WPFiller = math.Round(Value, 2) + + if not IsTracked then + BulletData.FillerPriority = "WP" + end + end + + self:UpdateRoundData(ToolData, BulletData) + + Panel:SetMax(BulletData.MaxFillerVol) + Panel:SetValue(BulletData.WPVol) + + return BulletData.WPVol + end) + end + + function Ammo:AddCrateDataTrackers(Trackers, ...) + Ammo.BaseClass.AddCrateDataTrackers(self, Trackers, ...) + + Trackers.SmokeFiller = true + Trackers.WPFiller = true + end + + function Ammo:AddAmmoInformation(Menu, ToolData, Data) + local RoundStats = Menu:AddLabel() + RoundStats:TrackClientData("Projectile", "SetText") + RoundStats:TrackClientData("Propellant") + RoundStats:TrackClientData("SmokeFiller") + RoundStats:TrackClientData("WPFiller") + RoundStats:DefineSetter(function() + self:UpdateRoundData(ToolData, Data) + + local Text = "Muzzle Velocity : %s m/s\nProjectile Mass : %s\nPropellant Mass : %s" + local MuzzleVel = math.Round(Data.MuzzleVel * ACF.Scale, 2) + local ProjMass = ACF.GetProperMass(Data.ProjMass) + local PropMass = ACF.GetProperMass(Data.PropMass) + + return Text:format(MuzzleVel, ProjMass, PropMass) + end) + + local SmokeStats = Menu:AddLabel() + SmokeStats:TrackClientData("SmokeFiller", "SetText") + SmokeStats:TrackClientData("WPFiller") + SmokeStats:DefineSetter(function() + self:UpdateRoundData(ToolData, Data) + + local SMText, WPText = "", "" + + if Data.FillerMass > 0 then + local Text = "Smoke Filler Mass : %s\nSmoke Filler Radius : %s m\nSmoke Filler Life : %s s\n" + local SmokeMass = ACF.GetProperMass(Data.FillerMass) + local SmokeRadius = (Data.SMRadiusMin + Data.SMRadiusMax) * 0.5 + + SMText = Text:format(SmokeMass, SmokeRadius, Data.SMLife) + end + + if Data.WPMass > 0 then + local Text = "WP Filler Mass : %s\nWP Filler Radius : %s m\nWP Filler Life : %s s" + local WPMass = ACF.GetProperMass(Data.WPMass) + local WPRadius = (Data.WPRadiusMin + Data.WPRadiusMax) * 0.5 + + WPText = Text:format(WPMass, WPRadius, Data.WPLife) + end + + return SMText .. WPText + end) + end +end diff --git a/lua/acf/shared/data_callbacks.lua b/lua/acf/shared/data_callbacks.lua new file mode 100644 index 000000000..f2e756553 --- /dev/null +++ b/lua/acf/shared/data_callbacks.lua @@ -0,0 +1,126 @@ +local ACF = ACF +local Message = SERVER and ACF.PrintLog or ACF.PrintToChat + +local Names = { + [1] = "Sandbox", + [2] = "Classic", + [3] = "Competitive" +} + +local Settings = { + Gamemode = function(_, _, Value) + local Mode = math.Clamp(math.floor(tonumber(Value) or 2), 1, 3) + + if Mode == ACF.Gamemode then return end + + ACF.Gamemode = Mode + + Message("Info", "ACF Gamemode has been changed to " .. Names[Mode]) + end, + ServerDataAllowAdmin = function(_, _, Value) + ACF.AllowAdminData = tobool(Value) + end, + RestrictInfo = function(_, _, Value) + ACF.RestrictInfo = tobool(Value) + end, + GunfireEnabled = function(_, _, Value) + local Bool = tobool(Value) + + if ACF.GunfireEnabled == Bool then return end + + ACF.GunfireEnabled = Bool + + Message("Info", "ACF Gunfire has been " .. (Bool and "enabled." or "disabled.")) + end, + HealthFactor = function(_, _, Value) + local Factor = math.Clamp(math.Round(tonumber(Value) or 1, 2), 0.01, 2) + + if ACF.HealthFactor == Factor then return end + + local Old = ACF.HealthFactor + + ACF.HealthFactor = Factor + ACF.Threshold = ACF.Threshold / Old * Factor + + Message("Info", "ACF Health Mod changed to a factor of " .. Factor) + end, + ArmorFactor = function(_, _, Value) + local Factor = math.Clamp(math.Round(tonumber(Value) or 1, 2), 0.01, 2) + + if ACF.ArmorFactor == Factor then return end + + local Old = ACF.ArmorFactor + + ACF.ArmorFactor = Factor + ACF.ArmorMod = ACF.ArmorMod / Old * Factor + + Message("Info", "ACF Armor Mod changed to a factor of " .. Factor) + end, + FuelFactor = function(_, _, Value) + local Factor = math.Clamp(math.Round(tonumber(Value) or 1, 2), 0.01, 2) + + if ACF.FuelFactor == Factor then return end + + local Old = ACF.FuelFactor + + ACF.FuelFactor = Factor + ACF.FuelRate = ACF.FuelRate / Old * Factor + + Message("Info", "ACF Fuel Rate changed to a factor of " .. Factor) + end, + CompFuelFactor = function(_, _, Value) + local Factor = math.Clamp(math.Round(tonumber(Value) or 1, 2), 0.01, 2) + + if ACF.CompFuelFactor == Factor then return end + + local Old = ACF.CompFuelFactor + + ACF.CompFuelFactor = Factor + ACF.CompFuelRate = ACF.CompFuelRate / Old * Factor + + Message("Info", "ACF Competitive Fuel Rate changed to a factor of " .. Factor) + end, + HEPush = function(_, _, Value) + ACF.HEPush = tobool(Value) + end, + KEPush = function(_, _, Value) + ACF.KEPush = tobool(Value) + end, + RecoilPush = function(_, _, Value) + ACF.RecoilPush = tobool(Value) + end, + AllowFunEnts = function(_, _, Value) + ACF.AllowFunEnts = tobool(Value) + end, + WorkshopContent = function(_, _, Value) + local Bool = tobool(Value) + + if ACF.WorkshopContent == Bool then return end + + ACF.WorkshopContent = Bool + + Message("Info", "ACF Workshop Content download has been " .. (Bool and "enabled." or "disabled.")) + end, + WorkshopExtras = function(_, _, Value) + local Bool = tobool(Value) + + if ACF.WorkshopExtras == Bool then return end + + ACF.WorkshopExtras = Bool + + Message("Info", "ACF Extra Workshop Content download has been " .. (Bool and "enabled." or "disabled.")) + end, +} + +for Key, Function in pairs(Settings) do + ACF.AddServerDataCallback(Key, "Global Variable Callback", Function) +end + +do -- Volume setting callback + local Realm = SERVER and "Server" or "Client" + local Callback = ACF["Add" .. Realm .. "DataCallback"] + + Callback("Volume", "Volume Variable Callback", function(_, _, Value) + ACF.Volume = math.Clamp(tonumber(Value) or 1, 0, 1) + end) +end diff --git a/lua/acf/shared/engine_types/electric.lua b/lua/acf/shared/engine_types/electric.lua new file mode 100644 index 000000000..28f3501d3 --- /dev/null +++ b/lua/acf/shared/engine_types/electric.lua @@ -0,0 +1,17 @@ +ACF.RegisterEngineType("Electric", { + Name = "Generic Electric Engine", + Efficiency = 0.85, --percent efficiency converting chemical kw into mechanical kw + TorqueScale = 0.5, + HealthMult = 0.75, + CalculatePeakEnergy = function(Entity) + -- Adjust torque to 1 rpm maximum, assuming a linear decrease from a max @ 1 rpm to min @ limiter + local peakkw = (Entity.PeakTorque * (1 + Entity.PeakMaxRPM / Entity.LimitRPM)) * Entity.LimitRPM / (4 * 9548.8) + local PeakKwRPM = math.floor(Entity.LimitRPM * 0.5) + + return peakkw, PeakKwRPM + end, + CalculateFuelUsage = function(Entity) + -- Electric engines use current power output, not max + return ACF.FuelRate * Entity.Efficiency / 3600 + end +}) diff --git a/lua/acf/shared/engine_types/generic_diesel.lua b/lua/acf/shared/engine_types/generic_diesel.lua new file mode 100644 index 000000000..f1a1e27d5 --- /dev/null +++ b/lua/acf/shared/engine_types/generic_diesel.lua @@ -0,0 +1,6 @@ +ACF.RegisterEngineType("GenericDiesel", { + Name = "Generic Diesel Engine", + Efficiency = 0.243, --up to 0.274 + TorqueScale = 0.35, + HealthMult = 0.5, +}) diff --git a/lua/acf/shared/engine_types/generic_petrol.lua b/lua/acf/shared/engine_types/generic_petrol.lua new file mode 100644 index 000000000..55e2286e6 --- /dev/null +++ b/lua/acf/shared/engine_types/generic_petrol.lua @@ -0,0 +1,6 @@ +ACF.RegisterEngineType("GenericPetrol", { + Name = "Generic Petrol Engine", + Efficiency = 0.304, --kg per kw hr + TorqueScale = 0.25, + HealthMult = 0.2, +}) diff --git a/lua/acf/shared/engine_types/radial.lua b/lua/acf/shared/engine_types/radial.lua new file mode 100644 index 000000000..1c7c8296f --- /dev/null +++ b/lua/acf/shared/engine_types/radial.lua @@ -0,0 +1,6 @@ +ACF.RegisterEngineType("Radial", { + Name = "Generic Radial Engine", + Efficiency = 0.4, -- 0.38 to 0.53 + TorqueScale = 0.3, + HealthMult = 0.3, +}) diff --git a/lua/acf/shared/engine_types/turbine.lua b/lua/acf/shared/engine_types/turbine.lua new file mode 100644 index 000000000..66cf8ea02 --- /dev/null +++ b/lua/acf/shared/engine_types/turbine.lua @@ -0,0 +1,13 @@ +ACF.RegisterEngineType("Turbine", { + Name = "Generic Turbine", + Efficiency = 0.375, -- previously 0.231 + TorqueScale = 0.2, + HealthMult = 0.125, + CalculatePeakEnergy = function(Entity) + -- Adjust torque to 1 rpm maximum, assuming a linear decrease from a max @ 1 rpm to min @ limiter + local peakkw = (Entity.PeakTorque * (1 + Entity.PeakMaxRPM / Entity.LimitRPM)) * Entity.LimitRPM / (4 * 9548.8) + local PeakKwRPM = math.floor(Entity.LimitRPM * 0.5) + + return peakkw, PeakKwRPM + end, +}) diff --git a/lua/acf/shared/engine_types/wankel.lua b/lua/acf/shared/engine_types/wankel.lua new file mode 100644 index 000000000..1fc873469 --- /dev/null +++ b/lua/acf/shared/engine_types/wankel.lua @@ -0,0 +1,6 @@ +ACF.RegisterEngineType("Wankel", { + Name = "Generic Wankel Engine", + Efficiency = 0.335, + TorqueScale = 0.2, + HealthMult = 0.125, +}) diff --git a/lua/acf/shared/engines/b4.lua b/lua/acf/shared/engines/b4.lua index a525b1335..460cf8da3 100644 --- a/lua/acf/shared/engines/b4.lua +++ b/lua/acf/shared/engines/b4.lua @@ -1,70 +1,83 @@ -- Flat 4 engines -ACF_DefineEngine( "1.4-B4", { - name = "1.4L Flat 4 Petrol", - desc = "Small air cooled flat four, most commonly found in nazi insects", - model = "models/engines/b4small.mdl", - sound = "acf_base/engines/b4_petrolsmall.wav", - category = "B4", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 60, - torque = 131, - flywheelmass = 0.06, - idlerpm = 600, - peakminrpm = 2600, - peakmaxrpm = 4200, - limitrpm = 4500 -} ) +ACF.RegisterEngineClass("B4", { + Name = "Flat 4 Engine", +}) -ACF_DefineEngine( "2.1-B4", { - name = "2.1L Flat 4 Petrol", - desc = "Tuned up flat four, probably find this in things that go fast in a desert.", - model = "models/engines/b4small.mdl", - sound = "acf_base/engines/b4_petrolmedium.wav", - category = "B4", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 125, - torque = 225, - flywheelmass = 0.15, - idlerpm = 700, - peakminrpm = 3000, - peakmaxrpm = 4800, - limitrpm = 5000 -} ) +do + ACF.RegisterEngine("1.4-B4", "B4", { + Name = "1.4L Flat 4 Petrol", + Description = "Small air cooled flat four, most commonly found in nazi insects", + Model = "models/engines/b4small.mdl", + Sound = "acf_base/engines/b4_petrolsmall.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 60, + Torque = 131, + FlywheelMass = 0.06, + RPM = { + Idle = 600, + PeakMin = 2600, + PeakMax = 4200, + Limit = 4500, + }, + }) -ACF_DefineEngine( "3.2-B4", { - name = "3.2L Flat 4 Petrol", - desc = "Bored out fuckswindleton batshit flat four. Fuck yourself.", - model = "models/engines/b4med.mdl", - sound = "acf_base/engines/b4_petrollarge.wav", - category = "B4", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 210, - torque = 315, - flywheelmass = 0.15, - idlerpm = 900, - peakminrpm = 3400, - peakmaxrpm = 5500, - limitrpm = 6500 -} ) + ACF.RegisterEngine("2.1-B4", "B4", { + Name = "2.1L Flat 4 Petrol", + Description = "Tuned up flat four, probably find this in things that go fast in a desert.", + Model = "models/engines/b4small.mdl", + Sound = "acf_base/engines/b4_petrolmedium.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 125, + Torque = 225, + FlywheelMass = 0.15, + RPM = { + Idle = 700, + PeakMin = 3000, + PeakMax = 4800, + Limit = 5000, + }, + }) -ACF_DefineEngine( "2.4-B4", { - name = "2.4L Flat 4 Multifuel", - desc = "Tiny military-grade multifuel. Heavy, but grunts hard.", - model = "models/engines/b4small.mdl", - sound = "acf_extra/vehiclefx/engines/coh/ba11.wav", - category = "B4", - fuel = "Multifuel", - enginetype = "GenericDiesel", - weight = 135, - torque = 310, - flywheelmass = 0.4, - idlerpm = 550, - peakminrpm = 1250, - peakmaxrpm = 2650, - limitrpm = 2800 -} ) + ACF.RegisterEngine("2.4-B4", "B4", { + Name = "2.4L Flat 4 Multifuel", + Description = "Tiny military-grade multifuel. Heavy, but grunts hard.", + Model = "models/engines/b4small.mdl", + Sound = "acf_extra/vehiclefx/engines/coh/ba11.wav", + Fuel = { Petrol = true, Diesel = true }, + Type = "GenericDiesel", + Mass = 135, + Torque = 310, + FlywheelMass = 0.4, + RPM = { + Idle = 550, + PeakMin = 1250, + PeakMax = 2650, + Limit = 2800, + }, + }) + + ACF.RegisterEngine("3.2-B4", "B4", { + Name = "3.2L Flat 4 Petrol", + Description = "Bored out fuckswindleton batshit flat four. Fuck yourself.", + Model = "models/engines/b4med.mdl", + Sound = "acf_base/engines/b4_petrollarge.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 210, + Torque = 315, + FlywheelMass = 0.15, + RPM = { + Idle = 900, + PeakMin = 3400, + PeakMax = 5500, + Limit = 6500 + }, + }) +end + +ACF.SetCustomAttachment("models/engines/b4med.mdl", "driveshaft", Vector(), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/engines/b4small.mdl", "driveshaft", Vector(), Angle(0, 0, 90)) diff --git a/lua/acf/shared/engines/b6.lua b/lua/acf/shared/engines/b6.lua index e06a7a556..2189e4461 100644 --- a/lua/acf/shared/engines/b6.lua +++ b/lua/acf/shared/engines/b6.lua @@ -1,71 +1,84 @@ -- Flat 6 engines -ACF_DefineEngine( "2.8-B6", { - name = "2.8L Flat 6 Petrol", - desc = "Car sized flat six engine, sporty and light", - model = "models/engines/b6small.mdl", - sound = "acf_base/engines/b6_petrolsmall.wav", - category = "B6", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 100, - torque = 170, - flywheelmass = 0.08, - idlerpm = 750, - peakminrpm = 4300, - peakmaxrpm = 6950, - limitrpm = 7250 -} ) +ACF.RegisterEngineClass("B6", { + Name = "Flat 6 Engine", +}) -ACF_DefineEngine( "5.0-B6", { - name = "5.0L Flat 6 Petrol", - desc = "Sports car grade flat six, renown for their smooth operation and light weight", - model = "models/engines/b6med.mdl", - sound = "acf_base/engines/b6_petrolmedium.wav", - category = "B6", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 240, - torque = 412, - flywheelmass = 0.11, - idlerpm = 900, - peakminrpm = 3500, - peakmaxrpm = 6000, - limitrpm = 6800 -} ) +do + ACF.RegisterEngine("2.8-B6", "B6", { + Name = "2.8L Flat 6 Petrol", + Description = "Car sized flat six engine, sporty and light", + Model = "models/engines/b6small.mdl", + Sound = "acf_base/engines/b6_petrolsmall.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 100, + Torque = 170, + FlywheelMass = 0.08, + RPM = { + Idle = 750, + PeakMin = 4300, + PeakMax = 6950, + Limit = 7250, + }, + }) -ACF_DefineEngine( "8.3-B6", { - name = "8.3L Flat 6 Multifuel", - desc = "Military-grade multifuel boxer engine. Although heavy, it is compact, durable, and has excellent performance under adverse conditions.", - model = "models/engines/b6med.mdl", - sound = "acf_base/engines/v8_diesel.wav", - category = "B6", - fuel = "Multifuel", - enginetype = "GenericDiesel", - weight = 480, - torque = 706, - flywheelmass = 0.65, - idlerpm = 500, - peakminrpm = 1900, - peakmaxrpm = 3600, - limitrpm = 4200 -} ) + ACF.RegisterEngine("5.0-B6", "B6", { + Name = "5.0L Flat 6 Petrol", + Description = "Sports car grade flat six, renown for their smooth operation and light weight", + Model = "models/engines/b6med.mdl", + Sound = "acf_base/engines/b6_petrolmedium.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 240, + Torque = 412, + FlywheelMass = 0.11, + RPM = { + Idle = 900, + PeakMin = 3500, + PeakMax = 6000, + Limit = 6800, + }, + }) + ACF.RegisterEngine("8.3-B6", "B6", { + Name = "8.3L Flat 6 Multifuel", + Description = "Military-grade multifuel boxer engine. Although heavy, it is compact, durable, and has excellent performance under adverse conditions.", + Model = "models/engines/b6med.mdl", + Sound = "acf_base/engines/v8_diesel.wav", + Fuel = { Petrol = true, Diesel = true }, + Type = "GenericDiesel", + Mass = 480, + Torque = 706, + FlywheelMass = 0.65, + RPM = { + Idle = 500, + PeakMin = 1900, + PeakMax = 3600, + Limit = 4200, + }, + }) -ACF_DefineEngine( "15.8-B6", { - name = "15.8L Flat 6 Petrol", - desc = "Monstrous aircraft-grade boxer with a high rev range biased powerband", - model = "models/engines/b6large.mdl", - sound = "acf_base/engines/b6_petrollarge.wav", - category = "B6", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 725, - torque = 1375, - flywheelmass = 1, - idlerpm = 620, - peakminrpm = 2500, - peakmaxrpm = 4275, - limitrpm = 4900 -} ) + ACF.RegisterEngine("15.8-B6", "B6", { + Name = "15.8L Flat 6 Petrol", + Description = "Monstrous aircraft-grade boxer with a high rev range biased powerband", + Model = "models/engines/b6large.mdl", + Sound = "acf_base/engines/b6_petrollarge.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 725, + Torque = 1375, + FlywheelMass = 1, + RPM = { + Idle = 620, + PeakMin = 2500, + PeakMax = 4275, + Limit = 4900, + }, + }) +end + +ACF.SetCustomAttachment("models/engines/b6large.mdl", "driveshaft", Vector(), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/engines/b6med.mdl", "driveshaft", Vector(), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/engines/b6small.mdl", "driveshaft", Vector(), Angle(0, 0, 90)) diff --git a/lua/acf/shared/engines/electric.lua b/lua/acf/shared/engines/electric.lua index 58c67d6e4..d07964987 100644 --- a/lua/acf/shared/engines/electric.lua +++ b/lua/acf/shared/engines/electric.lua @@ -1,135 +1,164 @@ -- Electric motors -ACF_DefineEngine( "Electric-Small", { - name = "Electric motor, Small", - desc = "A small electric motor, loads of torque, but low power\n\nElectric motors provide huge amounts of torque, but are very heavy", - model = "models/engines/emotorsmall.mdl", - sound = "acf_base/engines/electric_small.wav", - category = "Electric", - fuel = "Electric", - enginetype = "Electric", - weight = 250, - torque = 480, - flywheelmass = 0.3, - idlerpm = 10, - peakminrpm = 1, - peakmaxrpm = 1, - limitrpm = 10000, - iselec = true, - flywheeloverride = 5000 -} ) +do -- Electric Motors + ACF.RegisterEngineClass("EL", { + Name = "Electric Motor", + Description = "Electric motors provide huge amounts of torque, but are very heavy.", + }) -ACF_DefineEngine( "Electric-Medium", { - name = "Electric motor, Medium", - desc = "A medium electric motor, loads of torque, but low power\n\nElectric motors provide huge amounts of torque, but are very heavy", - model = "models/engines/emotormed.mdl", - sound = "acf_base/engines/electric_medium.wav", - category = "Electric", - fuel = "Electric", - enginetype = "Electric", - weight = 850, - torque = 1440, - flywheelmass = 1.5, - idlerpm = 10, - peakminrpm = 1, - peakmaxrpm = 1, - limitrpm = 7000, - iselec = true, - flywheeloverride = 8000 -} ) + ACF.RegisterEngine("Electric-Small", "EL", { + Name = "Small Electric Motor", + Description = "A small electric motor, loads of torque, but low power.", + Model = "models/engines/emotorsmall.mdl", + Sound = "acf_base/engines/electric_small.wav", + Fuel = { Electric = true }, + Type = "Electric", + Mass = 250, + Torque = 480, + FlywheelMass = 0.3, + IsElectric = true, + RPM = { + Idle = 10, + PeakMin = 1, + PeakMax = 1, + Limit = 10000, + Override = 5000, + }, + }) -ACF_DefineEngine( "Electric-Large", { - name = "Electric motor, Large", - desc = "A huge electric motor, loads of torque, but low power\n\nElectric motors provide huge amounts of torque, but are very heavy", - model = "models/engines/emotorlarge.mdl", - sound = "acf_base/engines/electric_large.wav", - category = "Electric", - fuel = "Electric", - enginetype = "Electric", - weight = 1900, - torque = 4200, - flywheelmass = 11.2, - idlerpm = 10, - peakminrpm = 1, - peakmaxrpm = 1, - limitrpm = 4500, - iselec = true, - flywheeloverride = 6000 -} ) + ACF.RegisterEngine("Electric-Medium", "EL", { + Name = "Medium Electric Motor", + Description = "A medium electric motor, loads of torque, but low power.", + Model = "models/engines/emotormed.mdl", + Sound = "acf_base/engines/electric_medium.wav", + Fuel = { Electric = true }, + Type = "Electric", + Mass = 850, + Torque = 1440, + FlywheelMass = 1.5, + IsElectric = true, + RPM = { + Idle = 10, + PeakMin = 1, + PeakMax = 1, + Limit = 7000, + Override = 8000, + } + }) -ACF_DefineEngine( "Electric-Tiny-NoBatt", { - name = "Electric motor, Tiny, Standalone", - desc = "A pint-size electric motor, for the lightest of light utility work. Can power electric razors, desk fans, or your hopes and dreams\n\nElectric motors provide huge amounts of torque, but are very heavy.\n\nStandalone electric motors don't have integrated batteries, saving on weight and volume, but require you to supply your own batteries.", - model = "models/engines/emotor-standalone-tiny.mdl", - sound = "acf_base/engines/electric_small.wav", - category = "Electric", - fuel = "Electric", - enginetype = "Electric", - weight = 50, --250 - torque = 40, - flywheelmass = 0.025, - idlerpm = 10, - peakminrpm = 1, - peakmaxrpm = 1, - limitrpm = 10000, - iselec = true, - flywheeloverride = 500 -} ) + ACF.RegisterEngine("Electric-Large", "EL", { + Name = "Large Electric Motor", + Description = "A huge electric motor, loads of torque, but low power.", + Model = "models/engines/emotorlarge.mdl", + Sound = "acf_base/engines/electric_large.wav", + Fuel = { Electric = true }, + Type = "Electric", + Mass = 1900, + Torque = 4200, + FlywheelMass = 11.2, + IsElectric = true, + RPM = { + Idle = 10, + PeakMin = 1, + PeakMax = 1, + Limit = 4500, + Override = 6000, + }, + }) +end -ACF_DefineEngine( "Electric-Small-NoBatt", { - name = "Electric motor, Small, Standalone", - desc = "A small electric motor, loads of torque, but low power\n\nElectric motors provide huge amounts of torque, but are very heavy.\n\nStandalone electric motors don't have integrated batteries, saving on weight and volume, but require you to supply your own batteries.", - model = "models/engines/emotor-standalone-sml.mdl", - sound = "acf_base/engines/electric_small.wav", - category = "Electric", - fuel = "Electric", - enginetype = "Electric", - weight = 125, --250 - torque = 384, - flywheelmass = 0.3, - idlerpm = 10, - peakminrpm = 1, - peakmaxrpm = 1, - limitrpm = 10000, - iselec = true, - flywheeloverride = 5000 -} ) +do -- Electric Standalone Motors + ACF.RegisterEngineClass("EL-S", { + Name = "Electric Standalone Motor", + Description = "Electric motors provide huge amounts of torque, but are very heavy. Standalones also require external batteries.", + }) -ACF_DefineEngine( "Electric-Medium-NoBatt", { - name = "Electric motor, Medium, Standalone", - desc = "A medium electric motor, loads of torque, but low power\n\nElectric motors provide huge amounts of torque, but are very heavy.\n\nStandalone electric motors don't have integrated batteries, saving on weight and volume, but require you to supply your own batteries.", - model = "models/engines/emotor-standalone-mid.mdl", - sound = "acf_base/engines/electric_medium.wav", - category = "Electric", - fuel = "Electric", - enginetype = "Electric", - weight = 575, --800 - torque = 1152, - flywheelmass = 1.5, - idlerpm = 10, - peakminrpm = 1, - peakmaxrpm = 1, - limitrpm = 7000, - iselec = true, - flywheeloverride = 8000 -} ) + ACF.RegisterEngine("Electric-Tiny-NoBatt", "EL-S", { + Name = "Tiny Electric Standalone Motor", + Description = "A pint-size electric motor, for the lightest of light utility work. Can power electric razors, desk fans, or your hopes and dreams.", + Model = "models/engines/emotor-standalone-tiny.mdl", + Sound = "acf_base/engines/electric_small.wav", + Fuel = { Electric = true }, + Type = "Electric", + Mass = 50, + Torque = 40, + FlywheelMass = 0.025, + IsElectric = true, + RPM = { + Idle = 10, + PeakMin = 1, + PeakMax = 1, + Limit = 10000, + Override = 500, + }, + }) -ACF_DefineEngine( "Electric-Large-NoBatt", { - name = "Electric motor, Large, Standalone", - desc = "A huge electric motor, loads of torque, but low power\n\nElectric motors provide huge amounts of torque, but are very heavy.\n\nStandalone electric motors don't have integrated batteries, saving on weight and volume, but require you to supply your own batteries.", - model = "models/engines/emotor-standalone-big.mdl", - sound = "acf_base/engines/electric_large.wav", - category = "Electric", - fuel = "Electric", - enginetype = "Electric", - weight = 1500, --1900 - torque = 3360, - flywheelmass = 11.2, - idlerpm = 10, - peakminrpm = 1, - peakmaxrpm = 1, - limitrpm = 4500, - iselec = true, - flywheeloverride = 6000 -} ) + ACF.RegisterEngine("Electric-Small-NoBatt", "EL-S", { + Name = "Small Electric Standalone Motor", + Description = "A small standalone electric motor, loads of torque, but low power.", + Model = "models/engines/emotor-standalone-sml.mdl", + Sound = "acf_base/engines/electric_small.wav", + Fuel = { Electric = true }, + Type = "Electric", + Mass = 125, + Torque = 384, + FlywheelMass = 0.3, + IsElectric = true, + RPM = { + Idle = 10, + PeakMin = 1, + PeakMax = 1, + Limit = 10000, + Override = 5000, + } + }) + + ACF.RegisterEngine("Electric-Medium-NoBatt", "EL-S", { + Name = "Medium Electric Standalone Motor", + Description = "A medium standalone electric motor, loads of torque, but low power.", + Model = "models/engines/emotor-standalone-mid.mdl", + Sound = "acf_base/engines/electric_medium.wav", + Fuel = { Electric = true }, + Type = "Electric", + Mass = 575, + Torque = 1152, + FlywheelMass = 1.5, + IsElectric = true, + RPM = { + Idle = 10, + PeakMin = 1, + PeakMax = 1, + Limit = 7000, + Override = 8000, + }, + }) + + ACF.RegisterEngine("Electric-Large-NoBatt", "EL-S", { + Name = "Large Electric Standalone Motor", + Description = "A huge standalone electric motor, loads of torque, but low power.", + Model = "models/engines/emotor-standalone-big.mdl", + Sound = "acf_base/engines/electric_large.wav", + Fuel = { Electric = true }, + Type = "Electric", + Mass = 1500, + Torque = 3360, + FlywheelMass = 11.2, + IsElectric = true, + RPM = { + Idle = 10, + PeakMin = 1, + PeakMax = 1, + Limit = 4500, + Override = 6000, + } + }) +end + +ACF.SetCustomAttachment("models/engines/emotorlarge.mdl", "driveshaft", Vector(), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/engines/emotormed.mdl", "driveshaft", Vector(), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/engines/emotorsmall.mdl", "driveshaft", Vector(), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/engines/emotor-standalone-big.mdl", "driveshaft", Vector(), Angle(0, -90, 90)) +ACF.SetCustomAttachment("models/engines/emotor-standalone-mid.mdl", "driveshaft", Vector(), Angle(0, -90, 90)) +ACF.SetCustomAttachment("models/engines/emotor-standalone-sml.mdl", "driveshaft", Vector(), Angle(0, -90, 90)) +ACF.SetCustomAttachment("models/engines/emotor-standalone-tiny.mdl", "driveshaft", Vector(), Angle(0, -90, 90)) diff --git a/lua/acf/shared/engines/i2.lua b/lua/acf/shared/engines/i2.lua index 44caac845..1dc2b55f6 100644 --- a/lua/acf/shared/engines/i2.lua +++ b/lua/acf/shared/engines/i2.lua @@ -1,38 +1,47 @@ -- Inline 2 engines -ACF_DefineEngine( "0.8L-I2", { - name = "0.8L I2 Diesel", - desc = "For when a 3 banger is still too bulky for your micro-needs", - model = "models/engines/inline2s.mdl", - sound = "acf_base/engines/i4_diesel2.wav", - category = "I2", - fuel = "Diesel", - enginetype = "GenericDiesel", - weight = 45, - torque = 131, - flywheelmass = 0.12, - idlerpm = 500, - peakminrpm = 750, - peakmaxrpm = 2450, - limitrpm = 2950 -} ) +ACF.RegisterEngineClass("I2", { + Name = "Inline 2 Engine", +}) +do + ACF.RegisterEngine("0.8L-I2", "I2", { + Name = "0.8L I2 Diesel", + Description = "For when a 3 banger is still too bulky for your micro-needs.", + Model = "models/engines/inline2s.mdl", + Sound = "acf_base/engines/i4_diesel2.wav", + Fuel = { Diesel = true }, + Type = "GenericDiesel", + Mass = 45, + Torque = 131, + FlywheelMass = 0.12, + RPM = { + Idle = 500, + PeakMin = 750, + PeakMax = 2450, + Limit = 2950, + } + }) + ACF.RegisterEngine("10.0-I2", "I2", { + Name = "10.0L I2 Diesel", + Description = "TORQUE.", + Model = "models/engines/inline2b.mdl", + Sound = "acf_base/engines/vtwin_large.wav", + Fuel = { Diesel = true }, + Type = "GenericDiesel", + Mass = 800, + Torque = 2500, + FlywheelMass = 7, + RPM = { + Idle = 350, + PeakMin = 450, + PeakMax = 900, + Limit = 1200, + } + }) +end -ACF_DefineEngine( "10.0-I2", { - name = "10.0L I2 Diesel", - desc = "TORQUE.", - model = "models/engines/inline2b.mdl", - sound = "acf_base/engines/vtwin_large.wav", - category = "I2", - fuel = "Diesel", - enginetype = "GenericDiesel", - weight = 800, - torque = 2500, - flywheelmass = 7, - idlerpm = 350, - peakminrpm = 450, - peakmaxrpm = 900, - limitrpm = 1200 -} ) +ACF.SetCustomAttachment("models/engines/inline2b.mdl", "driveshaft", Vector(), Angle(0, 180, 90)) +ACF.SetCustomAttachment("models/engines/inline2s.mdl", "driveshaft", Vector(-6, 0, 4), Angle(0, 180, 90)) diff --git a/lua/acf/shared/engines/i3.lua b/lua/acf/shared/engines/i3.lua index eca5b4a87..0e2224d40 100644 --- a/lua/acf/shared/engines/i3.lua +++ b/lua/acf/shared/engines/i3.lua @@ -1,108 +1,122 @@ -- Inline 3 engines --- Petrol +ACF.RegisterEngineClass("I3", { + Name = "Inline 3 Engine", +}) -ACF_DefineEngine( "1.2-I3", { - name = "1.2L I3 Petrol", - desc = "Tiny microcar engine, efficient but weak", - model = "models/engines/inline3s.mdl", - sound = "acf_base/engines/i4_petrolsmall2.wav", - category = "I3", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 40, - torque = 118, - flywheelmass = 0.05, - idlerpm = 1100, - peakminrpm = 3300, - peakmaxrpm = 5400, - limitrpm = 6000 -} ) +do -- Petrol Engines + ACF.RegisterEngine("1.2-I3", "I3", { + Name = "1.2L I3 Petrol", + Description = "Tiny microcar engine, efficient but weak.", + Model = "models/engines/inline3s.mdl", + Sound = "acf_base/engines/i4_petrolsmall2.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 40, + Torque = 118, + FlywheelMass = 0.05, + RPM = { + Idle = 1100, + PeakMin = 3300, + PeakMax = 5400, + Limit = 6000, + } + }) -ACF_DefineEngine( "3.4-I3", { - name = "3.4L I3 Petrol", - desc = "Short block engine for light utility use", - model = "models/engines/inline3m.mdl", - sound = "acf_base/engines/i4_petrolmedium2.wav", - category = "I3", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 170, - torque = 243, - flywheelmass = 0.2, - idlerpm = 900, - peakminrpm = 3500, - peakmaxrpm = 6600, - limitrpm = 6800 -} ) + ACF.RegisterEngine("3.4-I3", "I3", { + Name = "3.4L I3 Petrol", + Description = "Short block engine for light utility use.", + Model = "models/engines/inline3m.mdl", + Sound = "acf_base/engines/i4_petrolmedium2.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 170, + Torque = 243, + FlywheelMass = 0.2, + RPM = { + Idle = 900, + PeakMin = 3500, + PeakMax = 6600, + Limit = 6800, + } + }) -ACF_DefineEngine( "13.5-I3", { - name = "13.5L I3 Petrol", - desc = "Short block light tank engine, likes sideways mountings", - model = "models/engines/inline3b.mdl", - sound = "acf_base/engines/i4_petrollarge.wav", - category = "I3", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 500, - torque = 893, - flywheelmass = 3.7, - idlerpm = 500, - peakminrpm = 1900, - peakmaxrpm = 3500, - limitrpm = 3900 -} ) + ACF.RegisterEngine("13.5-I3", "I3", { + Name = "13.5L I3 Petrol", + Description = "Short block light tank engine, likes sideways mountings.", + Model = "models/engines/inline3b.mdl", + Sound = "acf_base/engines/i4_petrollarge.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 500, + Torque = 893, + FlywheelMass = 3.7, + RPM = { + Idle = 500, + PeakMin = 1900, + PeakMax = 3500, + Limit = 3900, + } + }) +end --- Diesel +do -- Diesel Engines + ACF.RegisterEngine("1.1-I3", "I3", { + Name = "1.1L I3 Diesel", + Description = "ATV grade 3-banger, enormous rev band but a choppy idle, great for light utility work.", + Model = "models/engines/inline3s.mdl", + Sound = "acf_base/engines/i4_diesel2.wav", + Fuel = { Diesel = true }, + Type = "GenericDiesel", + Mass = 65, + Torque = 187, + FlywheelMass = 0.2, + RPM = { + Idle = 550, + PeakMin = 800, + PeakMax = 2500, + Limit = 3000, + } + }) -ACF_DefineEngine( "1.1-I3", { - name = "1.1L I3 Diesel", - desc = "ATV grade 3-banger, enormous rev band but a choppy idle, great for light utility work", - model = "models/engines/inline3s.mdl", - sound = "acf_base/engines/i4_diesel2.wav", - category = "I3", - fuel = "Diesel", - enginetype = "GenericDiesel", - weight = 65, - torque = 187, - flywheelmass = 0.2, - idlerpm = 550, - peakminrpm = 800, - peakmaxrpm = 2500, - limitrpm = 3000 -} ) + ACF.RegisterEngine("2.8-I3", "I3", { + Name = "2.8L I3 Diesel", + Description = "Medium utility grade I3 diesel, for tractors", + Model = "models/engines/inline3m.mdl", + Sound = "acf_base/engines/i4_dieselmedium.wav", + Fuel = { Diesel = true }, + Type = "GenericDiesel", + Mass = 200, + Torque = 362, + FlywheelMass = 1, + RPM = { + Idle = 600, + PeakMin = 1200, + PeakMax = 3600, + Limit = 3800 + } + }) -ACF_DefineEngine( "2.8-I3", { - name = "2.8L I3 Diesel", - desc = "Medium utility grade I3 diesel, for tractors", - model = "models/engines/inline3m.mdl", - sound = "acf_base/engines/i4_dieselmedium.wav", - category = "I3", - fuel = "Diesel", - enginetype = "GenericDiesel", - weight = 200, - torque = 362, - flywheelmass = 1, - idlerpm = 600, - peakminrpm = 1200, - peakmaxrpm = 3600, - limitrpm = 3800 -} ) + ACF.RegisterEngine("11.0-I3", "I3", { + Name = "11.0L I3 Diesel", + Description = "Light tank duty engine, compact yet grunts hard.", + Model = "models/engines/inline3b.mdl", + Sound = "acf_base/engines/i4_diesellarge.wav", + Fuel = { Diesel = true }, + Type = "GenericDiesel", + Mass = 650, + Torque = 1500, + FlywheelMass = 5, + RPM = { + Idle = 550, + PeakMin = 650, + PeakMax = 1800, + Limit = 2000 + } + }) +end -ACF_DefineEngine( "11.0-I3", { - name = "11.0L I3 Diesel", - desc = "Light tank duty engine, compact yet grunts hard", - model = "models/engines/inline3b.mdl", - sound = "acf_base/engines/i4_diesellarge.wav", - category = "I3", - fuel = "Diesel", - enginetype = "GenericDiesel", - weight = 650, - torque = 1500, - flywheelmass = 5, - idlerpm = 550, - peakminrpm = 650, - peakmaxrpm = 1800, - limitrpm = 2000 -} ) +ACF.SetCustomAttachment("models/engines/inline3b.mdl", "driveshaft", Vector(-15, 0, 11), Angle(0, 180, 90)) +ACF.SetCustomAttachment("models/engines/inline3m.mdl", "driveshaft", Vector(-9, 0, 6.6), Angle(0, 180, 90)) +ACF.SetCustomAttachment("models/engines/inline3s.mdl", "driveshaft", Vector(-6, 0, 4.4), Angle(0, 180, 90)) diff --git a/lua/acf/shared/engines/i4.lua b/lua/acf/shared/engines/i4.lua index 5b04ad7c0..07ac6b562 100644 --- a/lua/acf/shared/engines/i4.lua +++ b/lua/acf/shared/engines/i4.lua @@ -1,108 +1,122 @@ -- Inline 4 engines --- Petrol +ACF.RegisterEngineClass("I4", { + Name = "Inline 4 Engine", +}) -ACF_DefineEngine( "1.5-I4", { - name = "1.5L I4 Petrol", - desc = "Small car engine, not a whole lot of git", - model = "models/engines/inline4s.mdl", - sound = "acf_base/engines/i4_petrolsmall2.wav", - category = "I4", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 50, - torque = 112, - flywheelmass = 0.06, - idlerpm = 900, - peakminrpm = 4000, - peakmaxrpm = 6500, - limitrpm = 7500 -} ) +do -- Petrol Engines + ACF.RegisterEngine("1.5-I4", "I4", { + Name = "1.5L I4 Petrol", + Description = "Small car engine, not a whole lot of git.", + Model = "models/engines/inline4s.mdl", + Sound = "acf_base/engines/i4_petrolsmall2.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 50, + Torque = 112, + FlywheelMass = 0.06, + RPM = { + Idle = 900, + PeakMin = 4000, + PeakMax = 6500, + Limit = 7500, + } + }) -ACF_DefineEngine( "3.7-I4", { - name = "3.7L I4 Petrol", - desc = "Large inline 4, sees most use in light trucks", - model = "models/engines/inline4m.mdl", - sound = "acf_base/engines/i4_petrolmedium2.wav", - category = "I4", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 200, - torque = 300, - flywheelmass = 0.2, - idlerpm = 900, - peakminrpm = 3700, - peakmaxrpm = 6000, - limitrpm = 6500 -} ) + ACF.RegisterEngine("3.7-I4", "I4", { + Name = "3.7L I4 Petrol", + Description = "Large inline 4, sees most use in light trucks.", + Model = "models/engines/inline4m.mdl", + Sound = "acf_base/engines/i4_petrolmedium2.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 200, + Torque = 300, + FlywheelMass = 0.2, + RPM = { + Idle = 900, + PeakMin = 3700, + PeakMax = 6000, + Limit = 6500 + } + }) -ACF_DefineEngine( "16.0-I4", { - name = "16.0L I4 Petrol", - desc = "Giant, thirsty I4 petrol, most commonly used in boats", - model = "models/engines/inline4l.mdl", - sound = "acf_base/engines/i4_petrollarge.wav", - category = "I4", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 600, - torque = 1062, - flywheelmass = 4, - idlerpm = 500, - peakminrpm = 1750, - peakmaxrpm = 3250, - limitrpm = 3500 -} ) + ACF.RegisterEngine("16.0-I4", "I4", { + Name = "16.0L I4 Petrol", + Description = "Giant, thirsty I4 petrol, most commonly used in boats.", + Model = "models/engines/inline4l.mdl", + Sound = "acf_base/engines/i4_petrollarge.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 600, + Torque = 1062, + FlywheelMass = 4, + RPM = { + Idle = 500, + PeakMin = 1750, + PeakMax = 3250, + Limit = 3500, + } + }) +end --- Diesel +do -- Diesel Engines + ACF.RegisterEngine("1.6-I4", "I4", { + Name = "1.6L I4 Diesel", + Description = "Small and light diesel, for low power applications requiring a wide powerband.", + Model = "models/engines/inline4s.mdl", + Sound = "acf_base/engines/i4_diesel2.wav", + Fuel = { Diesel = true }, + Type = "GenericDiesel", + Mass = 90, + Torque = 187, + FlywheelMass = 0.2, + RPM = { + Idle = 650, + PeakMin = 1000, + PeakMax = 3000, + Limit = 5000, + } + }) -ACF_DefineEngine( "1.6-I4", { - name = "1.6L I4 Diesel", - desc = "Small and light diesel, for low power applications requiring a wide powerband", - model = "models/engines/inline4s.mdl", - sound = "acf_base/engines/i4_diesel2.wav", - category = "I4", - fuel = "Diesel", - enginetype = "GenericDiesel", - weight = 90, - torque = 187, - flywheelmass = 0.2, - idlerpm = 650, - peakminrpm = 1000, - peakmaxrpm = 3000, - limitrpm = 5000 -} ) + ACF.RegisterEngine("3.1-I4", "I4", { + Name = "3.1L I4 Diesel", + Description = "Light truck duty diesel, good overall grunt.", + Model = "models/engines/inline4m.mdl", + Sound = "acf_base/engines/i4_dieselmedium.wav", + Fuel = { Diesel = true }, + Type = "GenericDiesel", + Mass = 250, + Torque = 400, + FlywheelMass = 1, + RPM = { + Idle = 500, + PeakMin = 1150, + PeakMax = 3500, + Limit = 4000, + } + }) -ACF_DefineEngine( "3.1-I4", { - name = "3.1L I4 Diesel", - desc = "Light truck duty diesel, good overall grunt", - model = "models/engines/inline4m.mdl", - sound = "acf_base/engines/i4_dieselmedium.wav", - category = "I4", - fuel = "Diesel", - enginetype = "GenericDiesel", - weight = 250, - torque = 400, - flywheelmass = 1, - idlerpm = 500, - peakminrpm = 1150, - peakmaxrpm = 3500, - limitrpm = 4000 -} ) + ACF.RegisterEngine("15.0-I4", "I4", { + Name = "15.0L I4 Diesel", + Description = "Small boat sized diesel, with large amounts of torque.", + Model = "models/engines/inline4l.mdl", + Sound = "acf_base/engines/i4_diesellarge.wav", + Fuel = { Diesel = true }, + Type = "GenericDiesel", + Mass = 800, + Torque = 1750, + FlywheelMass = 5, + RPM = { + Idle = 450, + PeakMin = 500, + PeakMax = 1800, + Limit = 2100, + } + }) +end -ACF_DefineEngine( "15.0-I4", { - name = "15.0L I4 Diesel", - desc = "Small boat sized diesel, with large amounts of torque", - model = "models/engines/inline4l.mdl", - sound = "acf_base/engines/i4_diesellarge.wav", - category = "I4", - fuel = "Diesel", - enginetype = "GenericDiesel", - weight = 800, - torque = 1750, - flywheelmass = 5, - idlerpm = 450, - peakminrpm = 500, - peakmaxrpm = 1800, - limitrpm = 2100 -} ) +ACF.SetCustomAttachment("models/engines/inline4l.mdl", "driveshaft", Vector(-15, 0, 10), Angle(0, 180, 90)) +ACF.SetCustomAttachment("models/engines/inline4m.mdl", "driveshaft", Vector(-9, 0, 6), Angle(0, 180, 90)) +ACF.SetCustomAttachment("models/engines/inline4s.mdl", "driveshaft", Vector(-6, 0, 4), Angle(0, 180, 90)) diff --git a/lua/acf/shared/engines/i5.lua b/lua/acf/shared/engines/i5.lua index 6ffb366f2..774338a24 100644 --- a/lua/acf/shared/engines/i5.lua +++ b/lua/acf/shared/engines/i5.lua @@ -1,74 +1,85 @@ -- Inline 5 engines --- Petrol +ACF.RegisterEngineClass("I5", { + Name = "Inline 5 Engine", +}) -ACF_DefineEngine( "2.3-I5", { - name = "2.3L I5 Petrol", - desc = "Sedan-grade 5-cylinder, solid and dependable", - model = "models/engines/inline5s.mdl", - sound = "acf_base/engines/i5_petrolsmall.wav", - category = "I5", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 100, - torque = 156, - flywheelmass = 0.12, - idlerpm = 900, - peakminrpm = 3600, - peakmaxrpm = 5900, - limitrpm = 7000 -} ) +do -- Petrol Engines + ACF.RegisterEngine("2.3-I5", "I5", { + Name = "2.3L I5 Petrol", + Description = "Sedan-grade 5-cylinder, solid and dependable.", + Model = "models/engines/inline5s.mdl", + Sound = "acf_base/engines/i5_petrolsmall.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 100, + Torque = 156, + FlywheelMass = 0.12, + RPM = { + Idle = 900, + PeakMin = 3600, + PeakMax = 5900, + Limit = 7000, + } + }) -ACF_DefineEngine( "3.9-I5", { - name = "3.9L I5 Petrol", - desc = "Truck sized inline 5, strong with a good balance of revs and torques", - model = "models/engines/inline5m.mdl", - sound = "acf_base/engines/i5_petrolmedium.wav", - category = "I5", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 250, - torque = 343, - flywheelmass = 0.25, - idlerpm = 700, - peakminrpm = 3700, - peakmaxrpm = 6000, - limitrpm = 6500 -} ) + ACF.RegisterEngine("3.9-I5", "I5", { + Name = "3.9L I5 Petrol", + Description = "Truck sized inline 5, strong with a good balance of revs and torque.", + Model = "models/engines/inline5m.mdl", + Sound = "acf_base/engines/i5_petrolmedium.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 250, + Torque = 343, + FlywheelMass = 0.25, + RPM = { + Idle = 700, + PeakMin = 3700, + PeakMax = 6000, + Limit = 6500, + } + }) +end --- Diesel +do -- Diesel Engines + ACF.RegisterEngine("2.9-I5", "I5", { + Name = "2.9L I5 Diesel", + Description = "Aging fuel-injected diesel, low in horsepower but very forgiving and durable.", + Model = "models/engines/inline5s.mdl", + Sound = "acf_base/engines/i5_dieselsmall2.wav", + Fuel = { Diesel = true }, + Type = "GenericDiesel", + Mass = 130, + Torque = 225, + FlywheelMass = 0.5, + RPM = { + Idle = 500, + PeakMin = 900, + PeakMax = 2800, + Limit = 4200, + } + }) -ACF_DefineEngine( "2.9-I5", { - name = "2.9L I5 Diesel", - desc = "Aging fuel-injected diesel, low in horsepower but very forgiving and durable", - model = "models/engines/inline5s.mdl", - sound = "acf_base/engines/i5_dieselsmall2.wav", - category = "I5", - fuel = "Diesel", - enginetype = "GenericDiesel", - weight = 130, - torque = 225, - flywheelmass = 0.5, - idlerpm = 500, - peakminrpm = 900, - peakmaxrpm = 2800, - limitrpm = 4200 -} ) + ACF.RegisterEngine("4.1-I5", "I5", { + Name = "4.1L I5 Diesel", + Description = "Heavier duty diesel, found in things that work hard.", + Model = "models/engines/inline5m.mdl", + Sound = "acf_base/engines/i5_dieselmedium.wav", + Fuel = { Diesel = true }, + Type = "GenericDiesel", + Mass = 400, + Torque = 550, + FlywheelMass = 1.5, + RPM = { + Idle = 650, + PeakMin = 1000, + PeakMax = 3200, + Limit = 3800, + } + }) +end -ACF_DefineEngine( "4.1-I5", { - name = "4.1L I5 Diesel", - desc = "Heavier duty diesel, found in things that work hard", - model = "models/engines/inline5m.mdl", - sound = "acf_base/engines/i5_dieselmedium.wav", - category = "I5", - fuel = "Diesel", - enginetype = "GenericDiesel", - weight = 400, - torque = 550, - flywheelmass = 1.5, - idlerpm = 650, - peakminrpm = 1000, - peakmaxrpm = 3200, - limitrpm = 3800 -} ) +ACF.SetCustomAttachment("models/engines/inline5m.mdl", "driveshaft", Vector(-15, 0, 6.6), Angle(0, 180, 90)) +ACF.SetCustomAttachment("models/engines/inline5s.mdl", "driveshaft", Vector(-10, 0, 4.4), Angle(0, 180, 90)) diff --git a/lua/acf/shared/engines/i6.lua b/lua/acf/shared/engines/i6.lua index 1583ad1e1..e59e09056 100644 --- a/lua/acf/shared/engines/i6.lua +++ b/lua/acf/shared/engines/i6.lua @@ -1,108 +1,122 @@ -- Inline 6 engines --- Petrol +ACF.RegisterEngineClass("I6", { + Name = "Inline 6 Engine", +}) -ACF_DefineEngine( "2.2-I6", { - name = "2.2L I6 Petrol", - desc = "Car sized I6 petrol with power in the high revs", - model = "models/engines/inline6s.mdl", - sound = "acf_base/engines/l6_petrolsmall2.wav", - category = "I6", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 120, - torque = 162, - flywheelmass = 0.1, - idlerpm = 800, - peakminrpm = 4000, - peakmaxrpm = 6500, - limitrpm = 7200 -} ) +do -- Petrol Engines + ACF.RegisterEngine("2.2-I6", "I6", { + Name = "2.2L I6 Petrol", + Description = "Car sized I6 petrol with power in the high revs.", + Model = "models/engines/inline6s.mdl", + Sound = "acf_base/engines/l6_petrolsmall2.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 120, + Torque = 162, + FlywheelMass = 0.1, + RPM = { + Idle = 800, + PeakMin = 4000, + PeakMax = 6500, + Limit = 7200, + } + }) -ACF_DefineEngine( "4.8-I6", { - name = "4.8L I6 Petrol", - desc = "Light truck duty I6, good for offroad applications", - model = "models/engines/inline6m.mdl", - sound = "acf_base/engines/l6_petrolmedium.wav", - category = "I6", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 300, - torque = 450, - flywheelmass = 0.2, - idlerpm = 900, - peakminrpm = 3100, - peakmaxrpm = 5000, - limitrpm = 5500 -} ) + ACF.RegisterEngine("4.8-I6", "I6", { + Name = "4.8L I6 Petrol", + Description = "Light truck duty I6, good for offroad applications.", + Model = "models/engines/inline6m.mdl", + Sound = "acf_base/engines/l6_petrolmedium.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 300, + Torque = 450, + FlywheelMass = 0.2, + RPM = { + Idle = 900, + PeakMin = 3100, + PeakMax = 5000, + Limit = 5500, + } + }) -ACF_DefineEngine( "17.2-I6", { - name = "17.2L I6 Petrol", - desc = "Heavy tractor duty petrol I6, decent overall powerband", - model = "models/engines/inline6l.mdl", - sound = "acf_base/engines/l6_petrollarge2.wav", - category = "I6", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 850, - torque = 1200, - flywheelmass = 2.5, - idlerpm = 800, - peakminrpm = 2000, - peakmaxrpm = 4000, - limitrpm = 4250 -} ) + ACF.RegisterEngine("17.2-I6", "I6", { + Name = "17.2L I6 Petrol", + Description = "Heavy tractor duty petrol I6, decent overall powerband.", + Model = "models/engines/inline6l.mdl", + Sound = "acf_base/engines/l6_petrollarge2.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 850, + Torque = 1200, + FlywheelMass = 2.5, + RPM = { + Idle = 800, + PeakMin = 2000, + PeakMax = 4000, + Limit = 4250, + } + }) +end --- Diesel +do -- Diesel Engines + ACF.RegisterEngine("3.0-I6", "I6", { + Name = "3.0L I6 Diesel", + Description = "Car sized I6 diesel, good, wide powerband.", + Model = "models/engines/inline6s.mdl", + Sound = "acf_base/engines/l6_dieselsmall.wav", + Fuel = { Diesel = true }, + Type = "GenericDiesel", + Mass = 150, + Torque = 250, + FlywheelMass = 0.5, + RPM = { + Idle = 650, + PeakMin = 1000, + PeakMax = 3000, + Limit = 4500, + } + }) -ACF_DefineEngine( "3.0-I6", { - name = "3.0L I6 Diesel", - desc = "Car sized I6 diesel, good, wide powerband", - model = "models/engines/inline6s.mdl", - sound = "acf_base/engines/l6_dieselsmall.wav", - category = "I6", - fuel = "Diesel", - enginetype = "GenericDiesel", - weight = 150, - torque = 250, - flywheelmass = 0.5, - idlerpm = 650, - peakminrpm = 1000, - peakmaxrpm = 3000, - limitrpm = 4500 -} ) + ACF.RegisterEngine("6.5-I6", "I6", { + Name = "6.5L I6 Diesel", + Description = "Truck duty I6, good overall powerband and torque.", + Model = "models/engines/inline6m.mdl", + Sound = "acf_base/engines/l6_dieselmedium4.wav", + Fuel = { Diesel = true }, + Type = "GenericDiesel", + Mass = 450, + Torque = 650, + FlywheelMass = 1.5, + RPM = { + Idle = 600, + PeakMin = 1000, + PeakMax = 3000, + Limit = 4000, + } + }) -ACF_DefineEngine( "6.5-I6", { - name = "6.5L I6 Diesel", - desc = "Truck duty I6, good overall powerband and torque", - model = "models/engines/inline6m.mdl", - sound = "acf_base/engines/l6_dieselmedium4.wav", - category = "I6", - fuel = "Diesel", - enginetype = "GenericDiesel", - weight = 450, - torque = 650, - flywheelmass = 1.5, - idlerpm = 600, - peakminrpm = 1000, - peakmaxrpm = 3000, - limitrpm = 4000 -} ) + ACF.RegisterEngine("20.0-I6", "I6", { + Name = "20.0L I6 Diesel", + Description = "Heavy duty diesel I6, used in generators and heavy movers.", + Model = "models/engines/inline6l.mdl", + Sound = "acf_base/engines/l6_diesellarge2.wav", + Fuel = { Diesel = true }, + Type = "GenericDiesel", + Mass = 1200, + Torque = 2125, + FlywheelMass = 8, + RPM = { + Idle = 400, + PeakMin = 650, + PeakMax = 2100, + Limit = 2600, + } + }) +end -ACF_DefineEngine( "20.0-I6", { - name = "20.0L I6 Diesel", - desc = "Heavy duty diesel I6, used in generators and heavy movers", - model = "models/engines/inline6l.mdl", - sound = "acf_base/engines/l6_diesellarge2.wav", - category = "I6", - fuel = "Diesel", - enginetype = "GenericDiesel", - weight = 1200, - torque = 2125, - flywheelmass = 8, - idlerpm = 400, - peakminrpm = 650, - peakmaxrpm = 2100, - limitrpm = 2600 -} ) +ACF.SetCustomAttachment("models/engines/inline6l.mdl", "driveshaft", Vector(-30, 0, 11), Angle(0, 180, 90)) +ACF.SetCustomAttachment("models/engines/inline6m.mdl", "driveshaft", Vector(-18, 0, 6.6), Angle(0, 180, 90)) +ACF.SetCustomAttachment("models/engines/inline6s.mdl", "driveshaft", Vector(-12, 0, 4.4), Angle(0, 180, 90)) diff --git a/lua/acf/shared/engines/radial.lua b/lua/acf/shared/engines/radial.lua index e9faf4484..ea0e97f66 100644 --- a/lua/acf/shared/engines/radial.lua +++ b/lua/acf/shared/engines/radial.lua @@ -1,71 +1,84 @@ -- Radial engines -ACF_DefineEngine( "3.8-R7", { - name = "3.8L R7 Petrol", - desc = "A tiny, old worn-out radial.", - model = "models/engines/radial7s.mdl", - sound = "acf_base/engines/r7_petrolsmall.wav", - category = "Radial", - fuel = "Petrol", - enginetype = "Radial", - weight = 210, - torque = 387, - flywheelmass = 0.22, - idlerpm = 700, - peakminrpm = 2600, - peakmaxrpm = 4350, - limitrpm = 4800 -} ) +ACF.RegisterEngineClass("R7", { + Name = "Radial 7 Engine", +}) -ACF_DefineEngine( "11.0-R7", { - name = "11.0 R7 Petrol", - desc = "Mid range radial, thirsty and smooth", - model = "models/engines/radial7m.mdl", - sound = "acf_base/engines/r7_petrolmedium.wav", - category = "Radial", - fuel = "Petrol", - enginetype = "Radial", - weight = 385, - torque = 700, - flywheelmass = 0.45, - idlerpm = 600, - peakminrpm = 2300, - peakmaxrpm = 3850, - limitrpm = 4400 -} ) +do + ACF.RegisterEngine("3.8-R7", "R7", { + Name = "3.8L R7 Petrol", + Description = "A tiny, old worn-out radial.", + Model = "models/engines/radial7s.mdl", + Sound = "acf_base/engines/r7_petrolsmall.wav", + Fuel = { Petrol = true }, + Type = "Radial", + Mass = 210, + Torque = 387, + FlywheelMass = 0.22, + RPM = { + Idle = 700, + PeakMin = 2600, + PeakMax = 4350, + Limit = 4800, + } + }) + ACF.RegisterEngine("11.0-R7", "R7", { + Name = "11.0L R7 Petrol", + Description = "Mid range radial, thirsty and smooth.", + Model = "models/engines/radial7m.mdl", + Sound = "acf_base/engines/r7_petrolmedium.wav", + Fuel = { Petrol = true }, + Type = "Radial", + Mass = 385, + Torque = 700, + FlywheelMass = 0.45, + RPM = { + Idle = 600, + PeakMin = 2300, + PeakMax = 3850, + Limit = 4400, + } + }) -ACF_DefineEngine( "8.0-R7", { - name = "8.0 R7 Diesel", - desc = "Military-grade radial engine, similar to a ZO 02A. Heavy and with a narrow powerband, but efficient, and well-optimized to cruising.", - model = "models/engines/radial7m.mdl", - sound = "acf_base/engines/r7_petrolmedium.wav", - category = "Radial", - fuel = "Multifuel", - enginetype = "GenericDiesel", - weight = 450, - torque = 1000, - flywheelmass = 1.0, - idlerpm = 400, - peakminrpm = 2200, - peakmaxrpm = 2500, - limitrpm = 2800 -} ) + ACF.RegisterEngine("8.0-R7", "R7", { + Name = "8.0L R7 Diesel", + Description = "Heavy and with a narrow powerband, but efficient, and well-optimized to cruising.", + Model = "models/engines/radial7m.mdl", + Sound = "acf_base/engines/r7_petrolmedium.wav", + Fuel = { Petrol = true, Diesel = true }, + Type = "GenericDiesel", + Mass = 450, + Torque = 1000, + FlywheelMass = 1, + RPM = { + Idle = 400, + PeakMin = 2200, + PeakMax = 2500, + Limit = 2800, + } + }) -ACF_DefineEngine( "24.0-R7", { - name = "24.0L R7 Petrol", - desc = "Massive American radial monster, destined for fighter aircraft and heavy tanks.", - model = "models/engines/radial7l.mdl", - sound = "acf_base/engines/r7_petrollarge.wav", - category = "Radial", - fuel = "Petrol", - enginetype = "Radial", - weight = 952, - torque = 2018, - flywheelmass = 3.4, - idlerpm = 750, - peakminrpm = 1900, - peakmaxrpm = 3150, - limitrpm = 3500 -} ) + ACF.RegisterEngine("24.0-R7", "R7", { + Name = "24.0L R7 Petrol", + Description = "Massive American radial monster, destined for fighter aircraft and heavy tanks.", + Model = "models/engines/radial7l.mdl", + Sound = "acf_base/engines/r7_petrollarge.wav", + Fuel = { Petrol = true }, + Type = "Radial", + Mass = 952, + Torque = 2018, + FlywheelMass = 3.4, + RPM = { + Idle = 750, + PeakMin = 1900, + PeakMax = 3150, + Limit = 3500, + } + }) +end + +ACF.SetCustomAttachment("models/engines/radial7l.mdl", "driveshaft", Vector(-12), Angle(0, 180, 90)) +ACF.SetCustomAttachment("models/engines/radial7m.mdl", "driveshaft", Vector(-8), Angle(0, 180, 90)) +ACF.SetCustomAttachment("models/engines/radial7s.mdl", "driveshaft", Vector(-6), Angle(0, 180, 90)) diff --git a/lua/acf/shared/engines/rotary.lua b/lua/acf/shared/engines/rotary.lua index 004d75006..9bc016d8e 100644 --- a/lua/acf/shared/engines/rotary.lua +++ b/lua/acf/shared/engines/rotary.lua @@ -1,53 +1,67 @@ -- Wankel engines -ACF_DefineEngine( "900cc-R", { - name = "0.9L Rotary", - desc = "Small 2-rotor Wankel; suited for yard use\n\nWankels have rather wide powerbands, but are very high strung", - model = "models/engines/wankel_2_small.mdl", - sound = "acf_base/engines/wankel_small.wav", - category = "Rotary", - fuel = "Petrol", - enginetype = "Wankel", - weight = 50, - torque = 97, - flywheelmass = 0.06, - idlerpm = 950, - peakminrpm = 4500, - peakmaxrpm = 9000, - limitrpm = 9200 -} ) +ACF.RegisterEngineClass("R", { + Name = "Rotary Engine", + Description = "Wankels have rather wide powerbands, but are very high strung." +}) -ACF_DefineEngine( "1.3L-R", { - name = "1.3L Rotary", - desc = "Medium 2-rotor Wankel\n\nWankels have rather wide powerbands, but are very high strung", - model = "models/engines/wankel_2_med.mdl", - sound = "acf_base/engines/wankel_medium.wav", - category = "Rotary", - fuel = "Petrol", - enginetype = "Wankel", - weight = 140, - torque = 155, - flywheelmass = 0.06, - idlerpm = 950, - peakminrpm = 4100, - peakmaxrpm = 8500, - limitrpm = 9000 -} ) +do + ACF.RegisterEngine("900cc-R", "R", { + Name = "0.9L Rotary", + Description = "Small 2-rotor Wankel, suited for yard use.", + Model = "models/engines/wankel_2_small.mdl", + Sound = "acf_base/engines/wankel_small.wav", + Fuel = { Petrol = true }, + Type = "Wankel", + Mass = 50, + Torque = 97, + FlywheelMass = 0.06, + RPM = { + Idle = 950, + PeakMin = 4500, + PeakMax = 9000, + Limit = 9200, + } + }) -ACF_DefineEngine( "2.0L-R", { - name = "2.0L Rotary", - desc = "High performance 3-rotor Wankel\n\nWankels have rather wide powerbands, but are very high strung", - model = "models/engines/wankel_3_med.mdl", - sound = "acf_base/engines/wankel_large.wav", - category = "Rotary", - fuel = "Petrol", - enginetype = "Wankel", - weight = 200, - torque = 235, - flywheelmass = 0.1, - idlerpm = 950, - peakminrpm = 4100, - peakmaxrpm = 8500, - limitrpm = 9500 -} ) + ACF.RegisterEngine("1.3L-R", "R", { + Name = "1.3L Rotary", + Description = "Medium 2-rotor Wankel.", + Model = "models/engines/wankel_2_med.mdl", + Sound = "acf_base/engines/wankel_medium.wav", + Fuel = { Petrol = true }, + Type = "Wankel", + Mass = 140, + Torque = 155, + FlywheelMass = 0.06, + RPM = { + Idle = 950, + PeakMin = 4100, + PeakMax = 8500, + Limit = 9000, + } + }) + + ACF.RegisterEngine("2.0L-R", "R", { + Name = "2.0L Rotary", + Description = "High performance 3-rotor Wankel.", + Model = "models/engines/wankel_3_med.mdl", + Sound = "acf_base/engines/wankel_large.wav", + Fuel = { Petrol = true }, + Type = "Wankel", + Mass = 200, + Torque = 235, + FlywheelMass = 0.1, + RPM = { + Idle = 950, + PeakMin = 4100, + PeakMax = 8500, + Limit = 9500, + } + }) +end + +ACF.SetCustomAttachment("models/engines/wankel_3_med.mdl", "driveshaft", Vector(), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/engines/wankel_2_med.mdl", "driveshaft", Vector(), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/engines/wankel_2_small.mdl", "driveshaft", Vector(), Angle(0, 0, 90)) diff --git a/lua/acf/shared/engines/single.lua b/lua/acf/shared/engines/single.lua index 4f2cfc2b5..ab265f252 100644 --- a/lua/acf/shared/engines/single.lua +++ b/lua/acf/shared/engines/single.lua @@ -1,53 +1,66 @@ -- Single-cylinder engines -ACF_DefineEngine( "0.25-I1", { - name = "250cc Single", - desc = "Tiny bike engine", - model = "models/engines/1cylsml.mdl", - sound = "acf_base/engines/i1_small.wav", - category = "Single", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 15, - torque = 25, - flywheelmass = 0.005, - idlerpm = 1200, - peakminrpm = 4000, - peakmaxrpm = 6500, - limitrpm = 7500 -} ) +ACF.RegisterEngineClass("I1", { + Name = "Single Cylinder Engine", +}) -ACF_DefineEngine( "0.5-I1", { - name = "500cc Single", - desc = "Large single cylinder bike engine", - model = "models/engines/1cylmed.mdl", - sound = "acf_base/engines/i1_medium.wav", - category = "Single", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 20, - torque = 50, - flywheelmass = 0.005, - idlerpm = 900, - peakminrpm = 4300, - peakmaxrpm = 7000, - limitrpm = 8000 -} ) +do + ACF.RegisterEngine("0.25-I1", "I1", { + Name = "250cc Single Cylinder", + Description = "Tiny bike engine.", + Model = "models/engines/1cylsml.mdl", + Sound = "acf_base/engines/i1_small.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 15, + Torque = 25, + FlywheelMass = 0.005, + RPM = { + Idle = 1200, + PeakMin = 4000, + PeakMax = 6500, + Limit = 7500, + } + }) -ACF_DefineEngine( "1.3-I1", { - name = "1300cc Single", - desc = "Ridiculously large single cylinder engine, seriously what the fuck", - model = "models/engines/1cylbig.mdl", - sound = "acf_base/engines/i1_large.wav", - category = "Single", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 50, - torque = 112, - flywheelmass = 0.1, - idlerpm = 600, - peakminrpm = 3600, - peakmaxrpm = 6000, - limitrpm = 6700 -} ) + ACF.RegisterEngine("0.5-I1", "I1", { + Name = "500cc Single Cylinder", + Description = "Large single cylinder bike engine.", + Model = "models/engines/1cylmed.mdl", + Sound = "acf_base/engines/i1_medium.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 20, + Torque = 50, + FlywheelMass = 0.005, + RPM = { + Idle = 900, + PeakMin = 4300, + PeakMax = 7000, + Limit = 8000, + } + }) + + ACF.RegisterEngine("1.3-I1", "I1", { + Name = "1300cc Single Cylinder", + Description = "Ridiculously large single cylinder engine, seriously what the fuck.", + Model = "models/engines/1cylbig.mdl", + Sound = "acf_base/engines/i1_large.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 50, + Torque = 112, + FlywheelMass = 0.1, + RPM = { + Idle = 600, + PeakMin = 3600, + PeakMax = 6000, + Limit = 6700, + } + }) +end + +ACF.SetCustomAttachment("models/engines/1cylbig.mdl", "driveshaft", Vector(), Angle(0, -90, 90)) +ACF.SetCustomAttachment("models/engines/1cylmed.mdl", "driveshaft", Vector(), Angle(0, -90, 90)) +ACF.SetCustomAttachment("models/engines/1cylsml.mdl", "driveshaft", Vector(), Angle(0, -90, 90)) diff --git a/lua/acf/shared/engines/special.lua b/lua/acf/shared/engines/special.lua index 015145130..22896ad87 100644 --- a/lua/acf/shared/engines/special.lua +++ b/lua/acf/shared/engines/special.lua @@ -1,192 +1,226 @@ -- Special engines -ACF_DefineEngine( "0.9L-I2", { - name = "0.9L I2 Petrol", - desc = "Turbocharged inline twin engine that delivers surprising pep for its size.", - model = "models/engines/inline2s.mdl", - sound = "acf_extra/vehiclefx/engines/ponyengine.wav", - category = "Special", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 60, - torque = 145, - flywheelmass = 0.085, - idlerpm = 750, - peakminrpm = 3125, - peakmaxrpm = 5100, - limitrpm = 6000 -} ) +ACF.RegisterEngineClass("SP", { + Name = "Special Engine", +}) -ACF_DefineEngine( "1.0L-I4", { - name = "1.0L I4 Petrol", - desc = "Tiny I4 designed for racing bikes. Doesn't pack much torque, but revs ludicrously high.", - model = "models/engines/inline4s.mdl", - sound = "acf_extra/vehiclefx/engines/l4/mini_onhigh.wav", - pitch = 0.75, - category = "Special", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 78, - torque = 85, - flywheelmass = 0.031, - idlerpm = 1200, - peakminrpm = 7500, - peakmaxrpm = 11500, - limitrpm = 12000 -} ) +do -- Special Rotary Engines + ACF.RegisterEngine("2.6L-Wankel", "SP", { + Name = "2.6L Rotary", + Description = "4 rotor racing Wankel, high revving and high strung.", + Model = "models/engines/wankel_4_med.mdl", + Sound = "acf_base/engines/wankel_large.wav", + Fuel = { Petrol = true }, + Type = "Wankel", + Mass = 260, + Torque = 312, + FlywheelMass = 0.11, + RPM = { + Idle = 1200, + PeakMin = 4500, + PeakMax = 9000, + Limit = 9500, + } + }) +end -ACF_DefineEngine( "1.8L-V4", { - name = "1.8L V4 Petrol", - desc = "Naturally aspirated rally-tuned V4 with enlarged bore and stroke.", - model = "models/engines/v4s.mdl", - sound = "acf_extra/vehiclefx/engines/l4/elan_onlow.wav", - category = "Special", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 92, - torque = 156, - flywheelmass = 0.04, - idlerpm = 900, - peakminrpm = 4600, - peakmaxrpm = 7000, - limitrpm = 7500 -} ) +do -- Special I2 Engines + ACF.RegisterEngine("0.9L-I2", "SP", { + Name = "0.9L I2 Petrol", + Description = "Turbocharged inline twin engine that delivers surprising pep for its size.", + Model = "models/engines/inline2s.mdl", + Sound = "acf_extra/vehiclefx/engines/ponyengine.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 60, + Torque = 145, + FlywheelMass = 0.085, + RPM = { + Idle = 750, + PeakMin = 3125, + PeakMax = 5100, + Limit = 6000, + } + }) +end -ACF_DefineEngine( "2.4L-V6", { - name = "2.4L V6 Petrol", - desc = "Although the cast iron engine block is fairly weighty, this tiny v6 makes up for it with impressive power. The unique V angle allows uncharacteristically high RPM for a V6.", - model = "models/engines/v6small.mdl", - sound = "acf_extra/vehiclefx/engines/l6/capri_onmid.wav", - category = "Special", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 134, - torque = 215, - flywheelmass = 0.075, - idlerpm = 950, - peakminrpm = 4500, - peakmaxrpm = 7100, - limitrpm = 8000 -} ) +do -- Special I4 Engines + ACF.RegisterEngine("1.0L-I4", "SP", { + Name = "1.0L I4 Petrol", + Description = "Tiny I4 designed for racing bikes. Doesn't pack much torque, but revs ludicrously high.", + Model = "models/engines/inline4s.mdl", + Sound = "acf_extra/vehiclefx/engines/l4/mini_onhigh.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 78, + Torque = 85, + FlywheelMass = 0.031, + Pitch = 0.75, + RPM = { + Idle = 1200, + PeakMin = 7500, + PeakMax = 11500, + Limit = 12000, + } + }) -ACF_DefineEngine( "1.9L-I4", { - name = "1.9L I4 Petrol", - desc = "Supercharged racing 4 cylinder, most of the power in the high revs.", - model = "models/engines/inline4s.mdl", - sound = "acf_base/engines/i4_special.wav", - category = "Special", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 150, - torque = 220, - flywheelmass = 0.06, - idlerpm = 950, - peakminrpm = 5200, - peakmaxrpm = 8500, - limitrpm = 9000 -} ) + ACF.RegisterEngine("1.9L-I4", "SP", { + Name = "1.9L I4 Petrol", + Description = "Supercharged racing 4 cylinder, most of the power in the high revs.", + Model = "models/engines/inline4s.mdl", + Sound = "acf_base/engines/i4_special.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 150, + Torque = 220, + FlywheelMass = 0.06, + RPM = { + Idle = 950, + PeakMin = 5200, + PeakMax = 8500, + Limit = 9000, + } + }) +end -ACF_DefineEngine( "2.6L-Wankel", { - name = "2.6L Rotary", - desc = "4 rotor racing Wankel, high revving and high strung.", - model = "models/engines/wankel_4_med.mdl", - sound = "acf_base/engines/wankel_large.wav", - category = "Special", - fuel = "Petrol", - enginetype = "Wankel", - weight = 260, - torque = 312, - flywheelmass = 0.11, - idlerpm = 1200, - peakminrpm = 4500, - peakmaxrpm = 9000, - limitrpm = 9500 -} ) +do -- Special V4 Engines + ACF.RegisterEngine("1.8L-V4", "SP", { + Name = "1.8L V4 Petrol", + Description = "Naturally aspirated rally-tuned V4 with enlarged bore and stroke.", + Model = "models/engines/v4s.mdl", + Sound = "acf_extra/vehiclefx/engines/l4/elan_onlow.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 92, + Torque = 156, + FlywheelMass = 0.04, + RPM = { + Idle = 900, + PeakMin = 4600, + PeakMax = 7000, + Limit = 7500, + } + }) +end -ACF_DefineEngine( "2.9-V8", { - name = "2.9L V8 Petrol", - desc = "Racing V8, very high revving and loud", - model = "models/engines/v8s.mdl", - sound = "acf_base/engines/v8_special.wav", - category = "Special", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 180, - torque = 250, - flywheelmass = 0.075, - idlerpm = 1000, - peakminrpm = 5500, - peakmaxrpm = 9000, - limitrpm = 10000 -} ) +do -- Special I6 Engines + ACF.RegisterEngine("3.8-I6", "SP", { + Name = "3.8L I6 Petrol", + Description = "Large racing straight six, powerful and high revving, but lacking in torque.", + Model = "models/engines/inline6m.mdl", + Sound = "acf_base/engines/l6_special.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 180, + Torque = 280, + FlywheelMass = 0.1, + RPM = { + Idle = 1100, + PeakMin = 5200, + PeakMax = 8500, + Limit = 9000, + } + }) +end -ACF_DefineEngine( "3.8-I6", { - name = "3.8L I6 Petrol", - desc = "Large racing straight six, powerful and high revving, but lacking in torque.", - model = "models/engines/inline6m.mdl", - sound = "acf_base/engines/l6_special.wav", - category = "Special", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 180, - torque = 280, - flywheelmass = 0.1, - idlerpm = 1100, - peakminrpm = 5200, - peakmaxrpm = 8500, - limitrpm = 9000 -} ) +do -- Special V6 Engines + ACF.RegisterEngine("2.4L-V6", "SP", { + Name = "2.4L V6 Petrol", + Description = "Although the cast iron engine block is fairly weighty, this tiny v6 makes up for it with impressive power. The unique V angle allows uncharacteristically high RPM for a V6.", + Model = "models/engines/v6small.mdl", + Sound = "acf_extra/vehiclefx/engines/l6/capri_onmid.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 134, + Torque = 215, + FlywheelMass = 0.075, + RPM = { + Idle = 950, + PeakMin = 4500, + PeakMax = 7100, + Limit = 8000, + } + }) +end -ACF_DefineEngine( "5.3-V10", { - name = "5.3L V10 Special", - desc = "Extreme performance v10", - model = "models/engines/v10sml.mdl", - sound = "acf_base/engines/v10_special.wav", - category = "Special", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 300, - torque = 400, - flywheelmass = 0.15, - idlerpm = 1100, - peakminrpm = 5750, - peakmaxrpm = 8000, - limitrpm = 9000 -} ) +do -- Special V8 Engines + ACF.RegisterEngine("2.9-V8", "SP", { + Name = "2.9L V8 Petrol", + Description = "Racing V8, very high revving and loud", + Model = "models/engines/v8s.mdl", + Sound = "acf_base/engines/v8_special.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 180, + Torque = 250, + FlywheelMass = 0.075, + RPM = { + Idle = 1000, + PeakMin = 5500, + PeakMax = 9000, + Limit = 10000, + } + }) -ACF_DefineEngine( "7.2-V8", { - name = "7.2L V8 Petrol", - desc = "Very high revving, glorious v8 of ear rapetasticalness.", - model = "models/engines/v8m.mdl", - sound = "acf_base/engines/v8_special2.wav", - category = "Special", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 400, - torque = 425, - flywheelmass = 0.15, - idlerpm = 1000, - peakminrpm = 5000, - peakmaxrpm = 8000, - limitrpm = 8500 -} ) + ACF.RegisterEngine("7.2-V8", "SP", { + Name = "7.2L V8 Petrol", + Description = "Very high revving, glorious v8 of ear rapetasticalness.", + Model = "models/engines/v8m.mdl", + Sound = "acf_base/engines/v8_special2.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 400, + Torque = 425, + FlywheelMass = 0.15, + RPM = { + Idle = 1000, + PeakMin = 5000, + PeakMax = 8000, + Limit = 8500, + } + }) +end -ACF_DefineEngine( "3.0-V12", { - name = "3.0L V12 Petrol", - desc = "A purpose-built racing v12, not known for longevity.", - model = "models/engines/v12s.mdl", - sound = "acf_extra/vehiclefx/engines/v12/gtb4_onmid.wav", - pitch = 0.85, - category = "Special", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 175, - torque = 310, - flywheelmass = 0.1, - idlerpm = 1200, - peakminrpm = 6875, - peakmaxrpm = 11000, - limitrpm = 12500 -} ) +do -- Special V10 Engines + ACF.RegisterEngine("5.3-V10", "SP", { + Name = "5.3L V10 Special", + Description = "Extreme performance v10", + Model = "models/engines/v10sml.mdl", + Sound = "acf_base/engines/v10_special.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 300, + Torque = 400, + FlywheelMass = 0.15, + RPM = { + Idle = 1100, + PeakMin = 5750, + PeakMax = 8000, + Limit = 9000, + } + }) +end +do -- Special V12 Engines + ACF.RegisterEngine("3.0-V12", "SP", { + Name = "3.0L V12 Petrol", + Description = "A purpose-built racing v12, not known for longevity.", + Model = "models/engines/v12s.mdl", + Sound = "acf_extra/vehiclefx/engines/v12/gtb4_onmid.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 175, + Torque = 310, + FlywheelMass = 0.1, + Pitch = 0.85, + RPM = { + Idle = 1200, + PeakMin = 6875, + PeakMax = 11000, + Limit = 12500, + } + }) +end + +ACF.SetCustomAttachment("models/engines/wankel_4_med.mdl", "driveshaft", Vector(), Angle(0, 0, 90)) diff --git a/lua/acf/shared/engines/turbine.lua b/lua/acf/shared/engines/turbine.lua index e6d789552..614e807e3 100644 --- a/lua/acf/shared/engines/turbine.lua +++ b/lua/acf/shared/engines/turbine.lua @@ -1,246 +1,277 @@ -- Gas turbines -ACF_DefineEngine( "Turbine-Small-Trans", { - name = "Gas Turbine, Small, Transaxial", - desc = "A small gas turbine, high power and a very wide powerband\n\nThese turbines are optimized for aero use, but can be used in other specialized roles, being powerful but suffering from poor throttle response and fuel consumption.\n\nOutputs to the side instead of rear.", - model = "models/engines/turbine_s.mdl", - sound = "acf_base/engines/turbine_small.wav", - category = "Turbine", - fuel = "Multifuel", - enginetype = "Turbine", - weight = 160, - torque = 550, - flywheelmass = 2.3, - idlerpm = 1400, - peakminrpm = 1000, - peakmaxrpm = 1500, - limitrpm = 10000, - iselec = true, - istrans = true, - flywheeloverride = 4167 -} ) - -ACF_DefineEngine( "Turbine-Medium-Trans", { - name = "Gas Turbine, Medium, Transaxial", - desc = "A medium gas turbine, moderate power but a very wide powerband\n\nThese turbines are optimized for aero use, but can be used in other specialized roles, being powerful but suffering from poor throttle response and fuel consumption.\n\nOutputs to the side instead of rear.", - model = "models/engines/turbine_m.mdl", - sound = "acf_base/engines/turbine_medium.wav", - category = "Turbine", - fuel = "Multifuel", - enginetype = "Turbine", - weight = 320, - torque = 812, - flywheelmass = 3.4, - idlerpm = 1800, - peakminrpm = 1200, - peakmaxrpm = 1800, - limitrpm = 12000, - iselec = true, - istrans = true, - flywheeloverride = 5000 -} ) - -ACF_DefineEngine( "Turbine-Large-Trans", { - name = "Gas Turbine, Large, Transaxial", - desc = "A large gas turbine, powerful with a wide powerband\n\nThese turbines are optimized for aero use, but can be used in other specialized roles, being powerful but suffering from poor throttle response and fuel consumption.\n\nOutputs to the side instead of rear.", - model = "models/engines/turbine_l.mdl", - sound = "acf_base/engines/turbine_large.wav", - category = "Turbine", - fuel = "Multifuel", - enginetype = "Turbine", - weight = 880, - torque = 1990, - flywheelmass = 8.4, - idlerpm = 2000, - peakminrpm = 1350, - peakmaxrpm = 2025, - limitrpm = 13500, - iselec = true, - istrans = true, - flywheeloverride = 5625 -} ) - -ACF_DefineEngine( "Turbine-Small", { - name = "Gas Turbine, Small", - desc = "A small gas turbine, high power and a very wide powerband\n\nThese turbines are optimized for aero use, but can be used in other specialized roles, being powerful but suffering from poor throttle response and fuel consumption.", - model = "models/engines/gasturbine_s.mdl", - sound = "acf_base/engines/turbine_small.wav", - category = "Turbine", - fuel = "Multifuel", - enginetype = "Turbine", - weight = 200, - torque = 687, - flywheelmass = 2.9, - idlerpm = 1400, - peakminrpm = 1000, - peakmaxrpm = 1500, - limitrpm = 10000, - iselec = true, - flywheeloverride = 4167 -} ) - -ACF_DefineEngine( "Turbine-Medium", { - name = "Gas Turbine, Medium", - desc = "A medium gas turbine, moderate power but a very wide powerband\n\nThese turbines are optimized for aero use, but can be used in other specialized roles, being powerful but suffering from poor throttle response and fuel consumption.", - model = "models/engines/gasturbine_m.mdl", - sound = "acf_base/engines/turbine_medium.wav", - category = "Turbine", - fuel = "Multifuel", - enginetype = "Turbine", - weight = 400, - torque = 1016, - flywheelmass = 4.3, - idlerpm = 1800, - peakminrpm = 1200, - peakmaxrpm = 1800, - limitrpm = 12000, - iselec = true, - flywheeloverride = 5000 -} ) - -ACF_DefineEngine( "Turbine-Large", { - name = "Gas Turbine, Large", - desc = "A large gas turbine, powerful with a wide powerband\n\nThese turbines are optimized for aero use, but can be used in other specialized roles, being powerful but suffering from poor throttle response and fuel consumption.", - model = "models/engines/gasturbine_l.mdl", - sound = "acf_base/engines/turbine_large.wav", - category = "Turbine", - fuel = "Multifuel", - enginetype = "Turbine", - weight = 1100, - torque = 2487, - flywheelmass = 10.5, - idlerpm = 2000, - peakminrpm = 1350, - peakmaxrpm = 2025, - limitrpm = 13500, - iselec = true, - flywheeloverride = 5625 -} ) - ---Forward facing ground turbines - -ACF_DefineEngine( "Turbine-Ground-Small", { - name = "Ground Gas Turbine, Small", - desc = "A small gas turbine, fitted with ground-use air filters and tuned for ground use.\n\nGround-use turbines have excellent low-rev performance and are deceptively powerful, easily propelling loads that would have equivalent reciprocating engines struggling; however, they have sluggish throttle response, high gearbox demands, high fuel usage, and low tolerance to damage.", - model = "models/engines/gasturbine_s.mdl", - sound = "acf_base/engines/turbine_small.wav", - category = "Turbine", - fuel = "Multifuel", - enginetype = "Radial", - weight = 350, - torque = 1000, - flywheelmass = 14.3, - idlerpm = 700, - peakminrpm = 1000, - peakmaxrpm = 1350, - limitrpm = 3000, - iselec = true, - flywheeloverride = 1667 -} ) - -ACF_DefineEngine( "Turbine-Ground-Medium", { - name = "Ground Gas Turbine, Medium", - desc = "A medium gas turbine, fitted with ground-use air filters and tuned for ground use.\n\nGround-use turbines have excellent low-rev performance and are deceptively powerful, easily propelling loads that would have equivalent reciprocating engines struggling; however, they have sluggish throttle response, high gearbox demands, high fuel usage, and low tolerance to damage.", - model = "models/engines/gasturbine_m.mdl", - sound = "acf_base/engines/turbine_medium.wav", - category = "Turbine", - fuel = "Multifuel", - enginetype = "Radial", --This is done to give proper fuel consumption and make the turbines not instant-torque from idle - weight = 600, - torque = 1500, - flywheelmass = 29.6, - idlerpm = 600, - peakminrpm = 1500, - peakmaxrpm = 2000, - limitrpm = 3000, - iselec = true, - flywheeloverride = 1450, - pitch = 1.15 -} ) - -ACF_DefineEngine( "Turbine-Ground-Large", { - name = "Ground Gas Turbine, Large", - desc = "A large gas turbine, fitted with ground-use air filters and tuned for ground use. Doesn't have the sheer power output of an aero gas turbine, but compensates with an imperial fuckload of torque.\n\nGround-use turbines have excellent low-rev performance and are deceptively powerful, easily propelling loads that would have equivalent reciprocating engines struggling; however, they have sluggish throttle response, high gearbox demands, high fuel usage, and low tolerance to damage.", - model = "models/engines/gasturbine_l.mdl", - sound = "acf_base/engines/turbine_large.wav", - category = "Turbine", - fuel = "Multifuel", - enginetype = "Radial", - weight = 1650, - torque = 5000, - flywheelmass = 75, - idlerpm = 500, - peakminrpm = 1000, - peakmaxrpm = 1250, - limitrpm = 3000, - iselec = true, - flywheeloverride = 1250, - pitch = 1.35 -} ) - ---Transaxial Ground Turbines - -ACF_DefineEngine( "Turbine-Small-Ground-Trans", { - name = "Ground Gas Turbine, Small, Transaxial", - desc = "A small gas turbine, fitted with ground-use air filters and tuned for ground use.\n\nGround-use turbines have excellent low-rev performance and are deceptively powerful, easily propelling loads that would have equivalent reciprocating engines struggling; however, they have sluggish throttle response, high gearbox demands, high fuel usage, and low tolerance to damage. Outputs to the side instead of rear.", - model = "models/engines/turbine_s.mdl", - sound = "acf_base/engines/turbine_small.wav", - category = "Turbine", - fuel = "Multifuel", - enginetype = "Radial", - weight = 280, - torque = 750, - flywheelmass = 11.4, - idlerpm = 700, - peakminrpm = 1000, - peakmaxrpm = 1350, - limitrpm = 3000, - iselec = true, - istrans = true, - flywheeloverride = 1667 -} ) - -ACF_DefineEngine( "Turbine-Medium-Ground-Trans", { - name = "Ground Gas Turbine, Medium, Transaxial", - desc = "A medium gas turbine, fitted with ground-use air filters and tuned for ground use.\n\nGround-use turbines have excellent low-rev performance and are deceptively powerful, easily propelling loads that would have equivalent reciprocating engines struggling; however, they have sluggish throttle response, high gearbox demands, high fuel usage, and low tolerance to damage. Outputs to the side instead of rear.", - model = "models/engines/turbine_m.mdl", - sound = "acf_base/engines/turbine_medium.wav", - category = "Turbine", - fuel = "Multifuel", - enginetype = "Radial", - weight = 480, - torque = 1125, - flywheelmass = 23.7, - idlerpm = 600, - peakminrpm = 1500, - peakmaxrpm = 2000, - limitrpm = 3000, - iselec = true, - istrans = true, - flywheeloverride = 1450, - pitch = 1.15 -} ) - -ACF_DefineEngine( "Turbine-Large-Ground-Trans", { - name = "Ground Gas Turbine, Large, Transaxial", - desc = "A large gas turbine, fitted with ground-use air filters and tuned for ground use. Doesn't have the sheer power output of an aero gas turbine, but compensates with an imperial fuckload of torque.\n\nGround-use turbines have excellent low-rev performance and are deceptively powerful, easily propelling loads that would have equivalent reciprocating engines struggling; however, they have sluggish throttle response, high gearbox demands, high fuel usage, and low tolerance to damage. Outputs to the side instead of rear.", - model = "models/engines/turbine_l.mdl", - sound = "acf_base/engines/turbine_large.wav", - category = "Turbine", - fuel = "Multifuel", - enginetype = "Radial", - weight = 1320, - torque = 3750, - flywheelmass = 60, - idlerpm = 500, - peakminrpm = 1000, - peakmaxrpm = 1250, - limitrpm = 3000, - iselec = true, - istrans = true, - flywheeloverride = 1250, - pitch = 1.35 -} ) +ACF.RegisterEngineClass("GT", { + Name = "Gas Turbine", + Description = "These turbines are optimized for aero use due to them being powerful but suffering from poor throttle response and fuel consumption." +}) +do -- Forward-facing Gas Turbines + ACF.RegisterEngine("Turbine-Small", "GT", { + Name = "Small Gas Turbine", + Description = "A small gas turbine, high power and a very wide powerband.", + Model = "models/engines/gasturbine_s.mdl", + Sound = "acf_base/engines/turbine_small.wav", + Fuel = { Petrol = true, Diesel = true }, + Type = "Turbine", + Mass = 200, + Torque = 687, + FlywheelMass = 2.9, + IsElectric = true, + RPM = { + Idle = 1400, + PeakMin = 1000, + PeakMax = 1500, + Limit = 10000, + Override = 4167, + } + }) + ACF.RegisterEngine("Turbine-Medium", "GT", { + Name = "Medium Gas Turbine", + Description = "A medium gas turbine, moderate power but a very wide powerband.", + Model = "models/engines/gasturbine_m.mdl", + Sound = "acf_base/engines/turbine_medium.wav", + Fuel = { Petrol = true, Diesel = true }, + Type = "Turbine", + Mass = 400, + Torque = 1016, + FlywheelMass = 4.3, + IsElectric = true, + RPM = { + Idle = 1800, + PeakMin = 1200, + PeakMax = 1800, + Limit = 12000, + Override = 5000, + } + }) + + ACF.RegisterEngine("Turbine-Large", "GT", { + Name = "Large Gas Turbine", + Description = "A large gas turbine, powerful with a wide powerband.", + Model = "models/engines/gasturbine_l.mdl", + Sound = "acf_base/engines/turbine_large.wav", + Fuel = { Petrol = true, Diesel = true }, + Type = "Turbine", + Mass = 1100, + Torque = 2487, + FlywheelMass = 10.5, + IsElectric = true, + RPM = { + Idle = 2000, + PeakMin = 1350, + PeakMax = 2025, + Limit = 13500, + Override = 5625, + } + }) +end + +do -- Transaxial Gas Turbines + ACF.RegisterEngine("Turbine-Small-Trans", "GT", { + Name = "Small Transaxial Gas Turbine", + Description = "A small gas turbine, high power and a very wide powerband. Outputs to the side instead of rear.", + Model = "models/engines/turbine_s.mdl", + Sound = "acf_base/engines/turbine_small.wav", + Fuel = { Petrol = true, Diesel = true }, + Type = "Turbine", + Mass = 160, + Torque = 550, + FlywheelMass = 2.3, + IsElectric = true, + IsTrans = true, + RPM = { + Idle = 1400, + PeakMin = 1000, + PeakMax = 1500, + Limit = 10000, + Override = 4167, + } + }) + + ACF.RegisterEngine("Turbine-Medium-Trans", "GT", { + Name = "Medium Transaxial Gas Turbine", + Description = "A medium gas turbine, moderate power but a very wide powerband. Outputs to the side instead of rear.", + Model = "models/engines/turbine_m.mdl", + Sound = "acf_base/engines/turbine_medium.wav", + Fuel = { Petrol = true, Diesel = true }, + Type = "Turbine", + Mass = 320, + Torque = 812, + FlywheelMass = 3.4, + IsElectric = true, + IsTrans = true, + RPM = { + Idle = 1800, + PeakMin = 1200, + PeakMax = 1800, + Limit = 12000, + Override = 5000, + } + }) + + ACF.RegisterEngine("Turbine-Large-Trans", "GT", { + Name = "Large Transaxial Gas Turbine", + Description = "A large gas turbine, powerful with a wide powerband. Outputs to the side instead of rear.", + Model = "models/engines/turbine_l.mdl", + Sound = "acf_base/engines/turbine_large.wav", + Fuel = { Petrol = true, Diesel = true }, + Type = "Turbine", + Mass = 880, + Torque = 1990, + FlywheelMass = 8.4, + IsElectric = true, + IsTrans = true, + RPM = { + Idle = 2000, + PeakMin = 1350, + PeakMax = 2025, + Limit = 13500, + Override = 5625, + } + }) +end + +ACF.RegisterEngineClass("GGT", { + Name = "Ground Gas Turbine", + Description = "Ground-use turbines have excellent low-rev performance and are deceptively powerful. However, they have high gearbox demands, high fuel usage and low tolerance to damage." +}) + +do -- Forward-facing Ground Gas Turbines + ACF.RegisterEngine("Turbine-Ground-Small", "GGT", { + Name = "Small Ground Gas Turbine", + Description = "A small gas turbine, fitted with ground-use air filters and tuned for ground use.", + Model = "models/engines/gasturbine_s.mdl", + Sound = "acf_base/engines/turbine_small.wav", + Fuel = { Petrol = true, Diesel = true }, + Type = "Radial", + Mass = 350, + Torque = 1000, + FlywheelMass = 14.3, + IsElectric = true, + RPM = { + Idle = 700, + PeakMin = 1000, + PeakMax = 1350, + Limit = 3000, + Override = 1667, + } + }) + + ACF.RegisterEngine("Turbine-Ground-Medium", "GGT", { + Name = "Medium Ground Gas Turbine", + Description = "A medium gas turbine, fitted with ground-use air filters and tuned for ground use.", + Model = "models/engines/gasturbine_m.mdl", + Sound = "acf_base/engines/turbine_medium.wav", + Fuel = { Petrol = true, Diesel = true }, + Type = "Radial", --This is done to give proper fuel consumption and make the turbines not instant-torque from idle + Mass = 600, + Torque = 1500, + FlywheelMass = 29.6, + IsElectric = true, + Pitch = 1.15, + RPM = { + Idle = 600, + PeakMin = 1500, + PeakMax = 2000, + Limit = 3000, + Override = 1450, + } + }) + + ACF.RegisterEngine("Turbine-Ground-Large", "GGT", { + Name = "Large Ground Gas Turbine", + Description = "A large gas turbine, fitted with ground-use air filters and tuned for ground use.", + Model = "models/engines/gasturbine_l.mdl", + Sound = "acf_base/engines/turbine_large.wav", + Fuel = { Petrol = true, Diesel = true }, + Type = "Radial", + Mass = 1650, + Torque = 5000, + FlywheelMass = 75, + IsElectric = true, + Pitch = 1.35, + RPM = { + Idle = 500, + PeakMin = 1000, + PeakMax = 1250, + Limit = 3000, + Override = 1250, + } + }) +end + +do -- Transaxial Ground Gas Turbines + ACF.RegisterEngine("Turbine-Small-Ground-Trans", "GGT", { + Name = "Small Transaxial Ground Gas Turbine", + Description = "A small gas turbine fitted with ground-use air filters and tuned for ground use. Outputs to the side instead of rear.", + Model = "models/engines/turbine_s.mdl", + Sound = "acf_base/engines/turbine_small.wav", + Fuel = { Petrol = true, Diesel = true }, + Type = "Radial", + Mass = 280, + Torque = 750, + FlywheelMass = 11.4, + IsElectric = true, + IsTrans = true, + RPM = { + Idle = 700, + PeakMin = 1000, + PeakMax = 1350, + Limit = 3000, + Override = 1667, + } + }) + + ACF.RegisterEngine("Turbine-Medium-Ground-Trans", "GGT", { + Name = "Medium Transaxial Ground Gas Turbine", + Description = "A medium gas turbine fitted with ground-use air filters and tuned for ground use. Outputs to the side instead of rear.", + Model = "models/engines/turbine_m.mdl", + Sound = "acf_base/engines/turbine_medium.wav", + Fuel = { Petrol = true, Diesel = true }, + Type = "Radial", + Mass = 480, + Torque = 1125, + FlywheelMass = 23.7, + IsElectric = true, + IsTrans = true, + Pitch = 1.15, + RPM = { + Idle = 600, + PeakMin = 1500, + PeakMax = 2000, + Limit = 3000, + Override = 1450, + } + }) + + ACF.RegisterEngine("Turbine-Large-Ground-Trans", "GGT", { + Name = "Large Transaxial Ground Gas Turbine", + Description = "A large gas turbine fitted with ground-use air filters and tuned for ground use. Outputs to the side instead of rear.", + Model = "models/engines/turbine_l.mdl", + Sound = "acf_base/engines/turbine_large.wav", + Fuel = { Petrol = true, Diesel = true }, + Type = "Radial", + Mass = 1320, + Torque = 3750, + FlywheelMass = 60, + IsElectric = true, + IsTrans = true, + Pitch = 1.35, + RPM = { + Idle = 500, + PeakMin = 1000, + PeakMax = 1250, + Limit = 3000, + Override = 1250, + } + }) +end + +ACF.SetCustomAttachment("models/engines/turbine_l.mdl", "driveshaft", Vector(0, -15), Angle(0, -90)) +ACF.SetCustomAttachment("models/engines/turbine_m.mdl", "driveshaft", Vector(0, -11.25), Angle(0, -90)) +ACF.SetCustomAttachment("models/engines/turbine_s.mdl", "driveshaft", Vector(0, -7.5), Angle(0, -90)) +ACF.SetCustomAttachment("models/engines/gasturbine_l.mdl", "driveshaft", Vector(-42), Angle(0, -180)) +ACF.SetCustomAttachment("models/engines/gasturbine_m.mdl", "driveshaft", Vector(-31.5), Angle(0, -180)) +ACF.SetCustomAttachment("models/engines/gasturbine_s.mdl", "driveshaft", Vector(-21), Angle(0, -180)) diff --git a/lua/acf/shared/engines/v10.lua b/lua/acf/shared/engines/v10.lua index 47ce83779..f8a337ab0 100644 --- a/lua/acf/shared/engines/v10.lua +++ b/lua/acf/shared/engines/v10.lua @@ -1,52 +1,65 @@ --V10s -ACF_DefineEngine( "4.3-V10", { - name = "4.3L V10 Petrol", - desc = "Small-block V-10 gasoline engine, great for powering a hot rod lincoln", - model = "models/engines/v10sml.mdl", - sound = "acf_base/engines/v10_petrolsmall.wav", - category = "V10", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 160, - torque = 360, - flywheelmass = 0.2, - idlerpm = 900, - peakminrpm = 3500, - peakmaxrpm = 5800, - limitrpm = 6250 -} ) +ACF.RegisterEngineClass("V10", { + Name = "V10 Engine", +}) -ACF_DefineEngine( "8.0-V10", { - name = "8.0L V10 Petrol", - desc = "Beefy 10-cylinder gas engine, gets 9 kids to soccer practice", - model = "models/engines/v10med.mdl", - sound = "acf_base/engines/v10_petrolmedium.wav", - category = "V10", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 300, - torque = 612, - flywheelmass = 0.5, - idlerpm = 750, - peakminrpm = 3400, - peakmaxrpm = 5500, - limitrpm = 6500 -} ) +do + ACF.RegisterEngine("4.3-V10", "V10", { + Name = "4.3L V10 Petrol", + Description = "Small-block V-10 gasoline engine, great for powering a hot rod lincoln", + Model = "models/engines/v10sml.mdl", + Sound = "acf_base/engines/v10_petrolsmall.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 160, + Torque = 360, + FlywheelMass = 0.2, + RPM = { + Idle = 900, + PeakMin = 3500, + PeakMax = 5800, + Limit = 6250, + } + }) -ACF_DefineEngine( "22.0-V10", { - name = "22.0L V10 Multifuel", - desc = "Heavy multifuel V-10, gearbox-shredding torque but very heavy.", - model = "models/engines/v10big.mdl", - sound = "acf_base/engines/v10_diesellarge.wav", - category = "V10", - fuel = "Multifuel", - enginetype = "GenericDiesel", - weight = 1600, - torque = 3256, - flywheelmass = 5, - idlerpm = 525, - peakminrpm = 750, - peakmaxrpm = 1900, - limitrpm = 2500 -} ) + ACF.RegisterEngine("8.0-V10", "V10", { + Name = "8.0L V10 Petrol", + Description = "Beefy 10-cylinder gas engine, gets 9 kids to soccer practice", + Model = "models/engines/v10med.mdl", + Sound = "acf_base/engines/v10_petrolmedium.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 300, + Torque = 612, + FlywheelMass = 0.5, + RPM = { + Idle = 750, + PeakMin = 3400, + PeakMax = 5500, + Limit = 6500, + } + }) + + ACF.RegisterEngine("22.0-V10", "V10", { + Name = "22.0L V10 Multifuel", + Description = "Heavy multifuel V-10, gearbox-shredding torque but very heavy.", + Model = "models/engines/v10big.mdl", + Sound = "acf_base/engines/v10_diesellarge.wav", + Fuel = { Petrol = true, Diesel = true }, + Type = "GenericDiesel", + Mass = 1600, + Torque = 3256, + FlywheelMass = 5, + RPM = { + Idle = 525, + PeakMin = 750, + PeakMax = 1900, + Limit = 2500, + } + }) +end + +ACF.SetCustomAttachment("models/engines/v10big.mdl", "driveshaft", Vector(-33, 0, 7.2), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/engines/v10med.mdl", "driveshaft", Vector(-21.95, 0, 4.79), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/engines/v10sml.mdl", "driveshaft", Vector(-17.56, 0, 3.83), Angle(0, 0, 90)) diff --git a/lua/acf/shared/engines/v12.lua b/lua/acf/shared/engines/v12.lua index bae3a9cb3..7a7394395 100644 --- a/lua/acf/shared/engines/v12.lua +++ b/lua/acf/shared/engines/v12.lua @@ -1,125 +1,140 @@ -- V12 engines --- Petrol +ACF.RegisterEngineClass("V12", { + Name = "V12 Engine", +}) -ACF_DefineEngine( "4.6-V12", { - name = "4.6L V12 Petrol", - desc = "An elderly racecar engine; low on torque, but plenty of power", - model = "models/engines/v12s.mdl", - sound = "acf_base/engines/v12_petrolsmall.wav", - category = "V12", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 188, - torque = 293, - flywheelmass = 0.2, - idlerpm = 1000, - peakminrpm = 4500, - peakmaxrpm = 7500, - limitrpm = 8000 -} ) +do -- Petrol Engines + ACF.RegisterEngine("4.6-V12", "V12", { + Name = "4.6L V12 Petrol", + Description = "An elderly racecar engine; low on torque, but plenty of power", + Model = "models/engines/v12s.mdl", + Sound = "acf_base/engines/v12_petrolsmall.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 188, + Torque = 293, + FlywheelMass = 0.2, + RPM = { + Idle = 1000, + PeakMin = 4500, + PeakMax = 7500, + Limit = 8000, + } + }) -ACF_DefineEngine( "7.0-V12", { - name = "7.0L V12 Petrol", - desc = "A high end V12; primarily found in very expensive cars", - model = "models/engines/v12m.mdl", - sound = "acf_base/engines/v12_petrolmedium.wav", - category = "V12", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 360, - torque = 625, - flywheelmass = 0.45, - idlerpm = 800, - peakminrpm = 3600, - peakmaxrpm = 6000, - limitrpm = 7500 -} ) + ACF.RegisterEngine("7.0-V12", "V12", { + Name = "7.0L V12 Petrol", + Description = "A high end V12; primarily found in very expensive cars", + Model = "models/engines/v12m.mdl", + Sound = "acf_base/engines/v12_petrolmedium.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 360, + Torque = 625, + FlywheelMass = 0.45, + RPM = { + Idle = 800, + PeakMin = 3600, + PeakMax = 6000, + Limit = 7500, + } + }) -ACF_DefineEngine( "23.0-V12", { - name = "23.0L V12 Petrol", - desc = "A large, thirsty gasoline V12, found in early cold war tanks", - model = "models/engines/v12l.mdl", - sound = "acf_base/engines/v12_petrollarge.wav", - category = "V12", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 1350, - torque = 2406, - flywheelmass = 5, - idlerpm = 600, - peakminrpm = 1500, - peakmaxrpm = 3000, - limitrpm = 3250 -} ) + ACF.RegisterEngine("13.0-V12", "V12", { + Name = "13.0L V12 Petrol", + Description = "Thirsty gasoline v12, good torque and power for medium applications.", + Model = "models/engines/v12m.mdl", + Sound = "acf_base/engines/v12_special.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 520, + Torque = 825, + FlywheelMass = 1, + RPM = { + Idle = 700, + PeakMin = 2500, + PeakMax = 4000, + Limit = 4250, + } + }) --- Diesel + ACF.RegisterEngine("23.0-V12", "V12", { + Name = "23.0L V12 Petrol", + Description = "A large, thirsty gasoline V12, found in early cold war tanks", + Model = "models/engines/v12l.mdl", + Sound = "acf_base/engines/v12_petrollarge.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 1350, + Torque = 2406, + FlywheelMass = 5, + RPM = { + Idle = 600, + PeakMin = 1500, + PeakMax = 3000, + Limit = 3250, + } + }) +end -ACF_DefineEngine( "4.0-V12", { - name = "4.0L V12 Diesel", - desc = "Reliable truck-duty diesel; a lot of smooth torque", - model = "models/engines/v12s.mdl", - sound = "acf_base/engines/v12_dieselsmall.wav", - category = "V12", - fuel = "Diesel", - enginetype = "GenericDiesel", - weight = 305, - torque = 468, - flywheelmass = 0.475, - idlerpm = 650, - peakminrpm = 1200, - peakmaxrpm = 3800, - limitrpm = 4000 -} ) +do -- Diesel Engines + ACF.RegisterEngine("4.0-V12", "V12", { + Name = "4.0L V12 Diesel", + Description = "Reliable truck-duty diesel; a lot of smooth torque", + Model = "models/engines/v12s.mdl", + Sound = "acf_base/engines/v12_dieselsmall.wav", + Fuel = { Diesel = true }, + Type = "GenericDiesel", + Mass = 305, + Torque = 468, + FlywheelMass = 0.475, + RPM = { + Idle = 650, + PeakMin = 1200, + PeakMax = 3800, + Limit = 4000, + } + }) -ACF_DefineEngine( "9.2-V12", { - name = "9.2L V12 Diesel", - desc = "High torque light-tank V12, used mainly for vehicles that require balls", - model = "models/engines/v12m.mdl", - sound = "acf_base/engines/v12_dieselmedium.wav", - category = "V12", - fuel = "Diesel", - enginetype = "GenericDiesel", - weight = 600, - torque = 937, - flywheelmass = 2.5, - idlerpm = 675, - peakminrpm = 1100, - peakmaxrpm = 3300, - limitrpm = 3500 -} ) + ACF.RegisterEngine("9.2-V12", "V12", { + Name = "9.2L V12 Diesel", + Description = "High torque light-tank V12, used mainly for vehicles that require balls", + Model = "models/engines/v12m.mdl", + Sound = "acf_base/engines/v12_dieselmedium.wav", + Fuel = { Diesel = true }, + Type = "GenericDiesel", + Mass = 600, + Torque = 937, + FlywheelMass = 2.5, + RPM = { + Idle = 675, + PeakMin = 1100, + PeakMax = 3300, + Limit = 3500, + } + }) -ACF_DefineEngine( "21.0-V12", { - name = "21.0L V12 Diesel", - desc = "AVDS-1790-2 tank engine; massively powerful, but enormous and heavy", - model = "models/engines/v12l.mdl", - sound = "acf_base/engines/v12_diesellarge.wav", - category = "V12", - fuel = "Diesel", - enginetype = "GenericDiesel", - weight = 1800, - torque = 4450, - flywheelmass = 7, - idlerpm = 400, - peakminrpm = 500, - peakmaxrpm = 1500, - limitrpm = 2500 -} ) + ACF.RegisterEngine("21.0-V12", "V12", { + Name = "21.0L V12 Diesel", + Description = "AVDS-1790-2 tank engine; massively powerful, but enormous and heavy", + Model = "models/engines/v12l.mdl", + Sound = "acf_base/engines/v12_diesellarge.wav", + Fuel = { Diesel = true }, + Type = "GenericDiesel", + Mass = 1800, + Torque = 4450, + FlywheelMass = 7, + RPM = { + Idle = 400, + PeakMin = 500, + PeakMax = 1500, + Limit = 2500, + } + }) +end -ACF_DefineEngine( "13.0-V12", { - name = "13.0L V12 Petrol", - desc = "Thirsty gasoline v12, good torque and power for medium applications.", - model = "models/engines/v12m.mdl", - sound = "acf_base/engines/v12_special.wav", - category = "V12", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 520, - torque = 825, - flywheelmass = 1, - idlerpm = 700, - peakminrpm = 2500, - peakmaxrpm = 4000, - limitrpm = 4250 -} ) +ACF.SetCustomAttachment("models/engines/v12l.mdl", "driveshaft", Vector(-34, 0, 7.3), Angle(0, 90, 90)) +ACF.SetCustomAttachment("models/engines/v12m.mdl", "driveshaft", Vector(-22.61, 0, 4.85), Angle(0, 90, 90)) +ACF.SetCustomAttachment("models/engines/v12s.mdl", "driveshaft", Vector(-18.09, 0, 3.88), Angle(0, 90, 90)) diff --git a/lua/acf/shared/engines/v2.lua b/lua/acf/shared/engines/v2.lua index 30b87dff9..53f716a0c 100644 --- a/lua/acf/shared/engines/v2.lua +++ b/lua/acf/shared/engines/v2.lua @@ -1,53 +1,66 @@ -- V-Twin engines -ACF_DefineEngine( "0.6-V2", { - name = "600cc V-Twin", - desc = "Twin cylinder bike engine, torquey for its size", - model = "models/engines/v-twins2.mdl", - sound = "acf_base/engines/vtwin_small.wav", - category = "V-Twin", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 30, - torque = 62, - flywheelmass = 0.01, - idlerpm = 900, - peakminrpm = 4000, - peakmaxrpm = 6500, - limitrpm = 7000 -} ) +ACF.RegisterEngineClass("V2", { + Name = "V-Twin Engine", +}) -ACF_DefineEngine( "1.2-V2", { - name = "1200cc V-Twin", - desc = "Large displacement vtwin engine", - model = "models/engines/v-twinm2.mdl", - sound = "acf_base/engines/vtwin_medium.wav", - category = "V-Twin", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 50, - torque = 106, - flywheelmass = 0.02, - idlerpm = 725, - peakminrpm = 3300, - peakmaxrpm = 5500, - limitrpm = 6250 -} ) +do -- Petrol Engines + ACF.RegisterEngine("0.6-V2", "V2", { + Name = "600cc V-Twin", + Description = "Twin cylinder bike engine, torquey for its size", + Model = "models/engines/v-twins2.mdl", + Sound = "acf_base/engines/vtwin_small.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 30, + Torque = 62, + FlywheelMass = 0.01, + RPM = { + Idle = 900, + PeakMin = 4000, + PeakMax = 6500, + Limit = 7000, + } + }) -ACF_DefineEngine( "2.4-V2", { - name = "2400cc V-Twin", - desc = "Huge fucking Vtwin 'MURRICA FUCK YEAH", - model = "models/engines/v-twinl2.mdl", - sound = "acf_base/engines/vtwin_large.wav", - category = "V-Twin", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 100, - torque = 200, - flywheelmass = 0.075, - idlerpm = 900, - peakminrpm = 3300, - peakmaxrpm = 5500, - limitrpm = 6000 -} ) + ACF.RegisterEngine("1.2-V2", "V2", { + Name = "1200cc V-Twin", + Description = "Large displacement vtwin engine", + Model = "models/engines/v-twinm2.mdl", + Sound = "acf_base/engines/vtwin_medium.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 50, + Torque = 106, + FlywheelMass = 0.02, + RPM = { + Idle = 725, + PeakMin = 3300, + PeakMax = 5500, + Limit = 6250, + } + }) + + ACF.RegisterEngine("2.4-V2", "V2", { + Name = "2400cc V-Twin", + Description = "Huge fucking Vtwin 'MURRICA FUCK YEAH", + Model = "models/engines/v-twinl2.mdl", + Sound = "acf_base/engines/vtwin_large.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 100, + Torque = 200, + FlywheelMass = 0.075, + RPM = { + Idle = 900, + PeakMin = 3300, + PeakMax = 5500, + Limit = 6000, + } + }) +end + +ACF.SetCustomAttachment("models/engines/v-twinl2.mdl", "driveshaft", Vector(), Angle(0, 90, 90)) +ACF.SetCustomAttachment("models/engines/v-twinm2.mdl", "driveshaft", Vector(), Angle(0, 90, 90)) +ACF.SetCustomAttachment("models/engines/v-twins2.mdl", "driveshaft", Vector(), Angle(0, 90, 90)) diff --git a/lua/acf/shared/engines/v4.lua b/lua/acf/shared/engines/v4.lua index 500d01218..e89650773 100644 --- a/lua/acf/shared/engines/v4.lua +++ b/lua/acf/shared/engines/v4.lua @@ -1,38 +1,47 @@ --V4 Engines ---Diesel +ACF.RegisterEngineClass("V4", { + Name = "V4 Engine", +}) -ACF_DefineEngine( "1.9L-V4", { - name = "1.9L V4 Diesel", - desc = "Torquey little lunchbox; for those smaller vehicles that don't agree with petrol powerbands", - model = "models/engines/v4s.mdl", - sound = "acf_base/engines/i4_diesel2.wav", - category = "V4", - fuel = "Diesel", - enginetype = "GenericDiesel", - weight = 110, - torque = 206, - flywheelmass = 0.3, - idlerpm = 650, - peakminrpm = 950, - peakmaxrpm = 3000, - limitrpm = 4000 -} ) +do -- Diesel Engines + ACF.RegisterEngine("1.9L-V4", "V4", { + Name = "1.9L V4 Diesel", + Description = "Torquey little lunchbox; for those smaller vehicles that don't agree with petrol powerbands", + Model = "models/engines/v4s.mdl", + Sound = "acf_base/engines/i4_diesel2.wav", + Fuel = { Diesel = true }, + Type = "GenericDiesel", + Mass = 110, + Torque = 206, + FlywheelMass = 0.3, + RPM = { + Idle = 650, + PeakMin = 950, + PeakMax = 3000, + Limit = 4000, + } + }) -ACF_DefineEngine( "3.3L-V4", { - name = "3.3L V4 Diesel", - desc = "Compact cube of git; for moderate utility applications", - model = "models/engines/v4m.mdl", - sound = "acf_base/engines/i4_dieselmedium.wav", - category = "V4", - fuel = "Diesel", - enginetype = "GenericDiesel", - weight = 275, - torque = 600, - flywheelmass = 1.05, - idlerpm = 600, - peakminrpm = 1050, - peakmaxrpm = 3100, - limitrpm = 3900 -} ) + ACF.RegisterEngine("3.3L-V4", "V4", { + Name = "3.3L V4 Diesel", + Description = "Compact cube of git; for moderate utility applications", + Model = "models/engines/v4m.mdl", + Sound = "acf_base/engines/i4_dieselmedium.wav", + Fuel = { Diesel = true }, + Type = "GenericDiesel", + Mass = 275, + Torque = 600, + FlywheelMass = 1.05, + RPM = { + Idle = 600, + PeakMin = 1050, + PeakMax = 3100, + Limit = 3900, + } + }) +end + +ACF.SetCustomAttachment("models/engines/v4m.mdl", "driveshaft", Vector(-5.99, 0, 4.85), Angle(0, 90, 90)) +ACF.SetCustomAttachment("models/engines/v4s.mdl", "driveshaft", Vector(-4.79, 0, 3.88), Angle(0, 90, 90)) diff --git a/lua/acf/shared/engines/v6.lua b/lua/acf/shared/engines/v6.lua index 2286c2a3a..02ff06541 100644 --- a/lua/acf/shared/engines/v6.lua +++ b/lua/acf/shared/engines/v6.lua @@ -1,87 +1,105 @@ -- V6 engines -ACF_DefineEngine( "3.6-V6", { - name = "3.6L V6 Petrol", - desc = "Meaty Car sized V6, lots of torque\n\nV6s are more torquey than the Boxer and Inline 6s but suffer in power", - model = "models/engines/v6small.mdl", - sound = "acf_base/engines/v6_petrolsmall.wav", - category = "V6", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 190, - torque = 316, - flywheelmass = 0.25, - idlerpm = 700, - peakminrpm = 2200, - peakmaxrpm = 3500, - limitrpm = 5000 -} ) +ACF.RegisterEngineClass("V6", { + Name = "V6 Engine", + Description = "V6s are more torquey than the Boxer and Inline 6s but suffer in power." +}) -ACF_DefineEngine( "6.2-V6", { - name = "6.2L V6 Petrol", - desc = "Heavy duty 6V71 v6, throatier than an LA whore, but loaded with torque\n\nV6s are more torquey than the Boxer and Inline 6s but suffer in power", - model = "models/engines/v6med.mdl", - sound = "acf_base/engines/v6_petrolmedium.wav", - category = "V6", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 360, - torque = 590, - flywheelmass = 0.45, - idlerpm = 800, - peakminrpm = 2200, - peakmaxrpm = 3600, - limitrpm = 5000 -} ) +do -- Petrol Engines + ACF.RegisterEngine("3.6-V6", "V6", { + Name = "3.6L V6 Petrol", + Description = "Meaty Car sized V6, lots of torque.", + Model = "models/engines/v6small.mdl", + Sound = "acf_base/engines/v6_petrolsmall.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 190, + Torque = 316, + FlywheelMass = 0.25, + RPM = { + Idle = 700, + PeakMin = 2200, + PeakMax = 3500, + Limit = 5000, + } + }) -ACF_DefineEngine( "5.2-V6", { - name = "5.2L V6 Diesel", - desc = "Light AFV-grade two-stroke multifuel, high output but heavy", - model = "models/engines/v6med.mdl", - sound = "acf_base/engines/i5_dieselmedium.wav", - category = "V6", - fuel = "Multifuel", - enginetype = "GenericDiesel", - weight = 520, - torque = 606, - flywheelmass = 0.8, - idlerpm = 650, - peakminrpm = 1800, - peakmaxrpm = 4200, - limitrpm = 4300 -} ) + ACF.RegisterEngine("6.2-V6", "V6", { + Name = "6.2L V6 Petrol", + Description = "Heavy duty 6V71 v6, throatier than an LA whore, but loaded with torque.", + Model = "models/engines/v6med.mdl", + Sound = "acf_base/engines/v6_petrolmedium.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 360, + Torque = 590, + FlywheelMass = 0.45, + RPM = { + Idle = 800, + PeakMin = 2200, + PeakMax = 3600, + Limit = 5000, + } + }) -ACF_DefineEngine( "12.0-V6", { - name = "12.0L V6 Petrol", - desc = "Fuck duty V6, guts ripped from god himself diluted in salt and shaped into an engine.\n\nV6s are more torquey than the Boxer and Inline 6s but suffer in power", - model = "models/engines/v6large.mdl", - sound = "acf_base/engines/v6_petrollarge.wav", - category = "V6", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 675, - torque = 1806, - flywheelmass = 4, - idlerpm = 600, - peakminrpm = 1575, - peakmaxrpm = 2650, - limitrpm = 3800 -} ) + ACF.RegisterEngine("12.0-V6", "V6", { + Name = "12.0L V6 Petrol", + Description = "Fuck duty V6, guts ripped from god himself diluted in salt and shaped into an engine.", + Model = "models/engines/v6large.mdl", + Sound = "acf_base/engines/v6_petrollarge.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 675, + Torque = 1806, + FlywheelMass = 4, + RPM = { + Idle = 600, + PeakMin = 1575, + PeakMax = 2650, + Limit = 3800, + } + }) +end -ACF_DefineEngine( "15.0-V6", { - name = "15.0L V6 Diesel", - desc = "Powerful military-grade large V6, with impressive output. Well suited to moderately-sized AFVs and able to handle multiple fuel types.\n\nV6s are more torquey than the Boxer and Inline 6s but suffer in power", - model = "models/engines/v6large.mdl", - sound = "acf_base/engines/v6_diesellarge.wav", - category = "V6", - fuel = "Multifuel", - enginetype = "GenericDiesel", - weight = 900, - torque = 2208, - flywheelmass = 6.4, - idlerpm = 400, - peakminrpm = 1150, - peakmaxrpm = 1950, - limitrpm = 3100 -} ) +do -- Diesel Engines + ACF.RegisterEngine("5.2-V6", "V6", { + Name = "5.2L V6 Diesel", + Description = "Light AFV-grade two-stroke diesel, high output but heavy.", + Model = "models/engines/v6med.mdl", + Sound = "acf_base/engines/i5_dieselmedium.wav", + Fuel = { Diesel = true }, + Type = "GenericDiesel", + Mass = 520, + Torque = 606, + FlywheelMass = 0.8, + RPM = { + Idle = 650, + PeakMin = 1800, + PeakMax = 4200, + Limit = 4300, + } + }) + + ACF.RegisterEngine("15.0-V6", "V6", { + Name = "15.0L V6 Diesel", + Description = "Powerful military-grade large V6, with impressive output. Well suited to medium-sized AFVs.", + Model = "models/engines/v6large.mdl", + Sound = "acf_base/engines/v6_diesellarge.wav", + Fuel = { Diesel = true }, + Type = "GenericDiesel", + Mass = 900, + Torque = 2208, + FlywheelMass = 6.4, + RPM = { + Idle = 400, + PeakMin = 1150, + PeakMax = 1950, + Limit = 3100, + } + }) +end + +ACF.SetCustomAttachment("models/engines/v6large.mdl", "driveshaft", Vector(2), Angle(0, 90, 90)) +ACF.SetCustomAttachment("models/engines/v6med.mdl", "driveshaft", Vector(1.33), Angle(0, 90, 90)) +ACF.SetCustomAttachment("models/engines/v6small.mdl", "driveshaft", Vector(1.06), Angle(0, 90, 90)) diff --git a/lua/acf/shared/engines/v8.lua b/lua/acf/shared/engines/v8.lua index 6970f3590..acd94b6d2 100644 --- a/lua/acf/shared/engines/v8.lua +++ b/lua/acf/shared/engines/v8.lua @@ -1,108 +1,122 @@ -- V8 engines --- Petrol +ACF.RegisterEngineClass("V8", { + Name = "V8 Engine", +}) -ACF_DefineEngine( "5.7-V8", { - name = "5.7L V8 Petrol", - desc = "Car sized petrol engine, good power and mid range torque", - model = "models/engines/v8s.mdl", - sound = "acf_base/engines/v8_petrolsmall.wav", - category = "V8", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 260, - torque = 400, - flywheelmass = 0.15, - idlerpm = 800, - peakminrpm = 3000, - peakmaxrpm = 5000, - limitrpm = 6500 -} ) +do -- Petrol Engines + ACF.RegisterEngine("5.7-V8", "V8", { + Name = "5.7L V8 Petrol", + Description = "Car sized petrol engine, good power and mid range torque", + Model = "models/engines/v8s.mdl", + Sound = "acf_base/engines/v8_petrolsmall.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 260, + Torque = 400, + FlywheelMass = 0.15, + RPM = { + Idle = 800, + PeakMin = 3000, + PeakMax = 5000, + Limit = 6500, + } + }) -ACF_DefineEngine( "9.0-V8", { - name = "9.0L V8 Petrol", - desc = "Thirsty, giant V8, for medium applications", - model = "models/engines/v8m.mdl", - sound = "acf_base/engines/v8_petrolmedium.wav", - category = "V8", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 400, - torque = 575, - flywheelmass = 0.25, - idlerpm = 700, - peakminrpm = 3100, - peakmaxrpm = 5000, - limitrpm = 5500 -} ) + ACF.RegisterEngine("9.0-V8", "V8", { + Name = "9.0L V8 Petrol", + Description = "Thirsty, giant V8, for medium applications", + Model = "models/engines/v8m.mdl", + Sound = "acf_base/engines/v8_petrolmedium.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 400, + Torque = 575, + FlywheelMass = 0.25, + RPM = { + Idle = 700, + PeakMin = 3100, + PeakMax = 5000, + Limit = 5500, + } + }) -ACF_DefineEngine( "18.0-V8", { - name = "18.0L V8 Petrol", - desc = "American gasoline tank V8, good overall power and torque and fairly lightweight", - model = "models/engines/v8l.mdl", - sound = "acf_base/engines/v8_petrollarge.wav", - category = "V8", - fuel = "Petrol", - enginetype = "GenericPetrol", - weight = 850, - torque = 1822, - flywheelmass = 2.8, - idlerpm = 600, - peakminrpm = 2000, - peakmaxrpm = 3300, - limitrpm = 3800 -} ) + ACF.RegisterEngine("18.0-V8", "V8", { + Name = "18.0L V8 Petrol", + Description = "American gasoline tank V8, good overall power and torque and fairly lightweight", + Model = "models/engines/v8l.mdl", + Sound = "acf_base/engines/v8_petrollarge.wav", + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 850, + Torque = 1822, + FlywheelMass = 2.8, + RPM = { + Idle = 600, + PeakMin = 2000, + PeakMax = 3300, + Limit = 3800, + } + }) +end --- Diesel +do -- Diesel Engines + ACF.RegisterEngine("4.5-V8", "V8", { + Name = "4.5L V8 Diesel", + Description = "Light duty diesel v8, good for light vehicles that require a lot of torque", + Model = "models/engines/v8s.mdl", + Sound = "acf_base/engines/v8_dieselsmall.wav", + Fuel = { Diesel = true }, + Type = "GenericDiesel", + Mass = 320, + Torque = 518, + FlywheelMass = 0.75, + RPM = { + Idle = 800, + PeakMin = 1000, + PeakMax = 3000, + Limit = 5000, + } + }) -ACF_DefineEngine( "4.5-V8", { - name = "4.5L V8 Diesel", - desc = "Light duty diesel v8, good for light vehicles that require a lot of torque", - model = "models/engines/v8s.mdl", - sound = "acf_base/engines/v8_dieselsmall.wav", - category = "V8", - fuel = "Diesel", - enginetype = "GenericDiesel", - weight = 320, - torque = 518, - flywheelmass = 0.75, - idlerpm = 800, - peakminrpm = 1000, - peakmaxrpm = 3000, - limitrpm = 5000 -} ) + ACF.RegisterEngine("7.8-V8", "V8", { + Name = "7.8L V8 Diesel", + Description = "Redneck chariot material. Truck duty V8 diesel, has a good, wide powerband", + Model = "models/engines/v8m.mdl", + Sound = "acf_base/engines/v8_dieselmedium2.wav", + Fuel = { Diesel = true }, + Type = "GenericDiesel", + Mass = 520, + Torque = 875, + FlywheelMass = 1.6, + RPM = { + Idle = 650, + PeakMin = 1000, + PeakMax = 3000, + Limit = 4000, + } + }) -ACF_DefineEngine( "7.8-V8", { - name = "7.8L V8 Diesel", - desc = "Redneck chariot material. Truck duty V8 diesel, has a good, wide powerband", - model = "models/engines/v8m.mdl", - sound = "acf_base/engines/v8_dieselmedium2.wav", - category = "V8", - fuel = "Diesel", - enginetype = "GenericDiesel", - weight = 520, - torque = 875, - flywheelmass = 1.6, - idlerpm = 650, - peakminrpm = 1000, - peakmaxrpm = 3000, - limitrpm = 4000 -} ) + ACF.RegisterEngine("19.0-V8", "V8", { + Name = "19.0L V8 Diesel", + Description = "Heavy duty diesel V8, used in heavy construction equipment and tanks", + Model = "models/engines/v8l.mdl", + Sound = "acf_base/engines/v8_diesellarge.wav", + Fuel = { Diesel = true }, + Type = "GenericDiesel", + Mass = 1200, + Torque = 2875, + FlywheelMass = 4.5, + RPM = { + Idle = 500, + PeakMin = 600, + PeakMax = 1800, + Limit = 2500, + } + }) +end -ACF_DefineEngine( "19.0-V8", { - name = "19.0L V8 Diesel", - desc = "Heavy duty diesel V8, used in heavy construction equipment and tanks", - model = "models/engines/v8l.mdl", - sound = "acf_base/engines/v8_diesellarge.wav", - category = "V8", - fuel = "Diesel", - enginetype = "GenericDiesel", - weight = 1200, - torque = 2875, - flywheelmass = 4.5, - idlerpm = 500, - peakminrpm = 600, - peakmaxrpm = 1800, - limitrpm = 2500 -} ) +ACF.SetCustomAttachment("models/engines/v8l.mdl", "driveshaft", Vector(-25.6, 0, 7.4), Angle(0, 90, 90)) +ACF.SetCustomAttachment("models/engines/v8m.mdl", "driveshaft", Vector(-17.02, 0, 4.92), Angle(0, 90, 90)) +ACF.SetCustomAttachment("models/engines/v8s.mdl", "driveshaft", Vector(-13.62, 0, 3.94), Angle(0, 90, 90)) diff --git a/lua/acf/shared/fuel_types/diesel.lua b/lua/acf/shared/fuel_types/diesel.lua new file mode 100644 index 000000000..839051414 --- /dev/null +++ b/lua/acf/shared/fuel_types/diesel.lua @@ -0,0 +1,4 @@ +ACF.RegisterFuelType("Diesel", { + Name = "Diesel Fuel", + Density = 0.745, +}) diff --git a/lua/acf/shared/fuel_types/electric.lua b/lua/acf/shared/fuel_types/electric.lua new file mode 100644 index 000000000..527a0dbbe --- /dev/null +++ b/lua/acf/shared/fuel_types/electric.lua @@ -0,0 +1,17 @@ +ACF.RegisterFuelType("Electric", { + Name = "Lit-Ion Battery", + Density = 3.89, + ConsumptionText = function(PeakkW, _, Efficiency) + local Text = "Peak Energy Consumption :\n%s kW - %s MJ/min" + local Rate = ACF.FuelRate * PeakkW / Efficiency + + return Text:format(math.Round(Rate, 2), math.Round(Rate * 0.06, 2)) + end, + FuelTankText = function(Capacity, Mass) + local Text = "Charge : %s kW per hour - %s MJ\nMass : %s" + local kWh = math.Round(Capacity * ACF.LiIonED, 2) + local MJ = math.Round(Capacity * ACF.LiIonED * 3.6, 2) + + return Text:format(kWh, MJ, ACF.GetProperMass(Mass)) + end, +}) diff --git a/lua/acf/shared/fuel_types/petrol.lua b/lua/acf/shared/fuel_types/petrol.lua new file mode 100644 index 000000000..153662d85 --- /dev/null +++ b/lua/acf/shared/fuel_types/petrol.lua @@ -0,0 +1,4 @@ +ACF.RegisterFuelType("Petrol", { + Name = "Petrol Fuel", + Density = 0.832, +}) diff --git a/lua/acf/shared/fueltanks/basic.lua b/lua/acf/shared/fueltanks/basic.lua deleted file mode 100644 index 66924783a..000000000 --- a/lua/acf/shared/fueltanks/basic.lua +++ /dev/null @@ -1,355 +0,0 @@ - ---definition for the fuel tank that shows on menu -ACF_DefineFuelTank( "Basic_FuelTank", { - name = "Fuel Tank", - desc = "A steel box containing fuel.", - --category = "Fuel" -} ) - ---definitions for the possible tank sizes selectable from the basic tank. -ACF_DefineFuelTankSize( "Tank_1x1x1", { --ID used in code - name = "1x1x1", --human readable name - desc = "Seriously consider walking.", - model = "models/fueltank/fueltank_1x1x1.mdl", - dims = { S = 590.5, V = 1019.9 } --surface area and volume of prop in gmu, used for capacity calcs in gui -} ) - -ACF_DefineFuelTankSize( "Tank_1x1x2", { - name = "1x1x2", - desc = "Will keep a kart running all day.", - model = "models/fueltank/fueltank_1x1x2.mdl", - dims = { S = 974, V = 1983.1 } -} ) - -ACF_DefineFuelTankSize( "Tank_1x1x4", { - name = "1x1x4", - desc = "Dinghy", - model = "models/fueltank/fueltank_1x1x4.mdl", - dims = { S = 1777.4, V = 3995.1 } -} ) - -ACF_DefineFuelTankSize( "Tank_1x2x1", { - name = "1x2x1", - desc = "Will keep a kart running all day.", - model = "models/fueltank/fueltank_1x2x1.mdl", - dims = { S = 995, V = 2062.5 } -} ) - -ACF_DefineFuelTankSize( "Tank_1x2x2", { - name = "1x2x2", - desc = "Dinghy", - model = "models/fueltank/fueltank_1x2x2.mdl", - dims = { S = 1590.8, V = 4070.9 } -} ) - -ACF_DefineFuelTankSize( "Tank_1x2x4", { - name = "1x2x4", - desc = "Outboard motor.", - model = "models/fueltank/fueltank_1x2x4.mdl", - dims = { S = 2796.6, V = 8119.2 } -} ) - -ACF_DefineFuelTankSize( "Tank_1x4x1", { - name = "1x4x1", - desc = "Dinghy", - model = "models/fueltank/fueltank_1x4x1.mdl", - dims = { S = 1745.6, V = 3962 } -} ) - -ACF_DefineFuelTankSize( "Tank_1x4x2", { - name = "1x4x2", - desc = "Clown car.", - model = "models/fueltank/fueltank_1x4x2.mdl", - dims = { S = 2753.9, V = 8018 } -} ) - -ACF_DefineFuelTankSize( "Tank_1x4x4", { - name = "1x4x4", - desc = "Fuel pancake.", - model = "models/fueltank/fueltank_1x4x4.mdl", - dims = { S = 4761, V = 16030.4 } -} ) - -ACF_DefineFuelTankSize( "Tank_1x6x1", { - name = "1x6x1", - desc = "Lawn tractors.", - model = "models/fueltank/fueltank_1x6x1.mdl", - dims = { S = 2535.3, V = 5973.1 } -} ) - -ACF_DefineFuelTankSize( "Tank_1x6x2", { - name = "1x6x2", - desc = "Small tractor tank.", - model = "models/fueltank/fueltank_1x6x2.mdl", - dims = { S = 3954.1, V = 12100.3 } -} ) - -ACF_DefineFuelTankSize( "Tank_1x6x4", { - name = "1x6x4", - desc = "Fuel. Will keep you going for awhile.", - model = "models/fueltank/fueltank_1x6x4.mdl", - dims = { S = 6743.3, V = 24109.4 } -} ) - -ACF_DefineFuelTankSize( "Tank_1x8x1", { - name = "1x8x1", - desc = "Clown car.", - model = "models/fueltank/fueltank_1x8x1.mdl", - dims = { S = 3315.5, V = 7962.4 } -} ) - -ACF_DefineFuelTankSize( "Tank_1x8x2", { - name = "1x8x2", - desc = "Gas stations? We don't need no stinking gas stations!", - model = "models/fueltank/fueltank_1x8x2.mdl", - dims = { S = 5113.7, V = 16026.2 } -} ) - -ACF_DefineFuelTankSize( "Tank_1x8x4", { - name = "1x8x4", - desc = "Beep beep.", - model = "models/fueltank/fueltank_1x8x4.mdl", - dims = { S = 8696, V = 31871 } -} ) - -ACF_DefineFuelTankSize( "Tank_2x2x1", { - name = "2x2x1", - desc = "Dinghy", - model = "models/fueltank/fueltank_2x2x1.mdl", - dims = { S = 1592.2, V = 4285.2 } -} ) - -ACF_DefineFuelTankSize( "Tank_2x2x2", { - name = "2x2x2", - desc = "Clown car.", - model = "models/fueltank/fueltank_2x2x2.mdl", - dims = { S = 2360.4, V = 8212.9 } -} ) - -ACF_DefineFuelTankSize( "Tank_2x2x4", { - name = "2x2x4", - desc = "Mini Cooper.", - model = "models/fueltank/fueltank_2x2x4.mdl", - dims = { S = 3988.6, V = 16362 } -} ) - -ACF_DefineFuelTankSize( "Tank_2x4x1", { - name = "2x4x1", - desc = "Good bit of go-juice.", - model = "models/fueltank/fueltank_2x4x1.mdl", - dims = { S = 2808.8, V = 8628 } -} ) - -ACF_DefineFuelTankSize( "Tank_2x4x2", { - name = "2x4x2", - desc = "Mini Cooper.", - model = "models/fueltank/fueltank_2x4x2.mdl", - dims = { S = 3996.1, V = 16761.4 } -} ) - -ACF_DefineFuelTankSize( "Tank_2x4x4", { - name = "2x4x4", - desc = "Land boat.", - model = "models/fueltank/fueltank_2x4x4.mdl", - dims = { S = 6397.3, V = 32854.4 } -} ) - -ACF_DefineFuelTankSize( "Tank_2x6x1", { - name = "2x6x1", - desc = "Conformal fuel tank, fits narrow spaces.", - model = "models/fueltank/fueltank_2x6x1.mdl", - dims = { S = 3861.4, V = 12389.9 } -} ) - -ACF_DefineFuelTankSize( "Tank_2x6x2", { - name = "2x6x2", - desc = "Compact car.", - model = "models/fueltank/fueltank_2x6x2.mdl", - dims = { S = 5388, V = 24127.7 } -} ) - -ACF_DefineFuelTankSize( "Tank_2x6x4", { - name = "2x6x4", - desc = "Sedan.", - model = "models/fueltank/fueltank_2x6x4.mdl", - dims = { S = 8485.6, V = 47537.2 } -} ) - -ACF_DefineFuelTankSize( "Tank_2x8x1", { - name = "2x8x1", - desc = "Conformal fuel tank, fits into tight spaces", - model = "models/fueltank/fueltank_2x8x1.mdl", - dims = { S = 5094.5, V = 16831.8 } -} ) - -ACF_DefineFuelTankSize( "Tank_2x8x2", { - name = "2x8x2", - desc = "Truck.", - model = "models/fueltank/fueltank_2x8x2.mdl", - dims = { S = 6980, V = 32275.9 } -} ) - -ACF_DefineFuelTankSize( "Tank_2x8x4", { - name = "2x8x4", - desc = "With great capacity, comes great responsibili--VROOOOM", - model = "models/fueltank/fueltank_2x8x4.mdl", - dims = { S = 10898.2, V = 63976 } -} ) - -ACF_DefineFuelTankSize( "Tank_4x4x1", { - name = "4x4x1", - desc = "Sedan.", - model = "models/fueltank/fueltank_4x4x1.mdl", - dims = { S = 4619.1, V = 16539.8 } -} ) - -ACF_DefineFuelTankSize( "Tank_4x4x2", { - name = "4x4x2", - desc = "Land boat.", - model = "models/fueltank/fueltank_4x4x2.mdl", - dims = { S = 6071.4, V = 32165.2 } -} ) - -ACF_DefineFuelTankSize( "Tank_4x4x4", { - name = "4x4x4", - desc = "Popular with arsonists.", - model = "models/fueltank/fueltank_4x4x4.mdl", - dims = { S = 9145.3, V = 62900.1 } -} ) - -ACF_DefineFuelTankSize( "Tank_4x6x1", { - name = "4x6x1", - desc = "Conformal fuel tank, fits in tight spaces.", - model = "models/fueltank/fueltank_4x6x1.mdl", - dims = { S = 6553.6, V = 24918.6 } -} ) - -ACF_DefineFuelTankSize( "Tank_4x6x2", { - name = "4x6x2", - desc = "Fire juice.", - model = "models/fueltank/fueltank_4x6x2.mdl", - dims = { S = 8425.3, V = 48581.2 } -} ) - -ACF_DefineFuelTankSize( "Tank_4x6x4", { - name = "4x6x4", - desc = "Trees are gay anyway.", - model = "models/fueltank/fueltank_4x6x4.mdl", - dims = { S = 12200.6, V = 94640 } -} ) - -ACF_DefineFuelTankSize( "Tank_4x8x1", { - name = "4x8x1", - desc = "Arson material.", - model = "models/fueltank/fueltank_4x8x1.mdl", - dims = { S = 8328.2, V = 32541.9 } -} ) - -ACF_DefineFuelTankSize( "Tank_4x8x2", { - name = "4x8x2", - desc = "What's a gas station?", - model = "models/fueltank/fueltank_4x8x2.mdl", - dims = { S = 10419.5, V = 63167.1 } -} ) - -ACF_DefineFuelTankSize( "Tank_4x8x4", { - name = "4x8x4", - desc = "\'MURRICA FUCKYEAH!", - model = "models/fueltank/fueltank_4x8x4.mdl", - dims = { S = 14993.3, V = 123693.2 } -} ) - -ACF_DefineFuelTankSize( "Tank_6x6x1", { - name = "6x6x1", - desc = "Got gas?", - model = "models/fueltank/fueltank_6x6x1.mdl", - dims = { S = 9405.2, V = 37278.5 } -} ) - -ACF_DefineFuelTankSize( "Tank_6x6x2", { - name = "6x6x2", - desc = "Drive across the desert without a fuck to give.", - model = "models/fueltank/fueltank_6x6x2.mdl", - dims = { S = 11514.5, V = 73606.2 } -} ) - -ACF_DefineFuelTankSize( "Tank_6x6x4", { - name = "6x6x4", - desc = "May contain Mesozoic ghosts.", - model = "models/fueltank/fueltank_6x6x4.mdl", - dims = { S = 16028.8, V = 143269 } -} ) - -ACF_DefineFuelTankSize( "Tank_6x8x1", { - name = "6x8x1", - desc = "Conformal fuel tank, does what all its friends do.", - model = "models/fueltank/fueltank_6x8x1.mdl", - dims = { S = 12131.1, V = 48480.2 } -} ) - -ACF_DefineFuelTankSize( "Tank_6x8x2", { - name = "6x8x2", - desc = "Certified 100% dinosaur juice.", - model = "models/fueltank/fueltank_6x8x2.mdl", - dims = { S = 14403.8, V = 95065.5 } -} ) - -ACF_DefineFuelTankSize( "Tank_6x8x4", { - name = "6x8x4", - desc = "Will last you a while.", - model = "models/fueltank/fueltank_6x8x4.mdl", - dims = { S = 19592.4, V = 187296.4 } -} ) - -ACF_DefineFuelTankSize( "Tank_8x8x1", { - name = "8x8x1", - desc = "Sloshy sloshy!", - model = "models/fueltank/fueltank_8x8x1.mdl", - dims = { S = 15524.8, V = 64794.2 } -} ) - -ACF_DefineFuelTankSize( "Tank_8x8x2", { - name = "8x8x2", - desc = "What's global warming?", - model = "models/fueltank/fueltank_8x8x2.mdl", - dims = { S = 18086.4, V = 125868.9 } -} ) - -ACF_DefineFuelTankSize( "Tank_8x8x4", { - name = "8x8x4", - desc = "Tank Tank.", - model = "models/fueltank/fueltank_8x8x4.mdl", - dims = { S = 23957.6, V = 246845.3 } -} ) - -ACF_DefineFuelTankSize( "Fuel_Drum", { - name = "Fuel Drum", - desc = "Tends to explode when shot.", - model = "models/props_c17/oildrum001_explosive.mdl", - dims = { S = 5128.9, V = 26794.4 } -} ) - -ACF_DefineFuelTankSize( "Jerry_Can", { - name = "Jerry Can", - desc = "Handy portable fuel container.", - model = "models/props_junk/gascan001a.mdl", - dims = { S = 1839.7, V = 4384.1 }, -} ) - -ACF_DefineFuelTankSize( "Transport_Tank", { - name = "Transport Tank", - desc = "Disappointingly non-explosive.", - model = "models/props_wasteland/horizontalcoolingtank04.mdl", - dims = { S = 127505.5, V = 2102493.3 }, - explosive = false, - nolinks = true -} ) - -ACF_DefineFuelTankSize( "Storage_Tank", { - name = "Storage Tank", - desc = "Disappointingly non-explosive.", - model = "models/props_wasteland/coolingtank02.mdl", - dims = { S = 144736.3, V = 2609960 }, - explosive = false, - nolinks = true -} ) diff --git a/lua/acf/shared/fueltanks/misc.lua b/lua/acf/shared/fueltanks/misc.lua new file mode 100644 index 000000000..a746993d0 --- /dev/null +++ b/lua/acf/shared/fueltanks/misc.lua @@ -0,0 +1,42 @@ +ACF.RegisterFuelTankClass("FTS_M", { + Name = "Miscellaneous", + Description = "Random fuel tank models, some of them can only be used for refueling.", +}) + +do + ACF.RegisterFuelTank("Fuel_Drum","FTS_M", { + Name = "Fuel Drum", + Description = "Tends to explode when shot.", + Model = "models/props_c17/oildrum001_explosive.mdl", + SurfaceArea = 5128.9, + Volume = 26794.4, + }) + + ACF.RegisterFuelTank("Jerry_Can","FTS_M", { + Name = "Jerry Can", + Description = "Handy portable fuel container.", + Model = "models/props_junk/gascan001a.mdl", + SurfaceArea = 1839.7, + Volume = 4384.1, + }) + + ACF.RegisterFuelTank("Transport_Tank","FTS_M", { + Name = "Transport Tank", + Description = "Disappointingly non-explosive.", + Model = "models/props_wasteland/horizontalcoolingtank04.mdl", + SurfaceArea = 127505.5, + Volume = 2102493.3, + IsExplosive = false, + Unlinkable = true, + }) + + ACF.RegisterFuelTank("Storage_Tank","FTS_M", { + Name = "Storage Tank", + Description = "Disappointingly non-explosive.", + Model = "models/props_wasteland/coolingtank02.mdl", + SurfaceArea = 144736.3, + Volume = 2609960, + IsExplosive = false, + Unlinkable = true, + }) +end diff --git a/lua/acf/shared/fueltanks/size_eight.lua b/lua/acf/shared/fueltanks/size_eight.lua new file mode 100644 index 000000000..04d221665 --- /dev/null +++ b/lua/acf/shared/fueltanks/size_eight.lua @@ -0,0 +1,30 @@ +ACF.RegisterFuelTankClass("FTS_8", { + Name = "Size 8 Container", + Description = "Size 8 fuel containers, required for engines to work.", +}) + +do + ACF.RegisterFuelTank("Tank_8x8x1","FTS_8", { + Name = "8x8x1 Container", + Description = "Sloshy sloshy!", + Model = "models/fueltank/fueltank_8x8x1.mdl", + SurfaceArea = 15524.8, + Volume = 64794.2, + }) + + ACF.RegisterFuelTank("Tank_8x8x2","FTS_8", { + Name = "8x8x2 Container", + Description = "What's global warming?", + Model = "models/fueltank/fueltank_8x8x2.mdl", + SurfaceArea = 18086.4, + Volume = 125868.9, + }) + + ACF.RegisterFuelTank("Tank_8x8x4","FTS_8", { + Name = "8x8x4 Container", + Description = "Tank Tank.", + Model = "models/fueltank/fueltank_8x8x4.mdl", + SurfaceArea = 23957.6, + Volume = 246845.3, + }) +end diff --git a/lua/acf/shared/fueltanks/size_four.lua b/lua/acf/shared/fueltanks/size_four.lua new file mode 100644 index 000000000..faa9ed866 --- /dev/null +++ b/lua/acf/shared/fueltanks/size_four.lua @@ -0,0 +1,78 @@ +ACF.RegisterFuelTankClass("FTS_4", { + Name = "Size 4 Container", + Description = "Size 4 fuel containers, required for engines to work.", +}) + +do + ACF.RegisterFuelTank("Tank_4x4x1","FTS_4", { + Name = "4x4x1 Container", + Description = "Sedan.", + Model = "models/fueltank/fueltank_4x4x1.mdl", + SurfaceArea = 4619.1, + Volume = 16539.8, + }) + + ACF.RegisterFuelTank("Tank_4x4x2","FTS_4", { + Name = "4x4x2 Container", + Description = "Land boat.", + Model = "models/fueltank/fueltank_4x4x2.mdl", + SurfaceArea = 6071.4, + Volume = 32165.2, + }) + + ACF.RegisterFuelTank("Tank_4x4x4","FTS_4", { + Name = "4x4x4 Container", + Description = "Popular with arsonists.", + Model = "models/fueltank/fueltank_4x4x4.mdl", + SurfaceArea = 9145.3, + Volume = 62900.1, + }) + + ACF.RegisterFuelTank("Tank_4x6x1","FTS_4", { + Name = "4x6x1 Container", + Description = "Conformal fuel tank, fits in tight spaces.", + Model = "models/fueltank/fueltank_4x6x1.mdl", + SurfaceArea = 6553.6, + Volume = 24918.6, + }) + + ACF.RegisterFuelTank("Tank_4x6x2","FTS_4", { + Name = "4x6x2 Container", + Description = "Fire juice.", + Model = "models/fueltank/fueltank_4x6x2.mdl", + SurfaceArea = 8425.3, + Volume = 48581.2, + }) + + ACF.RegisterFuelTank("Tank_4x6x4","FTS_4", { + Name = "4x6x4 Container", + Description = "Trees are gay anyway.", + Model = "models/fueltank/fueltank_4x6x4.mdl", + SurfaceArea = 12200.6, + Volume = 94640, + }) + + ACF.RegisterFuelTank("Tank_4x8x1","FTS_4", { + Name = "4x8x1 Container", + Description = "Arson material.", + Model = "models/fueltank/fueltank_4x8x1.mdl", + SurfaceArea = 8328.2, + Volume = 32541.9, + }) + + ACF.RegisterFuelTank("Tank_4x8x2","FTS_4", { + Name = "4x8x2 Container", + Description = "What's a gas station?", + Model = "models/fueltank/fueltank_4x8x2.mdl", + SurfaceArea = 10419.5, + Volume = 63167.1, + }) + + ACF.RegisterFuelTank("Tank_4x8x4","FTS_4", { + Name = "4x8x4 Container", + Description = "\'MURRICA FUCKYEAH!", + Model = "models/fueltank/fueltank_4x8x4.mdl", + SurfaceArea = 14993.3, + Volume = 123693.2, + }) +end diff --git a/lua/acf/shared/fueltanks/size_one.lua b/lua/acf/shared/fueltanks/size_one.lua new file mode 100644 index 000000000..c4c27efb9 --- /dev/null +++ b/lua/acf/shared/fueltanks/size_one.lua @@ -0,0 +1,126 @@ +ACF.RegisterFuelTankClass("FTS_1", { + Name = "Size 1 Container", + Description = "Size 1 fuel containers, required for engines to work.", +}) + +do + ACF.RegisterFuelTank("Tank_1x1x1","FTS_1", { + Name = "1x1x1 Container", + Description = "Seriously consider walking.", + Model = "models/fueltank/fueltank_1x1x1.mdl", + SurfaceArea = 590.5, + Volume = 1019.9, + }) + + ACF.RegisterFuelTank("Tank_1x1x2","FTS_1", { + Name = "1x1x2 Container", + Description = "Will keep a kart running all day.", + Model = "models/fueltank/fueltank_1x1x2.mdl", + SurfaceArea = 974, + Volume = 1983.1, + }) + + ACF.RegisterFuelTank("Tank_1x1x4","FTS_1", { + Name = "1x1x4 Container", + Description = "Dinghy", + Model = "models/fueltank/fueltank_1x1x4.mdl", + SurfaceArea = 1777.4, + Volume = 3995.1, + }) + + ACF.RegisterFuelTank("Tank_1x2x1","FTS_1", { + Name = "1x2x1 Container", + Description = "Will keep a kart running all day.", + Model = "models/fueltank/fueltank_1x2x1.mdl", + SurfaceArea = 995, + Volume = 2062.5, + }) + + ACF.RegisterFuelTank("Tank_1x2x2","FTS_1", { + Name = "1x2x2 Container", + Description = "Dinghy", + Model = "models/fueltank/fueltank_1x2x2.mdl", + SurfaceArea = 1590.8, + Volume = 4070.9, + }) + + ACF.RegisterFuelTank("Tank_1x2x4","FTS_1", { + Name = "1x2x4 Container", + Description = "Outboard motor.", + Model = "models/fueltank/fueltank_1x2x4.mdl", + SurfaceArea = 2796.6, + Volume = 8119.2, + }) + + ACF.RegisterFuelTank("Tank_1x4x1","FTS_1", { + Name = "1x4x1 Container", + Description = "Dinghy", + Model = "models/fueltank/fueltank_1x4x1.mdl", + SurfaceArea = 1745.6, + Volume = 3962, + }) + + ACF.RegisterFuelTank("Tank_1x4x2","FTS_1", { + Name = "1x4x2 Container", + Description = "Clown car.", + Model = "models/fueltank/fueltank_1x4x2.mdl", + SurfaceArea = 2753.9, + Volume = 8018, + }) + + ACF.RegisterFuelTank("Tank_1x4x4","FTS_1", { + Name = "1x4x4 Container", + Description = "Fuel pancake.", + Model = "models/fueltank/fueltank_1x4x4.mdl", + SurfaceArea = 4761, + Volume = 16030.4, + }) + + ACF.RegisterFuelTank("Tank_1x6x1","FTS_1", { + Name = "1x6x1 Container", + Description = "Lawn tractors.", + Model = "models/fueltank/fueltank_1x6x1.mdl", + SurfaceArea = 2535.3, + Volume = 5973.1, + }) + + ACF.RegisterFuelTank("Tank_1x6x2","FTS_1", { + Name = "1x6x2 Container", + Description = "Small tractor tank.", + Model = "models/fueltank/fueltank_1x6x2.mdl", + SurfaceArea = 3954.1, + Volume = 12100.3, + }) + + ACF.RegisterFuelTank("Tank_1x6x4","FTS_1", { + Name = "1x6x4 Container", + Description = "Fuel. Will keep you going for awhile.", + Model = "models/fueltank/fueltank_1x6x4.mdl", + SurfaceArea = 6743.3, + Volume = 24109.4, + }) + + ACF.RegisterFuelTank("Tank_1x8x1","FTS_1", { + Name = "1x8x1 Container", + Description = "Clown car.", + Model = "models/fueltank/fueltank_1x8x1.mdl", + SurfaceArea = 3315.5, + Volume = 7962.4, + }) + + ACF.RegisterFuelTank("Tank_1x8x2","FTS_1", { + Name = "1x8x2 Container", + Description = "Gas stations? We don't need no stinking gas stations!", + Model = "models/fueltank/fueltank_1x8x2.mdl", + SurfaceArea = 5113.7, + Volume = 16026.2, + }) + + ACF.RegisterFuelTank("Tank_1x8x4","FTS_1", { + Name = "1x8x4 Container", + Description = "Beep beep.", + Model = "models/fueltank/fueltank_1x8x4.mdl", + SurfaceArea = 8696, + Volume = 31871, + }) +end diff --git a/lua/acf/shared/fueltanks/size_six.lua b/lua/acf/shared/fueltanks/size_six.lua new file mode 100644 index 000000000..1456fdf9e --- /dev/null +++ b/lua/acf/shared/fueltanks/size_six.lua @@ -0,0 +1,54 @@ +ACF.RegisterFuelTankClass("FTS_6", { + Name = "Size 6 Container", + Description = "Size 6 fuel containers, required for engines to work.", +}) + +do + ACF.RegisterFuelTank("Tank_6x6x1","FTS_6", { + Name = "6x6x1 Container", + Description = "Got gas?", + Model = "models/fueltank/fueltank_6x6x1.mdl", + SurfaceArea = 9405.2, + Volume = 37278.5, + }) + + ACF.RegisterFuelTank("Tank_6x6x2","FTS_6", { + Name = "6x6x2 Container", + Description = "Drive across the desert without a fuck to give.", + Model = "models/fueltank/fueltank_6x6x2.mdl", + SurfaceArea = 11514.5, + Volume = 73606.2, + }) + + ACF.RegisterFuelTank("Tank_6x6x4","FTS_6", { + Name = "6x6x4 Container", + Description = "May contain Mesozoic ghosts.", + Model = "models/fueltank/fueltank_6x6x4.mdl", + SurfaceArea = 16028.8, + Volume = 143269, + }) + + ACF.RegisterFuelTank("Tank_6x8x1","FTS_6", { + Name = "6x8x1 Container", + Description = "Conformal fuel tank, does what all its friends do.", + Model = "models/fueltank/fueltank_6x8x1.mdl", + SurfaceArea = 12131.1, + Volume = 48480.2, + }) + + ACF.RegisterFuelTank("Tank_6x8x2","FTS_6", { + Name = "6x8x2 Container", + Description = "Certified 100% dinosaur juice.", + Model = "models/fueltank/fueltank_6x8x2.mdl", + SurfaceArea = 14403.8, + Volume = 95065.5, + }) + + ACF.RegisterFuelTank("Tank_6x8x4","FTS_6", { + Name = "6x8x4 Container", + Description = "Will last you a while.", + Model = "models/fueltank/fueltank_6x8x4.mdl", + SurfaceArea = 19592.4, + Volume = 187296.4, + }) +end diff --git a/lua/acf/shared/fueltanks/size_two.lua b/lua/acf/shared/fueltanks/size_two.lua new file mode 100644 index 000000000..9214d5176 --- /dev/null +++ b/lua/acf/shared/fueltanks/size_two.lua @@ -0,0 +1,102 @@ +ACF.RegisterFuelTankClass("FTS_2", { + Name = "Size 2 Container", + Description = "Size 2 fuel containers, required for engines to work.", +}) + +do + ACF.RegisterFuelTank("Tank_2x2x1","FTS_2", { + Name = "2x2x1 Container", + Description = "Dinghy", + Model = "models/fueltank/fueltank_2x2x1.mdl", + SurfaceArea = 1592.2, + Volume = 4285.2, + }) + + ACF.RegisterFuelTank("Tank_2x2x2","FTS_2", { + Name = "2x2x2 Container", + Description = "Clown car.", + Model = "models/fueltank/fueltank_2x2x2.mdl", + SurfaceArea = 2360.4, + Volume = 8212.9, + }) + + ACF.RegisterFuelTank("Tank_2x2x4","FTS_2", { + Name = "2x2x4 Container", + Description = "Mini Cooper.", + Model = "models/fueltank/fueltank_2x2x4.mdl", + SurfaceArea = 3988.6, + Volume = 16362, + }) + + ACF.RegisterFuelTank("Tank_2x4x1","FTS_2", { + Name = "2x4x1 Container", + Description = "Good bit of go-juice.", + Model = "models/fueltank/fueltank_2x4x1.mdl", + SurfaceArea = 2808.8, + Volume = 8628, + }) + + ACF.RegisterFuelTank("Tank_2x4x2","FTS_2", { + Name = "2x4x2 Container", + Description = "Mini Cooper.", + Model = "models/fueltank/fueltank_2x4x2.mdl", + SurfaceArea = 3996.1, + Volume = 16761.4, + }) + + ACF.RegisterFuelTank("Tank_2x4x4","FTS_2", { + Name = "2x4x4 Container", + Description = "Land boat.", + Model = "models/fueltank/fueltank_2x4x4.mdl", + SurfaceArea = 6397.3, + Volume = 32854.4, + }) + + ACF.RegisterFuelTank("Tank_2x6x1","FTS_2", { + Name = "2x6x1 Container", + Description = "Conformal fuel tank, fits narrow spaces.", + Model = "models/fueltank/fueltank_2x6x1.mdl", + SurfaceArea = 3861.4, + Volume = 12389.9, + }) + + ACF.RegisterFuelTank("Tank_2x6x2","FTS_2", { + Name = "2x6x2 Container", + Description = "Compact car.", + Model = "models/fueltank/fueltank_2x6x2.mdl", + SurfaceArea = 5388, + Volume = 24127.7, + }) + + ACF.RegisterFuelTank("Tank_2x6x4","FTS_2", { + Name = "2x6x4 Container", + Description = "Sedan.", + Model = "models/fueltank/fueltank_2x6x4.mdl", + SurfaceArea = 8485.6, + Volume = 47537.2, + }) + + ACF.RegisterFuelTank("Tank_2x8x1","FTS_2", { + Name = "2x8x1 Container", + Description = "Conformal fuel tank, fits into tight spaces", + Model = "models/fueltank/fueltank_2x8x1.mdl", + SurfaceArea = 5094.5, + Volume = 16831.8, + }) + + ACF.RegisterFuelTank("Tank_2x8x2","FTS_2", { + Name = "2x8x2 Container", + Description = "Truck.", + Model = "models/fueltank/fueltank_2x8x2.mdl", + SurfaceArea = 6980, + Volume = 32275.9, + }) + + ACF.RegisterFuelTank("Tank_2x8x4","FTS_2", { + Name = "2x8x4 Container", + Description = "With great capacity, comes great responsibili--VROOOOM", + Model = "models/fueltank/fueltank_2x8x4.mdl", + SurfaceArea = 10898.2, + Volume = 63976, + }) +end diff --git a/lua/acf/shared/fun_stuff/piledriver.lua b/lua/acf/shared/fun_stuff/piledriver.lua new file mode 100644 index 000000000..eddba0bb5 --- /dev/null +++ b/lua/acf/shared/fun_stuff/piledriver.lua @@ -0,0 +1,41 @@ +ACF.RegisterPiledriverClass("PD", { + Name = "Piledriver", + Description = "Formerly a piece of construction equipment, it was modified to be used in close-quarters combat. Doesn't actually drive piles.", + Model = "models/piledriver/piledriver_100mm.mdl", + IsScalable = true, + Mass = 1200, -- Relative to the Base caliber + MagSize = 15, + Cyclic = 60, + ChargeRate = 0.5, + Caliber = { + Base = 100, + Min = 50, + Max = 300, + }, + Round = { + MaxLength = 114.3, -- Relative to the Base caliber, in cm + PropMass = 0, + }, + Preview = { + Position = Vector(0, 0, -5), + Offset = Vector(45, 45, 20), + Height = 120, + }, +}) + +ACF.RegisterPiledriver("75mmPD", "PD", { + Caliber = 75 +}) + +ACF.RegisterPiledriver("100mmPD", "PD", { + Caliber = 100 +}) + +ACF.RegisterPiledriver("150mmPD", "PD", { + Caliber = 150 +}) + +ACF.SetCustomAttachments("models/piledriver/piledriver_100mm.mdl", { + { Name = "muzzle", Pos = Vector(20), Ang = Angle() }, + { Name = "tip", Pos = Vector(65), Ang = Angle() }, +}) diff --git a/lua/acf/shared/gearboxes/3-auto.lua b/lua/acf/shared/gearboxes/3-auto.lua new file mode 100644 index 000000000..42b56a259 --- /dev/null +++ b/lua/acf/shared/gearboxes/3-auto.lua @@ -0,0 +1,277 @@ +-- Weight +local wmul = 1.5 +local Gear3SW = 60 * wmul +local Gear3MW = 120 * wmul +local Gear3LW = 240 * wmul + +-- Torque Rating +local Gear3ST = 675 +local Gear3MT = 2125 +local Gear3LT = 10000 + +-- Straight through bonuses +local StWB = 0.75 --straight weight bonus mulitplier +local StTB = 1.25 --straight torque bonus multiplier + +-- Shift Time +local ShiftS = 0.25 +local ShiftM = 0.35 +local ShiftL = 0.5 + +local function InitGearbox(Gearbox) + local Gears = Gearbox.Gears + + Gearbox.Automatic = true + Gearbox.ShiftScale = 1 + Gearbox.Hold = false + Gearbox.Drive = 0 + Gearbox.GearCount = Gearbox.MaxGear + 1 + + Gears[Gearbox.GearCount] = Gearbox.Reverse + + Gearbox:ChangeDrive(1) +end + +ACF.RegisterGearboxClass("3-Auto", { + Name = "3-Speed Automatic", + CreateMenu = ACF.AutomaticGearboxMenu, + Gears = { + Min = 0, + Max = 3, + }, + OnSpawn = InitGearbox, + OnUpdate = InitGearbox, + VerifyData = function(Data, Class) + do -- Shift point table verification + local Points = Data.ShiftPoints + local Mult = Data.ShiftUnit or 1 + local Max = Class.Gears.Max + + if not istable(Points) then + local Encoded = Data.Gear9 and tostring(Data.Gear9) + + Points = { [0] = -1 } + + if Encoded then + local Count = 0 + + for Point in string.gmatch(Encoded, "[^,]+") do + Count = Count + 1 + + if Count > Max then break end + + Points[Count] = ACF.CheckNumber(Point, Count * 100) + end + end + + Data.ShiftPoints = Points + else + Points[0] = -1 + end + + for I = 1, Max do + local Point = ACF.CheckNumber(Points[I]) + + if not Point then + Point = ACF.CheckNumber(Data["Shift" .. I], I * 100) * Mult + + Data["Shift" .. I] = nil + end + + Points[I] = math.Clamp(Point, 0, 9999) + end + end + + do -- Reverse gear verification + local Reverse = ACF.CheckNumber(Data.Reverse) + + if not Reverse then + Reverse = ACF.CheckNumber(Data.Gear8, -1) + + Data.Gear8 = nil + end + + Data.Reverse = math.Clamp(Reverse, -1, 1) + end + end, + SetupInputs = function(List) + local Count = #List + + List[Count + 1] = "Hold Gear" + List[Count + 2] = "Shift Speed Scale" + end, + OnLast = function(Gearbox) + Gearbox.Automatic = nil + Gearbox.ShiftScale = nil + Gearbox.Drive = nil + Gearbox.Hold = nil + end, + GetGearsText = function(Gearbox) + local GearText = "Gear %s: %s, Upshift @ %s kph / %s mph\n" + local Text = "%sReverse gear: %s\n" + local Points = Gearbox.ShiftPoints + local Gears = Gearbox.Gears + local GearsText = "" + + for I = 1, Gearbox.MaxGear do + local Ratio = math.Round(Gears[I], 2) + local KPH = math.Round(Points[I] / 10.936, 1) + local MPH = math.Round(Points[I] / 17.6, 1) + + GearsText = GearsText .. GearText:format(I, Ratio, KPH, MPH) + end + + return Text:format(GearsText, math.Round(Gearbox.Reverse, 2)) + end, +}) + +do -- Inline Gearboxes + ACF.RegisterGearbox("3Gear-A-L-S", "3-Auto", { + Name = "3-Speed Auto, Inline, Small", + Description = "A small, and light 3 speed automatic inline gearbox, with a somewhat limited max torque rating", + Model = "models/engines/linear_s.mdl", + Mass = Gear3SW, + Switch = ShiftS, + MaxTorque = Gear3ST, + }) + + ACF.RegisterGearbox("3Gear-A-L-M", "3-Auto", { + Name = "3-Speed Auto, Inline, Medium", + Description = "A medium sized, 3 speed automatic inline gearbox", + Model = "models/engines/linear_m.mdl", + Mass = Gear3MW, + Switch = ShiftM, + MaxTorque = Gear3MT, + }) + + ACF.RegisterGearbox("3Gear-A-L-L", "3-Auto", { + Name = "3-Speed Auto, Inline, Large", + Description = "A large, heavy and sturdy 3 speed inline gearbox", + Model = "models/engines/linear_l.mdl", + Mass = Gear3LW, + Switch = ShiftL, + MaxTorque = Gear3LT, + }) +end + +do -- Inline Dual Clutch Gearboxes + ACF.RegisterGearbox("3Gear-A-LD-S", "3-Auto", { + Name = "3-Speed Auto, Inline, Small, Dual Clutch", + Description = "A small, and light 3 speed automatic inline gearbox, with a somewhat limited max torque rating", + Model = "models/engines/linear_s.mdl", + Mass = Gear3SW, + Switch = ShiftS, + MaxTorque = Gear3ST, + DualClutch = true, + }) + + ACF.RegisterGearbox("3Gear-A-LD-M", "3-Auto", { + Name = "3-Speed Auto, Inline, Medium, Dual Clutch", + Description = "A medium sized, 3 speed automatic inline gearbox", + Model = "models/engines/linear_m.mdl", + Mass = Gear3MW, + Switch = ShiftM, + MaxTorque = Gear3MT, + DualClutch = true, + }) + + ACF.RegisterGearbox("3Gear-A-LD-L", "3-Auto", { + Name = "3-Speed Auto, Inline, Large, Dual Clutch", + Description = "A large, heavy and sturdy 3 speed automatic inline gearbox", + Model = "models/engines/linear_l.mdl", + Mass = Gear3LW, + Switch = ShiftL, + MaxTorque = Gear3LT, + DualClutch = true, + }) +end + +do -- Transaxial Gearboxes + ACF.RegisterGearbox("3Gear-A-T-S", "3-Auto", { + Name = "3-Speed Auto, Transaxial, Small", + Description = "A small, and light 3 speed automatic gearbox, with a somewhat limited max torque rating", + Model = "models/engines/transaxial_s.mdl", + Mass = Gear3SW, + Switch = ShiftS, + MaxTorque = Gear3ST, + }) + + ACF.RegisterGearbox("3Gear-A-T-M", "3-Auto", { + Name = "3-Speed Auto, Transaxial, Medium", + Description = "A medium sized, 3 speed automatic gearbox", + Model = "models/engines/transaxial_m.mdl", + Mass = Gear3MW, + Switch = ShiftM, + MaxTorque = Gear3MT, + }) + + ACF.RegisterGearbox("3Gear-A-T-L", "3-Auto", { + Name = "3-Speed Auto, Transaxial, Large", + Description = "A large, heavy and sturdy 3 speed automatic gearbox", + Model = "models/engines/transaxial_l.mdl", + Mass = Gear3LW, + Switch = ShiftL, + MaxTorque = Gear3LT, + }) +end + +do -- Transaxial Dual Clutch Gearboxes + ACF.RegisterGearbox("3Gear-A-TD-S", "3-Auto", { + Name = "3-Speed Auto, Transaxial, Small, Dual Clutch", + Description = "A small, and light 3 speed automatic gearbox, with a somewhat limited max torque rating", + Model = "models/engines/transaxial_s.mdl", + Mass = Gear3SW, + Switch = ShiftS, + MaxTorque = Gear3ST, + DualClutch = true, + }) + + ACF.RegisterGearbox("3Gear-A-TD-M", "3-Auto", { + Name = "3-Speed Auto, Transaxial, Medium, Dual Clutch", + Description = "A medium sized, 3 speed automatic gearbox", + Model = "models/engines/transaxial_m.mdl", + Mass = Gear3MW, + Switch = ShiftM, + MaxTorque = Gear3MT, + DualClutch = true, + }) + + ACF.RegisterGearbox("3Gear-A-TD-L", "3-Auto", { + Name = "3-Speed Auto, Transaxial, Large, Dual Clutch", + Description = "A large, heavy and sturdy 3 speed automatic gearbox", + Model = "models/engines/transaxial_l.mdl", + Mass = Gear3LW, + Switch = ShiftL, + MaxTorque = Gear3LT, + DualClutch = true, + }) +end + +do -- Straight-through Gearboxes + ACF.RegisterGearbox("3Gear-A-ST-S", "3-Auto", { + Name = "3-Speed Auto, Straight, Small", + Description = "A small straight-through automatic gearbox", + Model = "models/engines/t5small.mdl", + Mass = math.floor(Gear3SW * StWB), + Switch = ShiftS, + MaxTorque = math.floor(Gear3ST * StTB), + }) + + ACF.RegisterGearbox("3Gear-A-ST-M", "3-Auto", { + Name = "3-Speed Auto, Straight, Medium", + Description = "A medium sized, 3 speed automatic straight-through gearbox.", + Model = "models/engines/t5med.mdl", + Mass = math.floor(Gear3MW * StWB), + Switch = ShiftM, + MaxTorque = math.floor(Gear3MT * StTB), + }) + + ACF.RegisterGearbox("3Gear-A-ST-L", "3-Auto", { + Name = "3-Speed Auto, Straight, Large", + Description = "A large sized, 3 speed automatic straight-through gearbox.", + Model = "models/engines/t5large.mdl", + Mass = math.floor(Gear3LW * StWB), + Switch = ShiftL, + MaxTorque = math.floor(Gear3LT * StTB), + }) +end diff --git a/lua/acf/shared/gearboxes/4-speed.lua b/lua/acf/shared/gearboxes/4-speed.lua index dbf9d575f..3e762662a 100644 --- a/lua/acf/shared/gearboxes/4-speed.lua +++ b/lua/acf/shared/gearboxes/4-speed.lua @@ -13,303 +13,162 @@ local Gear4MT = 1700 local Gear4LT = 10000 local StTB = 1.25 --straight torque bonus multiplier --- Inline - -ACF_DefineGearbox( "4Gear-L-S", { - name = "4-Speed, Inline, Small", - desc = "A small, and light 4 speed inline gearbox, with a somewhat limited max torque rating\n\nThe Final Drive slider is a multiplier applied to all the other gear ratios", - model = "models/engines/linear_s.mdl", - category = "4-Speed", - weight = Gear4SW, - switch = 0.15, - maxtq = Gear4ST, - gears = 4, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "4Gear-L-M", { - name = "4-Speed, Inline, Medium", - desc = "A medium sized, 4 speed inline gearbox", - model = "models/engines/linear_m.mdl", - category = "4-Speed", - weight = Gear4MW, - switch = 0.2, - maxtq = Gear4MT, - gears = 4, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "4Gear-L-L", { - name = "4-Speed, Inline, Large", - desc = "A large, heavy and sturdy 4 speed inline gearbox", - model = "models/engines/linear_l.mdl", - category = "4-Speed", - weight = Gear4LW, - switch = 0.3, - maxtq = Gear4LT, - gears = 4, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = -0.1, - [ -1 ] = 1 - } -} ) - --- Inline Dual Clutch - -ACF_DefineGearbox( "4Gear-LD-S", { - name = "4-Speed, Inline, Small, Dual Clutch", - desc = "A small, and light 4 speed inline gearbox, with a somewhat limited max torque rating. The dual clutch allows you to apply power and brake each side independently\n\nThe Final Drive slider is a multiplier applied to all the other gear ratios", - model = "models/engines/linear_s.mdl", - category = "4-Speed", - weight = Gear4SW, - switch = 0.15, - maxtq = Gear4ST, - gears = 4, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "4Gear-LD-M", { - name = "4-Speed, Inline, Medium, Dual Clutch", - desc = "A medium sized, 4 speed inline gearbox. The dual clutch allows you to apply power and brake each side independently", - model = "models/engines/linear_m.mdl", - category = "4-Speed", - weight = Gear4MW, - switch = 0.2, - maxtq = Gear4MT, - gears = 4, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "4Gear-LD-L", { - name = "4-Speed, Inline, Large, Dual Clutch", - desc = "A large, heavy and sturdy 4 speed inline gearbox. The dual clutch allows you to apply power and brake each side independently", - model = "models/engines/linear_l.mdl", - category = "4-Speed", - weight = Gear4LW, - switch = 0.3, - maxtq = Gear4LT, - gears = 4, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = -0.1, - [ -1 ] = 1 - } -} ) - --- Transaxial - -ACF_DefineGearbox( "4Gear-T-S", { - name = "4-Speed, Transaxial, Small", - desc = "A small, and light 4 speed gearbox, with a somewhat limited max torque rating\n\nThe Final Drive slider is a multiplier applied to all the other gear ratios", - model = "models/engines/transaxial_s.mdl", - category = "4-Speed", - weight = Gear4SW, - switch = 0.15, - maxtq = Gear4ST, - gears = 4, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "4Gear-T-M", { - name = "4-Speed, Transaxial, Medium", - desc = "A medium sized, 4 speed gearbox", - model = "models/engines/transaxial_m.mdl", - category = "4-Speed", - weight = Gear4MW, - switch = 0.2, - maxtq = Gear4MT, - gears = 4, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "4Gear-T-L", { - name = "4-Speed, Transaxial, Large", - desc = "A large, heavy and sturdy 4 speed gearbox", - model = "models/engines/transaxial_l.mdl", - category = "4-Speed", - weight = Gear4LW, - switch = 0.3, - maxtq = Gear4LT, - gears = 4, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = -0.1, - [ -1 ] = 1 - } -} ) - --- Transaxial Dual Clutch - -ACF_DefineGearbox( "4Gear-TD-S", { - name = "4-Speed, Transaxial, Small, Dual Clutch", - desc = "A small, and light 4 speed gearbox, with a somewhat limited max torque rating. The dual clutch allows you to apply power and brake each side independently\n\nThe Final Drive slider is a multiplier applied to all the other gear ratios", - model = "models/engines/transaxial_s.mdl", - category = "4-Speed", - weight = Gear4SW, - switch = 0.15, - maxtq = Gear4ST, - gears = 4, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "4Gear-TD-M", { - name = "4-Speed, Transaxial, Medium, Dual Clutch", - desc = "A medium sized, 4 speed gearbox. The dual clutch allows you to apply power and brake each side independently", - model = "models/engines/transaxial_m.mdl", - category = "4-Speed", - weight = Gear4MW, - switch = 0.2, - maxtq = Gear4MT, - gears = 4, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "4Gear-TD-L", { - name = "4-Speed, Transaxial, Large, Dual Clutch", - desc = "A large, heavy and sturdy 4 speed gearbox. The dual clutch allows you to apply power and brake each side independently", - model = "models/engines/transaxial_l.mdl", - category = "4-Speed", - weight = Gear4LW, - switch = 0.3, - maxtq = Gear4LT, - gears = 4, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = -0.1, - [ -1 ] = 1 - } -} ) - --- Straight-through gearboxes - -ACF_DefineGearbox( "4Gear-ST-S", { - name = "4-Speed, Straight, Small", - desc = "A small straight-through gearbox", - model = "models/engines/t5small.mdl", - category = "4-Speed", - weight = math.floor(Gear4SW * StWB), - switch = 0.15, - maxtq = math.floor(Gear4ST * StTB), - gears = 4, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = -0.1, - [ -1 ] = 1 - } -} ) - -ACF_DefineGearbox( "4Gear-ST-M", { - name = "4-Speed, Straight, Medium", - desc = "A medium sized, 4 speed straight-through gearbox.", - model = "models/engines/t5med.mdl", - category = "4-Speed", - weight = math.floor(Gear4MW * StWB), - switch = 0.2, - maxtq = math.floor(Gear4MT * StTB), - gears = 4, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "4Gear-ST-L", { - name = "4-Speed, Straight, Large", - desc = "A large sized, 4 speed straight-through gearbox.", - model = "models/engines/t5large.mdl", - category = "4-Speed", - weight = math.floor(Gear4LW * StWB), - switch = 0.3, - maxtq = math.floor(Gear4LT * StTB), - gears = 4, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = -0.1, - [ -1 ] = 0.5 +ACF.RegisterGearboxClass("4-Speed", { + Name = "4-Speed Gearbox", + CreateMenu = ACF.ManualGearboxMenu, + Gears = { + Min = 0, + Max = 4, } -} ) +}) + +do -- Inline Gearboxes + ACF.RegisterGearbox("4Gear-L-S", "4-Speed", { + Name = "4-Speed, Inline, Small", + Description = "A small, and light 4 speed inline gearbox, with a somewhat limited max torque rating.", + Model = "models/engines/linear_s.mdl", + Mass = Gear4SW, + Switch = 0.15, + MaxTorque = Gear4ST, + }) + + ACF.RegisterGearbox("4Gear-L-M", "4-Speed", { + Name = "4-Speed, Inline, Medium", + Description = "A medium sized, 4 speed inline gearbox.", + Model = "models/engines/linear_m.mdl", + Mass = Gear4MW, + Switch = 0.2, + MaxTorque = Gear4MT, + }) + + ACF.RegisterGearbox("4Gear-L-L", "4-Speed", { + Name = "4-Speed, Inline, Large", + Description = "A large, heavy and sturdy 4 speed inline gearbox.", + Model = "models/engines/linear_l.mdl", + Mass = Gear4LW, + Switch = 0.3, + MaxTorque = Gear4LT, + }) +end + +do -- Inline Dual Clutch Gearboxes + ACF.RegisterGearbox("4Gear-LD-S", "4-Speed", { + Name = "4-Speed, Inline, Small, Dual Clutch", + Description = "A small, and light 4 speed inline gearbox, with a somewhat limited max torque rating.", + Model = "models/engines/linear_s.mdl", + Mass = Gear4SW, + Switch = 0.15, + MaxTorque = Gear4ST, + DualClutch = true, + }) + + ACF.RegisterGearbox("4Gear-LD-M", "4-Speed", { + Name = "4-Speed, Inline, Medium, Dual Clutch", + Description = "A medium sized, 4 speed inline gearbox.", + Model = "models/engines/linear_m.mdl", + Mass = Gear4MW, + Switch = 0.2, + MaxTorque = Gear4MT, + DualClutch = true, + }) + + ACF.RegisterGearbox("4Gear-LD-L", "4-Speed", { + Name = "4-Speed, Inline, Large, Dual Clutch", + Description = "A large, heavy and sturdy 4 speed inline gearbox.", + Model = "models/engines/linear_l.mdl", + Mass = Gear4LW, + Switch = 0.3, + MaxTorque = Gear4LT, + DualClutch = true, + }) +end + +do -- Transaxial Gearboxes + ACF.RegisterGearbox("4Gear-T-S", "4-Speed", { + Name = "4-Speed, Transaxial, Small", + Description = "A small, and light 4 speed gearbox, with a somewhat limited max torque rating.", + Model = "models/engines/transaxial_s.mdl", + Mass = Gear4SW, + Switch = 0.15, + MaxTorque = Gear4ST, + }) + + ACF.RegisterGearbox("4Gear-T-M", "4-Speed", { + Name = "4-Speed, Transaxial, Medium", + Description = "A medium sized, 4 speed gearbox.", + Model = "models/engines/transaxial_m.mdl", + Mass = Gear4MW, + Switch = 0.2, + MaxTorque = Gear4MT, + }) + + ACF.RegisterGearbox("4Gear-T-L", "4-Speed", { + Name = "4-Speed, Transaxial, Large", + Description = "A large, heavy and sturdy 4 speed gearbox.", + Model = "models/engines/transaxial_l.mdl", + Mass = Gear4LW, + Switch = 0.3, + MaxTorque = Gear4LT, + }) +end + +do -- Transaxial Dual Clutch Gearboxes + ACF.RegisterGearbox("4Gear-TD-S", "4-Speed", { + Name = "4-Speed, Transaxial, Small, Dual Clutch", + Description = "A small, and light 4 speed gearbox, with a somewhat limited max torque rating.", + Model = "models/engines/transaxial_s.mdl", + Mass = Gear4SW, + Switch = 0.15, + MaxTorque = Gear4ST, + DualClutch = true, + }) + + ACF.RegisterGearbox("4Gear-TD-M", "4-Speed", { + Name = "4-Speed, Transaxial, Medium, Dual Clutch", + Description = "A medium sized, 4 speed gearbox.", + Model = "models/engines/transaxial_m.mdl", + Mass = Gear4MW, + Switch = 0.2, + MaxTorque = Gear4MT, + DualClutch = true, + }) + + ACF.RegisterGearbox("4Gear-TD-L", "4-Speed", { + Name = "4-Speed, Transaxial, Large, Dual Clutch", + Description = "A large, heavy and sturdy 4 speed gearbox.", + Model = "models/engines/transaxial_l.mdl", + Mass = Gear4LW, + Switch = 0.3, + MaxTorque = Gear4LT, + DualClutch = true, + }) +end + +do -- Straight-through Gearboxes + ACF.RegisterGearbox("4Gear-ST-S", "4-Speed", { + Name = "4-Speed, Straight, Small", + Description = "A small straight-through gearbox.", + Model = "models/engines/t5small.mdl", + Mass = math.floor(Gear4SW * StWB), + Switch = 0.15, + MaxTorque = math.floor(Gear4ST * StTB), + }) + + ACF.RegisterGearbox("4Gear-ST-M", "4-Speed", { + Name = "4-Speed, Straight, Medium", + Description = "A medium sized, 4 speed straight-through gearbox.", + Model = "models/engines/t5med.mdl", + Mass = math.floor(Gear4MW * StWB), + Switch = 0.2, + MaxTorque = math.floor(Gear4MT * StTB), + }) + + ACF.RegisterGearbox("4Gear-ST-L", "4-Speed", { + Name = "4-Speed, Straight, Large", + Description = "A large sized, 4 speed straight-through gearbox.", + Model = "models/engines/t5large.mdl", + Mass = math.floor(Gear4LW * StWB), + Switch = 0.3, + MaxTorque = math.floor(Gear4LT * StTB), + }) +end diff --git a/lua/acf/shared/gearboxes/5-auto.lua b/lua/acf/shared/gearboxes/5-auto.lua new file mode 100644 index 000000000..b4128a20e --- /dev/null +++ b/lua/acf/shared/gearboxes/5-auto.lua @@ -0,0 +1,276 @@ +-- Weight +local wmul = 1.5 +local Gear5SW = 80 * wmul +local Gear5MW = 160 * wmul +local Gear5LW = 320 * wmul + +-- Torque Rating +local Gear5ST = 550 +local Gear5MT = 1700 +local Gear5LT = 10000 + +-- Straight through bonuses +local StWB = 0.75 --straight weight bonus mulitplier +local StTB = 1.25 --straight torque bonus multiplier + +-- Shift Time +local ShiftS = 0.25 +local ShiftM = 0.35 +local ShiftL = 0.5 + +local function InitGearbox(Gearbox) + local Gears = Gearbox.Gears + + Gearbox.Automatic = true + Gearbox.ShiftScale = 1 + Gearbox.Hold = false + Gearbox.GearCount = Gearbox.MaxGear + 1 + + Gears[Gearbox.GearCount] = Gearbox.Reverse + + Gearbox:ChangeDrive(1) +end + +ACF.RegisterGearboxClass("5-Auto", { + Name = "5-Speed Automatic", + CreateMenu = ACF.AutomaticGearboxMenu, + Gears = { + Min = 0, + Max = 5, + }, + OnSpawn = InitGearbox, + OnUpdate = InitGearbox, + VerifyData = function(Data, Class) + do -- Shift point table verification + local Points = Data.ShiftPoints + local Mult = Data.ShiftUnit or 1 + local Max = Class.Gears.Max + + if not istable(Points) then + local Encoded = Data.Gear9 and tostring(Data.Gear9) + + Points = { [0] = -1 } + + if Encoded then + local Count = 0 + + for Point in string.gmatch(Encoded, "[^,]+") do + Count = Count + 1 + + if Count > Max then break end + + Points[Count] = ACF.CheckNumber(Point, Count * 100) + end + end + + Data.ShiftPoints = Points + else + Points[0] = -1 + end + + for I = 1, Max do + local Point = ACF.CheckNumber(Points[I]) + + if not Point then + Point = ACF.CheckNumber(Data["Shift" .. I], I * 100) * Mult + + Data["Shift" .. I] = nil + end + + Points[I] = math.Clamp(Point, 0, 9999) + end + end + + do -- Reverse gear verification + local Reverse = ACF.CheckNumber(Data.Reverse) + + if not Reverse then + Reverse = ACF.CheckNumber(Data.Gear8, -1) + + Data.Gear8 = nil + end + + Data.Reverse = math.Clamp(Reverse, -1, 1) + end + end, + SetupInputs = function(List) + local Count = #List + + List[Count + 1] = "Hold Gear" + List[Count + 2] = "Shift Speed Scale" + end, + OnLast = function(Gearbox) + Gearbox.Automatic = nil + Gearbox.ShiftScale = nil + Gearbox.Drive = nil + Gearbox.Hold = nil + end, + GetGearsText = function(Gearbox) + local GearText = "Gear %s: %s, Upshift @ %s kph / %s mph\n" + local Text = "%sReverse gear: %s\n" + local Points = Gearbox.ShiftPoints + local Gears = Gearbox.Gears + local GearsText = "" + + for I = 1, Gearbox.MaxGear do + local Ratio = math.Round(Gears[I], 2) + local KPH = math.Round(Points[I] / 10.936, 1) + local MPH = math.Round(Points[I] / 17.6, 1) + + GearsText = GearsText .. GearText:format(I, Ratio, KPH, MPH) + end + + return Text:format(GearsText, math.Round(Gearbox.Reverse, 2)) + end, +}) + +do -- Inline Gearboxes + ACF.RegisterGearbox("5Gear-A-L-S", "5-Auto", { + Name = "5-Speed Auto, Inline, Small", + Description = "A small, and light 5 speed automatic inline gearbox, with a somewhat limited max torque rating", + Model = "models/engines/linear_s.mdl", + Mass = Gear5SW, + Switch = ShiftS, + MaxTorque = Gear5ST, + }) + + ACF.RegisterGearbox("5Gear-A-L-M", "5-Auto", { + Name = "5-Speed Auto, Inline, Medium", + Description = "A medium sized, 5 speed automatic inline gearbox", + Model = "models/engines/linear_m.mdl", + Mass = Gear5MW, + Switch = ShiftM, + MaxTorque = Gear5MT, + }) + + ACF.RegisterGearbox("5Gear-A-L-L", "5-Auto", { + Name = "5-Speed Auto, Inline, Large", + Description = "A large, heavy and sturdy 5 speed inline gearbox", + Model = "models/engines/linear_l.mdl", + Mass = Gear5LW, + Switch = ShiftL, + MaxTorque = Gear5LT, + }) +end + +do -- Inline Dual Clutch Gearboxes + ACF.RegisterGearbox("5Gear-A-LD-S", "5-Auto", { + Name = "5-Speed Auto, Inline, Small, Dual Clutch", + Description = "A small, and light 5 speed automatic inline gearbox, with a somewhat limited max torque rating", + Model = "models/engines/linear_s.mdl", + Mass = Gear5SW, + Switch = ShiftS, + MaxTorque = Gear5ST, + DualClutch = true, + }) + + ACF.RegisterGearbox("5Gear-A-LD-M", "5-Auto", { + Name = "5-Speed Auto, Inline, Medium, Dual Clutch", + Description = "A medium sized, 5 speed automatic inline gearbox", + Model = "models/engines/linear_m.mdl", + Mass = Gear5MW, + Switch = ShiftM, + MaxTorque = Gear5MT, + DualClutch = true, + }) + + ACF.RegisterGearbox("5Gear-A-LD-L", "5-Auto", { + Name = "5-Speed Auto, Inline, Large, Dual Clutch", + Description = "A large, heavy and sturdy 5 speed automatic inline gearbox", + Model = "models/engines/linear_l.mdl", + Mass = Gear5LW, + Switch = ShiftL, + MaxTorque = Gear5LT, + DualClutch = true, + }) +end + +do -- Transaxial Gearboxes + ACF.RegisterGearbox("5Gear-A-T-S", "5-Auto", { + Name = "5-Speed Auto, Transaxial, Small", + Description = "A small, and light 5 speed automatic gearbox, with a somewhat limited max torque rating", + Model = "models/engines/transaxial_s.mdl", + Mass = Gear5SW, + Switch = ShiftS, + MaxTorque = Gear5ST, + }) + + ACF.RegisterGearbox("5Gear-A-T-M", "5-Auto", { + Name = "5-Speed Auto, Transaxial, Medium", + Description = "A medium sized, 5 speed automatic gearbox", + Model = "models/engines/transaxial_m.mdl", + Mass = Gear5MW, + Switch = ShiftM, + MaxTorque = Gear5MT, + }) + + ACF.RegisterGearbox("5Gear-A-T-L", "5-Auto", { + Name = "5-Speed Auto, Transaxial, Large", + Description = "A large, heavy and sturdy 5 speed automatic gearbox", + Model = "models/engines/transaxial_l.mdl", + Mass = Gear5LW, + Switch = ShiftL, + MaxTorque = Gear5LT, + }) +end + +do -- Transaxial Dual Clutch Gearboxes + ACF.RegisterGearbox("5Gear-A-TD-S", "5-Auto", { + Name = "5-Speed Auto, Transaxial, Small, Dual Clutch", + Description = "A small, and light 5 speed automatic gearbox, with a somewhat limited max torque rating", + Model = "models/engines/transaxial_s.mdl", + Mass = Gear5SW, + Switch = ShiftS, + MaxTorque = Gear5ST, + DualClutch = true, + }) + + ACF.RegisterGearbox("5Gear-A-TD-M", "5-Auto", { + Name = "5-Speed Auto, Transaxial, Medium, Dual Clutch", + Description = "A medium sized, 5 speed automatic gearbox", + Model = "models/engines/transaxial_m.mdl", + Mass = Gear5MW, + Switch = ShiftM, + MaxTorque = Gear5MT, + DualClutch = true, + }) + + ACF.RegisterGearbox("5Gear-A-TD-L", "5-Auto", { + Name = "5-Speed Auto, Transaxial, Large, Dual Clutch", + Description = "A large, heavy and sturdy 5 speed automatic gearbox", + Model = "models/engines/transaxial_l.mdl", + Mass = Gear5LW, + Switch = ShiftL, + MaxTorque = Gear5LT, + DualClutch = true, + }) +end + +do -- Straight-through Gearboxes + ACF.RegisterGearbox("5Gear-A-ST-S", "5-Auto", { + Name = "5-Speed Auto, Straight, Small", + Description = "A small straight-through automatic gearbox", + Model = "models/engines/t5small.mdl", + Mass = math.floor(Gear5SW * StWB), + Switch = ShiftS, + MaxTorque = math.floor(Gear5ST * StTB), + }) + + ACF.RegisterGearbox("5Gear-A-ST-M", "5-Auto", { + Name = "5-Speed Auto, Straight, Medium", + Description = "A medium sized, 5 speed automatic straight-through gearbox.", + Model = "models/engines/t5med.mdl", + Mass = math.floor(Gear5MW * StWB), + Switch = ShiftM, + MaxTorque = math.floor(Gear5MT * StTB), + }) + + ACF.RegisterGearbox("5Gear-A-ST-L", "5-Auto", { + Name = "5-Speed Auto, Straight, Large", + Description = "A large sized, 5 speed automatic straight-through gearbox.", + Model = "models/engines/t5large.mdl", + Mass = math.floor(Gear5LW * StWB), + Switch = ShiftL, + MaxTorque = math.floor(Gear5LT * StTB), + }) +end diff --git a/lua/acf/shared/gearboxes/6-speed.lua b/lua/acf/shared/gearboxes/6-speed.lua index ab845af61..903bc6717 100644 --- a/lua/acf/shared/gearboxes/6-speed.lua +++ b/lua/acf/shared/gearboxes/6-speed.lua @@ -13,333 +13,162 @@ local Gear6MT = 1360 local Gear6LT = 10000 local StTB = 1.25 --straight torque bonus multiplier --- Inline - -ACF_DefineGearbox( "6Gear-L-S", { - name = "6-Speed, Inline, Small", - desc = "A small and light 6 speed inline gearbox, with a limited max torque rating.", - model = "models/engines/linear_s.mdl", - category = "6-Speed", - weight = Gear6SW, - switch = 0.15, - maxtq = Gear6ST, - gears = 6, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "6Gear-L-M", { - name = "6-Speed, Inline, Medium", - desc = "A medium duty 6 speed inline gearbox with a limited torque rating.", - model = "models/engines/linear_m.mdl", - category = "6-Speed", - weight = Gear6MW, - switch = 0.2, - maxtq = Gear6MT, - gears = 6, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "6Gear-L-L", { - name = "6-Speed, Inline, Large", - desc = "Heavy duty 6 speed inline gearbox, however not as resilient as a 4 speed.", - model = "models/engines/linear_l.mdl", - category = "6-Speed", - weight = Gear6LW, - switch = 0.3, - maxtq = Gear6LT, - gears = 6, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = -0.1, - [ -1 ] = 1 - } -} ) - --- Inline Dual Clutch - -ACF_DefineGearbox( "6Gear-LD-S", { - name = "6-Speed, Inline, Small, Dual Clutch", - desc = "A small and light 6 speed inline gearbox, with a limited max torque rating. The dual clutch allows you to apply power and brake each side independently\n\nThe Final Drive slider is a multiplier applied to all the other gear ratios", - model = "models/engines/linear_s.mdl", - category = "6-Speed", - weight = Gear6SW, - switch = 0.15, - maxtq = Gear6ST, - gears = 6, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "6Gear-LD-M", { - name = "6-Speed, Inline, Medium, Dual Clutch", - desc = "A a medium duty 6 speed inline gearbox. The added gears reduce torque capacity substantially. The dual clutch allows you to apply power and brake each side independently\n\nThe Final Drive slider is a multiplier applied to all the other gear ratios", - model = "models/engines/linear_m.mdl", - category = "6-Speed", - weight = Gear6MW, - switch = 0.2, - maxtq = Gear6MT, - gears = 6, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "6Gear-LD-L", { - name = "6-Speed, Inline, Large, Dual Clutch", - desc = "Heavy duty 6 speed inline gearbox, however not as resilient as a 4 speed. The dual clutch allows you to apply power and brake each side independently\n\nThe Final Drive slider is a multiplier applied to all the other gear ratios", - model = "models/engines/linear_l.mdl", - category = "6-Speed", - weight = Gear6LW, - switch = 0.3, - maxtq = Gear6LT, - gears = 6, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = -0.1, - [ -1 ] = 1 - } -} ) - --- Transaxial - -ACF_DefineGearbox( "6Gear-T-S", { - name = "6-Speed, Transaxial, Small", - desc = "A small and light 6 speed gearbox, with a limited max torque rating.", - model = "models/engines/transaxial_s.mdl", - category = "6-Speed", - weight = Gear6SW, - switch = 0.15, - maxtq = Gear6ST, - gears = 6, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "6Gear-T-M", { - name = "6-Speed, Transaxial, Medium", - desc = "A medium duty 6 speed gearbox with a limited torque rating.", - model = "models/engines/transaxial_m.mdl", - category = "6-Speed", - weight = Gear6MW, - switch = 0.2, - maxtq = Gear6MT, - gears = 6, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "6Gear-T-L", { - name = "6-Speed, Transaxial, Large", - desc = "Heavy duty 6 speed gearbox, however not as resilient as a 4 speed.", - model = "models/engines/transaxial_l.mdl", - category = "6-Speed", - weight = Gear6LW, - switch = 0.3, - maxtq = Gear6LT, - gears = 6, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = -0.1, - [ -1 ] = 1 - } -} ) - --- Transaxial Dual Clutch - -ACF_DefineGearbox( "6Gear-TD-S", { - name = "6-Speed, Transaxial, Small, Dual Clutch", - desc = "A small and light 6 speed gearbox, with a limited max torque rating. The dual clutch allows you to apply power and brake each side independently\n\nThe Final Drive slider is a multiplier applied to all the other gear ratios", - model = "models/engines/transaxial_s.mdl", - category = "6-Speed", - weight = Gear6SW, - switch = 0.15, - maxtq = Gear6ST, - gears = 6, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "6Gear-TD-M", { - name = "6-Speed, Transaxial, Medium, Dual Clutch", - desc = "A a medium duty 6 speed gearbox. The added gears reduce torque capacity substantially. The dual clutch allows you to apply power and brake each side independently\n\nThe Final Drive slider is a multiplier applied to all the other gear ratios", - model = "models/engines/transaxial_m.mdl", - category = "6-Speed", - weight = Gear6MW, - switch = 0.2, - maxtq = Gear6MT, - gears = 6, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "6Gear-TD-L", { - name = "6-Speed, Transaxial, Large, Dual Clutch", - desc = "Heavy duty 6 speed gearbox, however not as resilient as a 4 speed. The dual clutch allows you to apply power and brake each side independently\n\nThe Final Drive slider is a multiplier applied to all the other gear ratios", - model = "models/engines/transaxial_l.mdl", - category = "6-Speed", - weight = Gear6LW, - switch = 0.3, - maxtq = Gear6LT, - gears = 6, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = -0.1, - [ -1 ] = 1 - } -} ) - --- Straight-through gearboxes - -ACF_DefineGearbox( "6Gear-ST-S", { - name = "6-Speed, Straight, Small", - desc = "A small and light 6 speed straight-through gearbox.", - model = "models/engines/t5small.mdl", - category = "6-Speed", - weight = math.floor(Gear6SW * StWB), - switch = 0.15, - maxtq = math.floor(Gear6ST * StTB), - gears = 6, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "6Gear-ST-M", { - name = "6-Speed, Straight, Medium", - desc = "A medium 6 speed straight-through gearbox.", - model = "models/engines/t5med.mdl", - category = "6-Speed", - weight = math.floor(Gear6MW * StWB), - switch = 0.2, - maxtq = math.floor(Gear6MT * StTB), - gears = 6, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "6Gear-ST-L", { - name = "6-Speed, Straight, Large", - desc = "A large 6 speed straight-through gearbox.", - model = "models/engines/t5large.mdl", - category = "6-Speed", - weight = math.floor(Gear6LW * StWB), - switch = 0.3, - maxtq = math.floor(Gear6LT * StTB), - gears = 6, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = -0.1, - [ -1 ] = 0.5 +ACF.RegisterGearboxClass("6-Speed", { + Name = "6-Speed Gearbox", + CreateMenu = ACF.ManualGearboxMenu, + Gears = { + Min = 0, + Max = 6, } -} ) +}) + +do -- Inline Gearboxes + ACF.RegisterGearbox("6Gear-L-S", "6-Speed", { + Name = "6-Speed, Inline, Small", + Description = "A small and light 6 speed inline gearbox, with a limited max torque rating.", + Model = "models/engines/linear_s.mdl", + Mass = Gear6SW, + Switch = 0.15, + MaxTorque = Gear6ST, + }) + + ACF.RegisterGearbox("6Gear-L-M", "6-Speed", { + Name = "6-Speed, Inline, Medium", + Description = "A medium duty 6 speed inline gearbox with a limited torque rating.", + Model = "models/engines/linear_m.mdl", + Mass = Gear6MW, + Switch = 0.2, + MaxTorque = Gear6MT, + }) + + ACF.RegisterGearbox("6Gear-L-L", "6-Speed", { + Name = "6-Speed, Inline, Large", + Description = "Heavy duty 6 speed inline gearbox, however not as resilient as a 4 speed.", + Model = "models/engines/linear_l.mdl", + Mass = Gear6LW, + Switch = 0.3, + MaxTorque = Gear6LT, + }) +end + +do -- Inline Dual Clutch Gearboxes + ACF.RegisterGearbox("6Gear-LD-S", "6-Speed", { + Name = "6-Speed, Inline, Small, Dual Clutch", + Description = "A small and light 6 speed inline gearbox, with a limited max torque rating.", + Model = "models/engines/linear_s.mdl", + Mass = Gear6SW, + Switch = 0.15, + MaxTorque = Gear6ST, + DualClutch = true, + }) + + ACF.RegisterGearbox("6Gear-LD-M", "6-Speed", { + Name = "6-Speed, Inline, Medium, Dual Clutch", + Description = "A a medium duty 6 speed inline gearbox. The added gears reduce torque capacity substantially.", + Model = "models/engines/linear_m.mdl", + Mass = Gear6MW, + Switch = 0.2, + MaxTorque = Gear6MT, + DualClutch = true, + }) + + ACF.RegisterGearbox("6Gear-LD-L", "6-Speed", { + Name = "6-Speed, Inline, Large, Dual Clutch", + Description = "Heavy duty 6 speed inline gearbox, however not as resilient as a 4 speed.", + Model = "models/engines/linear_l.mdl", + Mass = Gear6LW, + Switch = 0.3, + MaxTorque = Gear6LT, + DualClutch = true, + }) +end + +do -- Transaxial Gearboxes + ACF.RegisterGearbox("6Gear-T-S", "6-Speed", { + Name = "6-Speed, Transaxial, Small", + Description = "A small and light 6 speed gearbox, with a limited max torque rating.", + Model = "models/engines/transaxial_s.mdl", + Mass = Gear6SW, + Switch = 0.15, + MaxTorque = Gear6ST, + }) + + ACF.RegisterGearbox("6Gear-T-M", "6-Speed", { + Name = "6-Speed, Transaxial, Medium", + Description = "A medium duty 6 speed gearbox with a limited torque rating.", + Model = "models/engines/transaxial_m.mdl", + Mass = Gear6MW, + Switch = 0.2, + MaxTorque = Gear6MT, + }) + + ACF.RegisterGearbox("6Gear-T-L", "6-Speed", { + Name = "6-Speed, Transaxial, Large", + Description = "Heavy duty 6 speed gearbox, however not as resilient as a 4 speed.", + Model = "models/engines/transaxial_l.mdl", + Mass = Gear6LW, + Switch = 0.3, + MaxTorque = Gear6LT, + }) +end + +do -- Transaxial Dual Clutch + ACF.RegisterGearbox("6Gear-TD-S", "6-Speed", { + Name = "6-Speed, Transaxial, Small, Dual Clutch", + Description = "A small and light 6 speed gearbox, with a limited max torque rating.", + Model = "models/engines/transaxial_s.mdl", + Mass = Gear6SW, + Switch = 0.15, + MaxTorque = Gear6ST, + DualClutch = true, + }) + + ACF.RegisterGearbox("6Gear-TD-M", "6-Speed", { + Name = "6-Speed, Transaxial, Medium, Dual Clutch", + Description = "A a medium duty 6 speed gearbox. The added gears reduce torque capacity substantially.", + Model = "models/engines/transaxial_m.mdl", + Mass = Gear6MW, + Switch = 0.2, + MaxTorque = Gear6MT, + DualClutch = true, + }) + + ACF.RegisterGearbox("6Gear-TD-L", "6-Speed", { + Name = "6-Speed, Transaxial, Large, Dual Clutch", + Description = "Heavy duty 6 speed gearbox, however not as resilient as a 4 speed.", + Model = "models/engines/transaxial_l.mdl", + Mass = Gear6LW, + Switch = 0.3, + MaxTorque = Gear6LT, + DualClutch = true, + }) +end + +do -- Straight-through Gearboxes + ACF.RegisterGearbox("6Gear-ST-S", "6-Speed", { + Name = "6-Speed, Straight, Small", + Description = "A small and light 6 speed straight-through gearbox.", + Model = "models/engines/t5small.mdl", + Mass = math.floor(Gear6SW * StWB), + Switch = 0.15, + MaxTorque = math.floor(Gear6ST * StTB), + }) + + ACF.RegisterGearbox("6Gear-ST-M", "6-Speed", { + Name = "6-Speed, Straight, Medium", + Description = "A medium 6 speed straight-through gearbox.", + Model = "models/engines/t5med.mdl", + Mass = math.floor(Gear6MW * StWB), + Switch = 0.2, + MaxTorque = math.floor(Gear6MT * StTB), + }) + + ACF.RegisterGearbox("6Gear-ST-L", "6-Speed", { + Name = "6-Speed, Straight, Large", + Description = "A large 6 speed straight-through gearbox.", + Model = "models/engines/t5large.mdl", + Mass = math.floor(Gear6LW * StWB), + Switch = 0.3, + MaxTorque = math.floor(Gear6LT * StTB), + }) +end diff --git a/lua/acf/shared/gearboxes/7-auto.lua b/lua/acf/shared/gearboxes/7-auto.lua new file mode 100644 index 000000000..32639f693 --- /dev/null +++ b/lua/acf/shared/gearboxes/7-auto.lua @@ -0,0 +1,276 @@ +-- Weight +local wmul = 1.5 +local Gear7SW = 100 * wmul +local Gear7MW = 200 * wmul +local Gear7LW = 400 * wmul + +-- Torque Rating +local Gear7ST = 425 +local Gear7MT = 1250 +local Gear7LT = 10000 + +-- Straight through bonuses +local StWB = 0.75 --straight weight bonus mulitplier +local StTB = 1.25 --straight torque bonus multiplier + +-- Shift Time +local ShiftS = 0.25 +local ShiftM = 0.35 +local ShiftL = 0.5 + +local function InitGearbox(Gearbox) + local Gears = Gearbox.Gears + + Gearbox.Automatic = true + Gearbox.ShiftScale = 1 + Gearbox.Hold = false + Gearbox.GearCount = Gearbox.MaxGear + 1 + + Gears[Gearbox.GearCount] = Gearbox.Reverse + + Gearbox:ChangeDrive(1) +end + +ACF.RegisterGearboxClass("7-Auto", { + Name = "7-Speed Automatic", + CreateMenu = ACF.AutomaticGearboxMenu, + Gears = { + Min = 0, + Max = 7, + }, + OnSpawn = InitGearbox, + OnUpdate = InitGearbox, + VerifyData = function(Data, Class) + do -- Shift point table verification + local Points = Data.ShiftPoints + local Mult = Data.ShiftUnit or 1 + local Max = Class.Gears.Max + + if not istable(Points) then + local Encoded = Data.Gear9 and tostring(Data.Gear9) + + Points = { [0] = -1 } + + if Encoded then + local Count = 0 + + for Point in string.gmatch(Encoded, "[^,]+") do + Count = Count + 1 + + if Count > Max then break end + + Points[Count] = ACF.CheckNumber(Point, Count * 100) + end + end + + Data.ShiftPoints = Points + else + Points[0] = -1 + end + + for I = 1, Max do + local Point = ACF.CheckNumber(Points[I]) + + if not Point then + Point = ACF.CheckNumber(Data["Shift" .. I], I * 100) * Mult + + Data["Shift" .. I] = nil + end + + Points[I] = math.Clamp(Point, 0, 9999) + end + end + + do -- Reverse gear verification + local Reverse = ACF.CheckNumber(Data.Reverse) + + if not Reverse then + Reverse = ACF.CheckNumber(Data.Gear8, -1) + + Data.Gear8 = nil + end + + Data.Reverse = math.Clamp(Reverse, -1, 1) + end + end, + SetupInputs = function(List) + local Count = #List + + List[Count + 1] = "Hold Gear" + List[Count + 2] = "Shift Speed Scale" + end, + OnLast = function(Gearbox) + Gearbox.Automatic = nil + Gearbox.ShiftScale = nil + Gearbox.Drive = nil + Gearbox.Hold = nil + end, + GetGearsText = function(Gearbox) + local GearText = "Gear %s: %s, Upshift @ %s kph / %s mph\n" + local Text = "%sReverse gear: %s\n" + local Points = Gearbox.ShiftPoints + local Gears = Gearbox.Gears + local GearsText = "" + + for I = 1, Gearbox.MaxGear do + local Ratio = math.Round(Gears[I], 2) + local KPH = math.Round(Points[I] / 10.936, 1) + local MPH = math.Round(Points[I] / 17.6, 1) + + GearsText = GearsText .. GearText:format(I, Ratio, KPH, MPH) + end + + return Text:format(GearsText, math.Round(Gearbox.Reverse, 2)) + end, +}) + +do -- Inline Gearboxes + ACF.RegisterGearbox("7Gear-A-L-S", "7-Auto", { + Name = "7-Speed Auto, Inline, Small", + Description = "A small, and light 7 speed automatic inline gearbox, with a somewhat limited max torque rating", + Model = "models/engines/linear_s.mdl", + Mass = Gear7SW, + Switch = ShiftS, + MaxTorque = Gear7ST, + }) + + ACF.RegisterGearbox("7Gear-A-L-M", "7-Auto", { + Name = "7-Speed Auto, Inline, Medium", + Description = "A medium sized, 7 speed automatic inline gearbox", + Model = "models/engines/linear_m.mdl", + Mass = Gear7MW, + Switch = ShiftM, + MaxTorque = Gear7MT, + }) + + ACF.RegisterGearbox("7Gear-A-L-L", "7-Auto", { + Name = "7-Speed Auto, Inline, Large", + Description = "A large, heavy and sturdy 7 speed inline gearbox", + Model = "models/engines/linear_l.mdl", + Mass = Gear7LW, + Switch = ShiftL, + MaxTorque = Gear7LT, + }) +end + +do -- Inline Dual Clutch Gearboxes + ACF.RegisterGearbox("7Gear-A-LD-S", "7-Auto", { + Name = "7-Speed Auto, Inline, Small, Dual Clutch", + Description = "A small, and light 7 speed automatic inline gearbox, with a somewhat limited max torque rating", + Model = "models/engines/linear_s.mdl", + Mass = Gear7SW, + Switch = ShiftS, + MaxTorque = Gear7ST, + DualClutch = true, + }) + + ACF.RegisterGearbox("7Gear-A-LD-M", "7-Auto", { + Name = "7-Speed Auto, Inline, Medium, Dual Clutch", + Description = "A medium sized, 7 speed automatic inline gearbox", + Model = "models/engines/linear_m.mdl", + Mass = Gear7MW, + Switch = ShiftM, + MaxTorque = Gear7MT, + DualClutch = true, + }) + + ACF.RegisterGearbox("7Gear-A-LD-L", "7-Auto", { + Name = "7-Speed Auto, Inline, Large, Dual Clutch", + Description = "A large, heavy and sturdy 7 speed automatic inline gearbox", + Model = "models/engines/linear_l.mdl", + Mass = Gear7LW, + Switch = ShiftL, + MaxTorque = Gear7LT, + DualClutch = true, + }) +end + +do -- Transaxial Gearboxes + ACF.RegisterGearbox("7Gear-A-T-S", "7-Auto", { + Name = "7-Speed Auto, Transaxial, Small", + Description = "A small, and light 7 speed automatic gearbox, with a somewhat limited max torque rating", + Model = "models/engines/transaxial_s.mdl", + Mass = Gear7SW, + Switch = ShiftS, + MaxTorque = Gear7ST, + }) + + ACF.RegisterGearbox("7Gear-A-T-M", "7-Auto", { + Name = "7-Speed Auto, Transaxial, Medium", + Description = "A medium sized, 7 speed automatic gearbox", + Model = "models/engines/transaxial_m.mdl", + Mass = Gear7MW, + Switch = ShiftM, + MaxTorque = Gear7MT, + }) + + ACF.RegisterGearbox("7Gear-A-T-L", "7-Auto", { + Name = "7-Speed Auto, Transaxial, Large", + Description = "A large, heavy and sturdy 7 speed automatic gearbox", + Model = "models/engines/transaxial_l.mdl", + Mass = Gear7LW, + Switch = ShiftL, + MaxTorque = Gear7LT, + }) +end + +do -- Transaxial Dual Clutch Gearboxes + ACF.RegisterGearbox("7Gear-A-TD-S", "7-Auto", { + Name = "7-Speed Auto, Transaxial, Small, Dual Clutch", + Description = "A small, and light 7 speed automatic gearbox, with a somewhat limited max torque rating", + Model = "models/engines/transaxial_s.mdl", + Mass = Gear7SW, + Switch = ShiftS, + MaxTorque = Gear7ST, + DualClutch = true, + }) + + ACF.RegisterGearbox("7Gear-A-TD-M", "7-Auto", { + Name = "7-Speed Auto, Transaxial, Medium, Dual Clutch", + Description = "A medium sized, 7 speed automatic gearbox", + Model = "models/engines/transaxial_m.mdl", + Mass = Gear7MW, + Switch = ShiftM, + MaxTorque = Gear7MT, + DualClutch = true, + }) + + ACF.RegisterGearbox("7Gear-A-TD-L", "7-Auto", { + Name = "7-Speed Auto, Transaxial, Large, Dual Clutch", + Description = "A large, heavy and sturdy 7 speed automatic gearbox", + Model = "models/engines/transaxial_l.mdl", + Mass = Gear7LW, + Switch = ShiftL, + MaxTorque = Gear7LT, + DualClutch = true, + }) +end + +do -- Straight-through Gearboxes + ACF.RegisterGearbox("7Gear-A-ST-S", "7-Auto", { + Name = "7-Speed Auto, Straight, Small", + Description = "A small straight-through automatic gearbox", + Model = "models/engines/t5small.mdl", + Mass = math.floor(Gear7SW * StWB), + Switch = ShiftS, + MaxTorque = math.floor(Gear7ST * StTB), + }) + + ACF.RegisterGearbox("7Gear-A-ST-M", "7-Auto", { + Name = "7-Speed Auto, Straight, Medium", + Description = "A medium sized, 7 speed automatic straight-through gearbox.", + Model = "models/engines/t5med.mdl", + Mass = math.floor(Gear7MW * StWB), + Switch = ShiftM, + MaxTorque = math.floor(Gear7MT * StTB), + }) + + ACF.RegisterGearbox("7Gear-A-ST-L", "7-Auto", { + Name = "7-Speed Auto, Straight, Large", + Description = "A large sized, 7 speed automatic straight-through gearbox.", + Model = "models/engines/t5large.mdl", + Mass = math.floor(Gear7LW * StWB), + Switch = ShiftL, + MaxTorque = math.floor(Gear7LT * StTB), + }) +end diff --git a/lua/acf/shared/gearboxes/8-speed.lua b/lua/acf/shared/gearboxes/8-speed.lua index f20262f39..25ac1c188 100644 --- a/lua/acf/shared/gearboxes/8-speed.lua +++ b/lua/acf/shared/gearboxes/8-speed.lua @@ -13,363 +13,162 @@ local Gear8MT = 1000 local Gear8LT = 10000 local StTB = 1.25 --straight torque bonus multiplier --- Inline - -ACF_DefineGearbox( "8Gear-L-S", { - name = "8-Speed, Inline, Small", - desc = "A small and light 8 speed gearbox.", - model = "models/engines/linear_s.mdl", - category = "8-Speed", - weight = Gear8SW, - switch = 0.15, - maxtq = Gear8ST, - gears = 8, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = 0.6, - [ 7 ] = 0.7, - [ 8 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "8Gear-L-M", { - name = "8-Speed, Inline, Medium", - desc = "A medium duty 8 speed gearbox..", - model = "models/engines/linear_m.mdl", - category = "8-Speed", - weight = Gear8MW, - switch = 0.2, - maxtq = Gear8MT, - gears = 8, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = 0.6, - [ 7 ] = 0.7, - [ 8 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "8Gear-L-L", { - name = "8-Speed, Inline, Large", - desc = "Heavy duty 8 speed gearbox, however rather heavy.", - model = "models/engines/linear_l.mdl", - category = "8-Speed", - weight = Gear8LW, - switch = 0.3, - maxtq = Gear8LT, - gears = 8, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = 0.6, - [ 7 ] = 0.7, - [ 8 ] = -0.1, - [ -1 ] = 1 - } -} ) - --- Inline Dual Clutch - -ACF_DefineGearbox( "8Gear-LD-S", { - name = "8-Speed, Inline, Small, Dual Clutch", - desc = "A small and light 8 speed gearbox The dual clutch allows you to apply power and brake each side independently\n\nThe Final Drive slider is a multiplier applied to all the other gear ratios", - model = "models/engines/linear_s.mdl", - category = "8-Speed", - weight = Gear8SW, - switch = 0.15, - maxtq = Gear8ST, - gears = 8, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = 0.6, - [ 7 ] = 0.7, - [ 8 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "8Gear-LD-M", { - name = "8-Speed, Inline, Medium, Dual Clutch", - desc = "A a medium duty 8 speed gearbox. The dual clutch allows you to apply power and brake each side independently\n\nThe Final Drive slider is a multiplier applied to all the other gear ratios", - model = "models/engines/linear_m.mdl", - category = "8-Speed", - weight = Gear8MW, - switch = 0.2, - maxtq = Gear8MT, - gears = 8, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = 0.6, - [ 7 ] = 0.7, - [ 8 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "8Gear-LD-L", { - name = "8-Speed, Inline, Large, Dual Clutch", - desc = "Heavy duty 8 speed gearbox. The dual clutch allows you to apply power and brake each side independently\n\nThe Final Drive slider is a multiplier applied to all the other gear ratios", - model = "models/engines/linear_l.mdl", - category = "8-Speed", - weight = Gear8LW, - switch = 0.3, - maxtq = Gear8LT, - gears = 8, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = 0.6, - [ 7 ] = 0.7, - [ 8 ] = -0.1, - [ -1 ] = 1 - } -} ) - --- Transaxial - -ACF_DefineGearbox( "8Gear-T-S", { - name = "8-Speed, Transaxial, Small", - desc = "A small and light 8 speed gearbox..", - model = "models/engines/transaxial_s.mdl", - category = "8-Speed", - weight = Gear8SW, - switch = 0.15, - maxtq = Gear8ST, - gears = 8, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = 0.6, - [ 7 ] = 0.7, - [ 8 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "8Gear-T-M", { - name = "8-Speed, Transaxial, Medium", - desc = "A medium duty 8 speed gearbox..", - model = "models/engines/transaxial_m.mdl", - category = "8-Speed", - weight = Gear8MW, - switch = 0.2, - maxtq = Gear8MT, - gears = 8, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = 0.6, - [ 7 ] = 0.7, - [ 8 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "8Gear-T-L", { - name = "8-Speed, Transaxial, Large", - desc = "Heavy duty 8 speed gearbox, however rather heavy.", - model = "models/engines/transaxial_l.mdl", - category = "8-Speed", - weight = Gear8LW, - switch = 0.3, - maxtq = Gear8LT, - gears = 8, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = 0.6, - [ 7 ] = 0.7, - [ 8 ] = -0.1, - [ -1 ] = 1 - } -} ) - --- Transaxial Dual Clutch - -ACF_DefineGearbox( "8Gear-TD-S", { - name = "8-Speed, Transaxial, Small, Dual Clutch", - desc = "A small and light 8 speed gearbox The dual clutch allows you to apply power and brake each side independently\n\nThe Final Drive slider is a multiplier applied to all the other gear ratios", - model = "models/engines/transaxial_s.mdl", - category = "8-Speed", - weight = Gear8SW, - switch = 0.15, - maxtq = Gear8ST, - gears = 8, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = 0.6, - [ 7 ] = 0.7, - [ 8 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "8Gear-TD-M", { - name = "8-Speed, Transaxial, Medium, Dual Clutch", - desc = "A a medium duty 8 speed gearbox. The dual clutch allows you to apply power and brake each side independently\n\nThe Final Drive slider is a multiplier applied to all the other gear ratios", - model = "models/engines/transaxial_m.mdl", - category = "8-Speed", - weight = Gear8MW, - switch = 0.2, - maxtq = Gear8MT, - gears = 8, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = 0.6, - [ 7 ] = 0.7, - [ 8 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "8Gear-TD-L", { - name = "8-Speed, Transaxial, Large, Dual Clutch", - desc = "Heavy duty 8 speed gearbox. The dual clutch allows you to apply power and brake each side independently\n\nThe Final Drive slider is a multiplier applied to all the other gear ratios", - model = "models/engines/transaxial_l.mdl", - category = "8-Speed", - weight = Gear8LW, - switch = 0.3, - maxtq = Gear8LT, - gears = 8, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = 0.6, - [ 7 ] = 0.7, - [ 8 ] = -0.1, - [ -1 ] = 1 - } -} ) - --- Straight-through gearboxes - -ACF_DefineGearbox( "8Gear-ST-S", { - name = "8-Speed, Straight, Small", - desc = "A small and light 8 speed straight-through gearbox.", - model = "models/engines/t5small.mdl", - category = "8-Speed", - weight = math.floor(Gear8SW * StWB), - switch = 0.15, - maxtq = math.floor(Gear8ST * StTB), - gears = 8, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = 0.6, - [ 7 ] = 0.7, - [ 8 ] = 0.8, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "8Gear-ST-M", { - name = "8-Speed, Straight, Medium", - desc = "A medium 8 speed straight-through gearbox.", - model = "models/engines/t5med.mdl", - category = "8-Speed", - weight = math.floor(Gear8MW * StWB), - switch = 0.2, - maxtq = math.floor(Gear8MT * StTB), - gears = 8, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = 0.6, - [ 7 ] = 0.7, - [ 8 ] = -0.1, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "8Gear-ST-L", { - name = "8-Speed, Straight, Large", - desc = "A large 8 speed straight-through gearbox.", - model = "models/engines/t5large.mdl", - category = "8-Speed", - weight = math.floor(Gear8LW * StWB), - switch = 0.3, - maxtq = math.floor(Gear8LT * StTB), - gears = 8, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.1, - [ 2 ] = 0.2, - [ 3 ] = 0.3, - [ 4 ] = 0.4, - [ 5 ] = 0.5, - [ 6 ] = 0.6, - [ 7 ] = 0.7, - [ 8 ] = -0.1, - [ -1 ] = 0.5 +ACF.RegisterGearboxClass("8-Speed", { + Name = "8-Speed Gearbox", + CreateMenu = ACF.ManualGearboxMenu, + Gears = { + Min = 0, + Max = 8, } -} ) +}) + +do -- Inline Gearboxes + ACF.RegisterGearbox("8Gear-L-S", "8-Speed", { + Name = "8-Speed, Inline, Small", + Description = "A small and light 8 speed gearbox.", + Model = "models/engines/linear_s.mdl", + Mass = Gear8SW, + Switch = 0.15, + MaxTorque = Gear8ST, + }) + + ACF.RegisterGearbox("8Gear-L-M", "8-Speed", { + Name = "8-Speed, Inline, Medium", + Description = "A medium duty 8 speed gearbox..", + Model = "models/engines/linear_m.mdl", + Mass = Gear8MW, + Switch = 0.2, + MaxTorque = Gear8MT, + }) + + ACF.RegisterGearbox("8Gear-L-L", "8-Speed", { + Name = "8-Speed, Inline, Large", + Description = "Heavy duty 8 speed gearbox, however rather heavy.", + Model = "models/engines/linear_l.mdl", + Mass = Gear8LW, + Switch = 0.3, + MaxTorque = Gear8LT, + }) +end + +do -- Inline Dual Clutch Gearboxes + ACF.RegisterGearbox("8Gear-LD-S", "8-Speed", { + Name = "8-Speed, Inline, Small, Dual Clutch", + Description = "A small and light 8 speed gearbox The dual clutch allows you to apply power and brake each side independently\n\nThe Final Drive slider is a multiplier applied to all the other gear ratios", + Model = "models/engines/linear_s.mdl", + Mass = Gear8SW, + Switch = 0.15, + MaxTorque = Gear8ST, + DualClutch = true, + }) + + ACF.RegisterGearbox("8Gear-LD-M", "8-Speed", { + Name = "8-Speed, Inline, Medium, Dual Clutch", + Description = "A a medium duty 8 speed gearbox. The dual clutch allows you to apply power and brake each side independently\n\nThe Final Drive slider is a multiplier applied to all the other gear ratios", + Model = "models/engines/linear_m.mdl", + Mass = Gear8MW, + Switch = 0.2, + MaxTorque = Gear8MT, + DualClutch = true, + }) + + ACF.RegisterGearbox("8Gear-LD-L", "8-Speed", { + Name = "8-Speed, Inline, Large, Dual Clutch", + Description = "Heavy duty 8 speed gearbox. The dual clutch allows you to apply power and brake each side independently\n\nThe Final Drive slider is a multiplier applied to all the other gear ratios", + Model = "models/engines/linear_l.mdl", + Mass = Gear8LW, + Switch = 0.3, + MaxTorque = Gear8LT, + DualClutch = true, + }) +end + +do -- Transaxial Gearboxes + ACF.RegisterGearbox("8Gear-T-S", "8-Speed", { + Name = "8-Speed, Transaxial, Small", + Description = "A small and light 8 speed gearbox..", + Model = "models/engines/transaxial_s.mdl", + Mass = Gear8SW, + Switch = 0.15, + MaxTorque = Gear8ST, + }) + + ACF.RegisterGearbox("8Gear-T-M", "8-Speed", { + Name = "8-Speed, Transaxial, Medium", + Description = "A medium duty 8 speed gearbox..", + Model = "models/engines/transaxial_m.mdl", + Mass = Gear8MW, + Switch = 0.2, + MaxTorque = Gear8MT, + }) + + ACF.RegisterGearbox("8Gear-T-L", "8-Speed", { + Name = "8-Speed, Transaxial, Large", + Description = "Heavy duty 8 speed gearbox, however rather heavy.", + Model = "models/engines/transaxial_l.mdl", + Mass = Gear8LW, + Switch = 0.3, + MaxTorque = Gear8LT, + }) +end + +do -- Transaxial Dual Clutch Gearboxes + ACF.RegisterGearbox("8Gear-TD-S", "8-Speed", { + Name = "8-Speed, Transaxial, Small, Dual Clutch", + Description = "A small and light 8 speed gearbox The dual clutch allows you to apply power and brake each side independently\n\nThe Final Drive slider is a multiplier applied to all the other gear ratios", + Model = "models/engines/transaxial_s.mdl", + Mass = Gear8SW, + Switch = 0.15, + MaxTorque = Gear8ST, + DualClutch = true, + }) + + ACF.RegisterGearbox("8Gear-TD-M", "8-Speed", { + Name = "8-Speed, Transaxial, Medium, Dual Clutch", + Description = "A a medium duty 8 speed gearbox. The dual clutch allows you to apply power and brake each side independently\n\nThe Final Drive slider is a multiplier applied to all the other gear ratios", + Model = "models/engines/transaxial_m.mdl", + Mass = Gear8MW, + Switch = 0.2, + MaxTorque = Gear8MT, + DualClutch = true, + }) + + ACF.RegisterGearbox("8Gear-TD-L", "8-Speed", { + Name = "8-Speed, Transaxial, Large, Dual Clutch", + Description = "Heavy duty 8 speed gearbox. The dual clutch allows you to apply power and brake each side independently\n\nThe Final Drive slider is a multiplier applied to all the other gear ratios", + Model = "models/engines/transaxial_l.mdl", + Mass = Gear8LW, + Switch = 0.3, + MaxTorque = Gear8LT, + DualClutch = true, + }) +end + +do -- Straight-through Gearboxes + ACF.RegisterGearbox("8Gear-ST-S", "8-Speed", { + Name = "8-Speed, Straight, Small", + Description = "A small and light 8 speed straight-through gearbox.", + Model = "models/engines/t5small.mdl", + Mass = math.floor(Gear8SW * StWB), + Switch = 0.15, + MaxTorque = math.floor(Gear8ST * StTB), + }) + + ACF.RegisterGearbox("8Gear-ST-M", "8-Speed", { + Name = "8-Speed, Straight, Medium", + Description = "A medium 8 speed straight-through gearbox.", + Model = "models/engines/t5med.mdl", + Mass = math.floor(Gear8MW * StWB), + Switch = 0.2, + MaxTorque = math.floor(Gear8MT * StTB), + }) + + ACF.RegisterGearbox("8Gear-ST-L", "8-Speed", { + Name = "8-Speed, Straight, Large", + Description = "A large 8 speed straight-through gearbox.", + Model = "models/engines/t5large.mdl", + Mass = math.floor(Gear8LW * StWB), + Switch = 0.3, + MaxTorque = math.floor(Gear8LT * StTB), + }) +end diff --git a/lua/acf/shared/gearboxes/automatic.lua b/lua/acf/shared/gearboxes/automatic.lua deleted file mode 100644 index 137e6dc81..000000000 --- a/lua/acf/shared/gearboxes/automatic.lua +++ /dev/null @@ -1,1059 +0,0 @@ --- Automatic Gearboxes --- Weight -local wmul = 1.5 -local Gear3SW = 60 * wmul -local Gear3MW = 120 * wmul -local Gear3LW = 240 * wmul -local Gear5SW = 80 * wmul -local Gear5MW = 160 * wmul -local Gear5LW = 320 * wmul -local Gear7SW = 100 * wmul -local Gear7MW = 200 * wmul -local Gear7LW = 400 * wmul --- Torque Rating -local Gear3ST = 675 -local Gear3MT = 2125 -local Gear3LT = 10000 -local Gear5ST = 550 -local Gear5MT = 1700 -local Gear5LT = 10000 -local Gear7ST = 425 -local Gear7MT = 1250 -local Gear7LT = 10000 --- Straight through bonuses -local StWB = 0.75 --straight weight bonus mulitplier -local StTB = 1.25 --straight torque bonus multiplier --- Shift Time -local ShiftS = 0.25 -local ShiftM = 0.35 -local ShiftL = 0.5 -local blurb = "\n\nAutomatics are controlled by shifting into either forward or reverse drive. In forward drive, the automatic will choose the appropriate gearing based the upshift speed setting for each gear." -blurb = blurb .. " For climbing inclines, automatics have an input to prevent upshifts. There's also an input for adjusting the shiftpoints, if for example you're driving with less throttle and want to shift earlier." -blurb = blurb .. " However, automatics are significantly heavier than their manual counterparts, and lose a bit of output torque due to inefficiency." - ---hold gear, shift scale, less efficient --- 3 Speed --- Inline -ACF_DefineGearbox("3Gear-A-L-S", { - name = "3-Speed Auto, Inline, Small", - desc = "A small, and light 3 speed automatic inline gearbox, with a somewhat limited max torque rating" .. blurb, - model = "models/engines/linear_s.mdl", - category = "Automatic", - weight = Gear3SW, - switch = ShiftS, - maxtq = Gear3ST, - auto = true, - gears = 3, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [-2] = -0.1, --reverse - [-1] = 0.5 --final drive - } -}) - -ACF_DefineGearbox("3Gear-A-L-M", { - name = "3-Speed Auto, Inline, Medium", - desc = "A medium sized, 3 speed automatic inline gearbox" .. blurb, - model = "models/engines/linear_m.mdl", - category = "Automatic", - weight = Gear3MW, - switch = ShiftM, - maxtq = Gear3MT, - auto = true, - gears = 3, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("3Gear-A-L-L", { - name = "3-Speed Auto, Inline, Large", - desc = "A large, heavy and sturdy 3 speed inline gearbox" .. blurb, - model = "models/engines/linear_l.mdl", - category = "Automatic", - weight = Gear3LW, - switch = ShiftL, - maxtq = Gear3LT, - auto = true, - gears = 3, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [-2] = -0.1, - [-1] = 1 - } -}) - --- Inline Dual Clutch -ACF_DefineGearbox("3Gear-A-LD-S", { - name = "3-Speed Auto, Inline, Small, Dual Clutch", - desc = "A small, and light 3 speed automatic inline gearbox, with a somewhat limited max torque rating" .. blurb, - model = "models/engines/linear_s.mdl", - category = "Automatic", - weight = Gear3SW, - switch = ShiftS, - maxtq = Gear3ST, - auto = true, - doubleclutch = true, - gears = 3, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("3Gear-A-LD-M", { - name = "3-Speed Auto, Inline, Medium, Dual Clutch", - desc = "A medium sized, 3 speed automatic inline gearbox" .. blurb, - model = "models/engines/linear_m.mdl", - category = "Automatic", - weight = Gear3MW, - switch = ShiftM, - maxtq = Gear3MT, - auto = true, - doubleclutch = true, - gears = 3, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("3Gear-A-LD-L", { - name = "3-Speed Auto, Inline, Large, Dual Clutch", - desc = "A large, heavy and sturdy 3 speed automatic inline gearbox" .. blurb, - model = "models/engines/linear_l.mdl", - category = "Automatic", - weight = Gear3LW, - switch = ShiftL, - maxtq = Gear3LT, - auto = true, - doubleclutch = true, - gears = 3, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [-2] = -0.1, - [-1] = 1 - } -}) - --- Transaxial -ACF_DefineGearbox("3Gear-A-T-S", { - name = "3-Speed Auto, Transaxial, Small", - desc = "A small, and light 3 speed automatic gearbox, with a somewhat limited max torque rating" .. blurb, - model = "models/engines/transaxial_s.mdl", - category = "Automatic", - weight = Gear3SW, - switch = ShiftS, - maxtq = Gear3ST, - auto = true, - gears = 3, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("3Gear-A-T-M", { - name = "3-Speed Auto, Transaxial, Medium", - desc = "A medium sized, 3 speed automatic gearbox" .. blurb, - model = "models/engines/transaxial_m.mdl", - category = "Automatic", - weight = Gear3MW, - switch = ShiftM, - maxtq = Gear3MT, - auto = true, - gears = 3, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("3Gear-A-T-L", { - name = "3-Speed Auto, Transaxial, Large", - desc = "A large, heavy and sturdy 3 speed automatic gearbox" .. blurb, - model = "models/engines/transaxial_l.mdl", - category = "Automatic", - weight = Gear3LW, - switch = ShiftL, - maxtq = Gear3LT, - auto = true, - gears = 3, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [-2] = -0.1, - [-1] = 1 - } -}) - --- Transaxial Dual Clutch -ACF_DefineGearbox("3Gear-A-TD-S", { - name = "3-Speed Auto, Transaxial, Small, Dual Clutch", - desc = "A small, and light 3 speed automatic gearbox, with a somewhat limited max torque rating" .. blurb, - model = "models/engines/transaxial_s.mdl", - category = "Automatic", - weight = Gear3SW, - switch = ShiftS, - maxtq = Gear3ST, - auto = true, - doubleclutch = true, - gears = 3, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("3Gear-A-TD-M", { - name = "3-Speed Auto, Transaxial, Medium, Dual Clutch", - desc = "A medium sized, 3 speed automatic gearbox" .. blurb, - model = "models/engines/transaxial_m.mdl", - category = "Automatic", - weight = Gear3MW, - switch = ShiftM, - maxtq = Gear3MT, - auto = true, - doubleclutch = true, - gears = 3, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("3Gear-A-TD-L", { - name = "3-Speed Auto, Transaxial, Large, Dual Clutch", - desc = "A large, heavy and sturdy 3 speed automatic gearbox" .. blurb, - model = "models/engines/transaxial_l.mdl", - category = "Automatic", - weight = Gear3LW, - switch = ShiftL, - maxtq = Gear3LT, - auto = true, - doubleclutch = true, - gears = 3, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [-2] = -0.1, - [-1] = 1 - } -}) - --- Straight-through gearboxes -ACF_DefineGearbox("3Gear-A-ST-S", { - name = "3-Speed Auto, Straight, Small", - desc = "A small straight-through automatic gearbox" .. blurb, - model = "models/engines/t5small.mdl", - category = "Automatic", - weight = math.floor(Gear3SW * StWB), - switch = ShiftS, - maxtq = math.floor(Gear3ST * StTB), - auto = true, - gears = 3, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [-2] = -0.1, - [-1] = 1 - } -}) - -ACF_DefineGearbox("3Gear-A-ST-M", { - name = "3-Speed Auto, Straight, Medium", - desc = "A medium sized, 3 speed automatic straight-through gearbox." .. blurb, - model = "models/engines/t5med.mdl", - category = "Automatic", - weight = math.floor(Gear3MW * StWB), - switch = ShiftM, - maxtq = math.floor(Gear3MT * StTB), - auto = true, - gears = 3, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("3Gear-A-ST-L", { - name = "3-Speed Auto, Straight, Large", - desc = "A large sized, 3 speed automatic straight-through gearbox." .. blurb, - model = "models/engines/t5large.mdl", - category = "Automatic", - weight = math.floor(Gear3LW * StWB), - switch = ShiftL, - maxtq = math.floor(Gear3LT * StTB), - auto = true, - gears = 3, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [-2] = -0.1, - [-1] = 0.5 - } -}) - --- 5 Speed --- Inline -ACF_DefineGearbox("5Gear-A-L-S", { - name = "5-Speed Auto, Inline, Small", - desc = "A small, and light 5 speed automatic inline gearbox, with a somewhat limited max torque rating" .. blurb, - model = "models/engines/linear_s.mdl", - category = "Automatic", - weight = Gear5SW, - switch = ShiftS, - maxtq = Gear5ST, - auto = true, - gears = 5, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("5Gear-A-L-M", { - name = "5-Speed Auto, Inline, Medium", - desc = "A medium sized, 5 speed automatic inline gearbox" .. blurb, - model = "models/engines/linear_m.mdl", - category = "Automatic", - weight = Gear5MW, - switch = ShiftM, - maxtq = Gear5MT, - auto = true, - gears = 5, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("5Gear-A-L-L", { - name = "5-Speed Auto, Inline, Large", - desc = "A large, heavy and sturdy 5 speed inline gearbox" .. blurb, - model = "models/engines/linear_l.mdl", - category = "Automatic", - weight = Gear5LW, - switch = ShiftL, - maxtq = Gear5LT, - auto = true, - gears = 5, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [-2] = -0.1, - [-1] = 0.5 - } -}) - --- Inline Dual Clutch -ACF_DefineGearbox("5Gear-A-LD-S", { - name = "5-Speed Auto, Inline, Small, Dual Clutch", - desc = "A small, and light 5 speed automatic inline gearbox, with a somewhat limited max torque rating" .. blurb, - model = "models/engines/linear_s.mdl", - category = "Automatic", - weight = Gear5SW, - switch = ShiftS, - maxtq = Gear5ST, - auto = true, - doubleclutch = true, - gears = 5, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("5Gear-A-LD-M", { - name = "5-Speed Auto, Inline, Medium, Dual Clutch", - desc = "A medium sized, 5 speed automatic inline gearbox" .. blurb, - model = "models/engines/linear_m.mdl", - category = "Automatic", - weight = Gear5MW, - switch = ShiftM, - maxtq = Gear5MT, - auto = true, - doubleclutch = true, - gears = 5, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("5Gear-A-LD-L", { - name = "5-Speed Auto, Inline, Large, Dual Clutch", - desc = "A large, heavy and sturdy 5 speed automatic inline gearbox" .. blurb, - model = "models/engines/linear_l.mdl", - category = "Automatic", - weight = Gear5LW, - switch = ShiftL, - maxtq = Gear5LT, - auto = true, - doubleclutch = true, - gears = 5, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [-2] = -0.1, - [-1] = 0.5 - } -}) - --- Transaxial -ACF_DefineGearbox("5Gear-A-T-S", { - name = "5-Speed Auto, Transaxial, Small", - desc = "A small, and light 5 speed automatic gearbox, with a somewhat limited max torque rating" .. blurb, - model = "models/engines/transaxial_s.mdl", - category = "Automatic", - weight = Gear5SW, - switch = ShiftS, - maxtq = Gear5ST, - auto = true, - gears = 5, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("5Gear-A-T-M", { - name = "5-Speed Auto, Transaxial, Medium", - desc = "A medium sized, 5 speed automatic gearbox" .. blurb, - model = "models/engines/transaxial_m.mdl", - category = "Automatic", - weight = Gear5MW, - switch = ShiftM, - maxtq = Gear5MT, - auto = true, - gears = 5, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("5Gear-A-T-L", { - name = "5-Speed Auto, Transaxial, Large", - desc = "A large, heavy and sturdy 5 speed automatic gearbox" .. blurb, - model = "models/engines/transaxial_l.mdl", - category = "Automatic", - weight = Gear5LW, - switch = ShiftL, - maxtq = Gear5LT, - auto = true, - gears = 5, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [-2] = -0.1, - [-1] = 0.5 - } -}) - --- Transaxial Dual Clutch -ACF_DefineGearbox("5Gear-A-TD-S", { - name = "5-Speed Auto, Transaxial, Small, Dual Clutch", - desc = "A small, and light 5 speed automatic gearbox, with a somewhat limited max torque rating" .. blurb, - model = "models/engines/transaxial_s.mdl", - category = "Automatic", - weight = Gear5SW, - switch = ShiftS, - maxtq = Gear5ST, - auto = true, - doubleclutch = true, - gears = 5, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("5Gear-A-TD-M", { - name = "5-Speed Auto, Transaxial, Medium, Dual Clutch", - desc = "A medium sized, 5 speed automatic gearbox" .. blurb, - model = "models/engines/transaxial_m.mdl", - category = "Automatic", - weight = Gear5MW, - switch = ShiftM, - maxtq = Gear5MT, - auto = true, - doubleclutch = true, - gears = 5, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("5Gear-A-TD-L", { - name = "5-Speed Auto, Transaxial, Large, Dual Clutch", - desc = "A large, heavy and sturdy 5 speed automatic gearbox" .. blurb, - model = "models/engines/transaxial_l.mdl", - category = "Automatic", - weight = Gear5LW, - switch = ShiftL, - maxtq = Gear5LT, - auto = true, - doubleclutch = true, - gears = 5, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [-2] = -0.1, - [-1] = 0.5 - } -}) - --- Straight-through gearboxes -ACF_DefineGearbox("5Gear-A-ST-S", { - name = "5-Speed Auto, Straight, Small", - desc = "A small straight-through automatic gearbox" .. blurb, - model = "models/engines/t5small.mdl", - category = "Automatic", - weight = math.floor(Gear5SW * StWB), - switch = ShiftS, - maxtq = math.floor(Gear5ST * StTB), - auto = true, - gears = 5, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("5Gear-A-ST-M", { - name = "5-Speed Auto, Straight, Medium", - desc = "A medium sized, 5 speed automatic straight-through gearbox." .. blurb, - model = "models/engines/t5med.mdl", - category = "Automatic", - weight = math.floor(Gear5MW * StWB), - switch = ShiftM, - maxtq = math.floor(Gear5MT * StTB), - auto = true, - gears = 5, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("5Gear-A-ST-L", { - name = "5-Speed Auto, Straight, Large", - desc = "A large sized, 5 speed automatic straight-through gearbox." .. blurb, - model = "models/engines/t5large.mdl", - category = "Automatic", - weight = math.floor(Gear5LW * StWB), - switch = ShiftL, - maxtq = math.floor(Gear5LT * StTB), - auto = true, - gears = 5, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [-2] = -0.1, - [-1] = 0.5 - } -}) - --- 7 Speed --- Inline -ACF_DefineGearbox("7Gear-A-L-S", { - name = "7-Speed Auto, Inline, Small", - desc = "A small, and light 7 speed automatic inline gearbox, with a somewhat limited max torque rating" .. blurb, - model = "models/engines/linear_s.mdl", - category = "Automatic", - weight = Gear7SW, - switch = ShiftS, - maxtq = Gear7ST, - auto = true, - gears = 7, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [6] = 0.6, - [7] = 0.7, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("7Gear-A-L-M", { - name = "7-Speed Auto, Inline, Medium", - desc = "A medium sized, 7 speed automatic inline gearbox" .. blurb, - model = "models/engines/linear_m.mdl", - category = "Automatic", - weight = Gear7MW, - switch = ShiftM, - maxtq = Gear7MT, - auto = true, - gears = 7, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [6] = 0.6, - [7] = 0.7, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("7Gear-A-L-L", { - name = "7-Speed Auto, Inline, Large", - desc = "A large, heavy and sturdy 7 speed inline gearbox" .. blurb, - model = "models/engines/linear_l.mdl", - category = "Automatic", - weight = Gear7LW, - switch = ShiftL, - maxtq = Gear7LT, - auto = true, - gears = 7, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [6] = 0.6, - [7] = 0.7, - [-2] = -0.1, - [-1] = 0.5 - } -}) - --- Inline Dual Clutch -ACF_DefineGearbox("7Gear-A-LD-S", { - name = "7-Speed Auto, Inline, Small, Dual Clutch", - desc = "A small, and light 7 speed automatic inline gearbox, with a somewhat limited max torque rating" .. blurb, - model = "models/engines/linear_s.mdl", - category = "Automatic", - weight = Gear7SW, - switch = ShiftS, - maxtq = Gear7ST, - auto = true, - doubleclutch = true, - gears = 7, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [6] = 0.6, - [7] = 0.7, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("7Gear-A-LD-M", { - name = "7-Speed Auto, Inline, Medium, Dual Clutch", - desc = "A medium sized, 7 speed automatic inline gearbox" .. blurb, - model = "models/engines/linear_m.mdl", - category = "Automatic", - weight = Gear7MW, - switch = ShiftM, - maxtq = Gear7MT, - auto = true, - doubleclutch = true, - gears = 7, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [6] = 0.6, - [7] = 0.7, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("7Gear-A-LD-L", { - name = "7-Speed Auto, Inline, Large, Dual Clutch", - desc = "A large, heavy and sturdy 7 speed automatic inline gearbox" .. blurb, - model = "models/engines/linear_l.mdl", - category = "Automatic", - weight = Gear7LW, - switch = ShiftL, - maxtq = Gear7LT, - auto = true, - doubleclutch = true, - gears = 7, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [6] = 0.6, - [7] = 0.7, - [-2] = -0.1, - [-1] = 0.5 - } -}) - --- Transaxial -ACF_DefineGearbox("7Gear-A-T-S", { - name = "7-Speed Auto, Transaxial, Small", - desc = "A small, and light 7 speed automatic gearbox, with a somewhat limited max torque rating" .. blurb, - model = "models/engines/transaxial_s.mdl", - category = "Automatic", - weight = Gear7SW, - switch = ShiftS, - maxtq = Gear7ST, - auto = true, - gears = 7, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [6] = 0.6, - [7] = 0.7, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("7Gear-A-T-M", { - name = "7-Speed Auto, Transaxial, Medium", - desc = "A medium sized, 7 speed automatic gearbox" .. blurb, - model = "models/engines/transaxial_m.mdl", - category = "Automatic", - weight = Gear7MW, - switch = ShiftM, - maxtq = Gear7MT, - auto = true, - gears = 7, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [6] = 0.6, - [7] = 0.7, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("7Gear-A-T-L", { - name = "7-Speed Auto, Transaxial, Large", - desc = "A large, heavy and sturdy 7 speed automatic gearbox" .. blurb, - model = "models/engines/transaxial_l.mdl", - category = "Automatic", - weight = Gear7LW, - switch = ShiftL, - maxtq = Gear7LT, - auto = true, - gears = 7, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [6] = 0.6, - [7] = 0.7, - [-2] = -0.1, - [-1] = 0.5 - } -}) - --- Transaxial Dual Clutch -ACF_DefineGearbox("7Gear-A-TD-S", { - name = "7-Speed Auto, Transaxial, Small, Dual Clutch", - desc = "A small, and light 7 speed automatic gearbox, with a somewhat limited max torque rating" .. blurb, - model = "models/engines/transaxial_s.mdl", - category = "Automatic", - weight = Gear7SW, - switch = ShiftS, - maxtq = Gear7ST, - auto = true, - doubleclutch = true, - gears = 7, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [6] = 0.6, - [7] = 0.7, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("7Gear-A-TD-M", { - name = "7-Speed Auto, Transaxial, Medium, Dual Clutch", - desc = "A medium sized, 7 speed automatic gearbox" .. blurb, - model = "models/engines/transaxial_m.mdl", - category = "Automatic", - weight = Gear7MW, - switch = ShiftM, - maxtq = Gear7MT, - auto = true, - doubleclutch = true, - gears = 7, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [6] = 0.6, - [7] = 0.7, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("7Gear-A-TD-L", { - name = "7-Speed Auto, Transaxial, Large, Dual Clutch", - desc = "A large, heavy and sturdy 7 speed automatic gearbox" .. blurb, - model = "models/engines/transaxial_l.mdl", - category = "Automatic", - weight = Gear7LW, - switch = ShiftL, - maxtq = Gear7LT, - auto = true, - doubleclutch = true, - gears = 7, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [6] = 0.6, - [7] = 0.7, - [-2] = -0.1, - [-1] = 0.5 - } -}) - --- Straight-through gearboxes -ACF_DefineGearbox("7Gear-A-ST-S", { - name = "7-Speed Auto, Straight, Small", - desc = "A small straight-through automatic gearbox" .. blurb, - model = "models/engines/t5small.mdl", - category = "Automatic", - weight = math.floor(Gear7SW * StWB), - switch = ShiftS, - maxtq = math.floor(Gear7ST * StTB), - auto = true, - gears = 7, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [6] = 0.6, - [7] = 0.7, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("7Gear-A-ST-M", { - name = "7-Speed Auto, Straight, Medium", - desc = "A medium sized, 7 speed automatic straight-through gearbox." .. blurb, - model = "models/engines/t5med.mdl", - category = "Automatic", - weight = math.floor(Gear7MW * StWB), - switch = ShiftM, - maxtq = math.floor(Gear7MT * StTB), - auto = true, - gears = 7, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [6] = 0.6, - [7] = 0.7, - [-2] = -0.1, - [-1] = 0.5 - } -}) - -ACF_DefineGearbox("7Gear-A-ST-L", { - name = "7-Speed Auto, Straight, Large", - desc = "A large sized, 7 speed automatic straight-through gearbox." .. blurb, - model = "models/engines/t5large.mdl", - category = "Automatic", - weight = math.floor(Gear7LW * StWB), - switch = ShiftL, - maxtq = math.floor(Gear7LT * StTB), - auto = true, - gears = 7, - geartable = { - [0] = 0, - [1] = 0.1, - [2] = 0.2, - [3] = 0.3, - [4] = 0.4, - [5] = 0.5, - [6] = 0.6, - [7] = 0.7, - [-2] = -0.1, - [-1] = 0.5 - } -}) \ No newline at end of file diff --git a/lua/acf/shared/gearboxes/clutch.lua b/lua/acf/shared/gearboxes/clutch.lua index 8425fa2d8..01f41059b 100644 --- a/lua/acf/shared/gearboxes/clutch.lua +++ b/lua/acf/shared/gearboxes/clutch.lua @@ -16,72 +16,70 @@ local CLT = 8000 -- general description local CDesc = "A standalone clutch for when a full size gearbox is unnecessary or too long." --- Straight-through - -ACF_DefineGearbox( "Clutch-S-T", { - name = "Clutch, Straight, Tiny", - desc = CDesc, - model = "models/engines/flywheelclutcht.mdl", - category = "Clutch", - weight = CTW, - parentable = true, - switch = 0.1, - maxtq = CTT, - gears = 0, - geartable = { - [ 0 ] = 1, - [ 1 ] = 1, - [ -1 ] = 1 +ACF.RegisterGearboxClass("Clutch", { + Name = "Clutch", + CreateMenu = ACF.ManualGearboxMenu, + Gears = { + Min = 0, + Max = 1, } -} ) +}) -ACF_DefineGearbox( "Clutch-S-S", { - name = "Clutch, Straight, Small", - desc = CDesc, - model = "models/engines/flywheelclutchs.mdl", - category = "Clutch", - weight = CSW, - parentable = true, - switch = 0.15, - maxtq = CST, - gears = 0, - geartable = { - [ 0 ] = 1, - [ 1 ] = 1, - [ -1 ] = 1 - } -} ) +do -- Straight-through Gearboxes + ACF.RegisterGearbox("Clutch-S-T", "Clutch", { + Name = "Clutch, Straight, Tiny", + Description = CDesc, + Model = "models/engines/flywheelclutcht.mdl", + Mass = CTW, + Switch = 0.1, + MaxTorque = CTT, + }) -ACF_DefineGearbox( "Clutch-S-M", { - name = "Clutch, Straight, Medium", - desc = CDesc, - model = "models/engines/flywheelclutchm.mdl", - category = "Clutch", - weight = CMW, - parentable = true, - switch = 0.2, - maxtq = CMT, - gears = 0, - geartable = { - [ 0 ] = 1, - [ 1 ] = 1, - [ -1 ] = 1 - } -} ) + ACF.RegisterGearbox("Clutch-S-S", "Clutch", { + Name = "Clutch, Straight, Small", + Description = CDesc, + Model = "models/engines/flywheelclutchs.mdl", + Mass = CSW, + Switch = 0.15, + MaxTorque = CST, + }) -ACF_DefineGearbox( "Clutch-S-L", { - name = "Clutch, Straight, Large", - desc = CDesc, - model = "models/engines/flywheelclutchb.mdl", - category = "Clutch", - weight = CLW, - parentable = true, - switch = 0.3, - maxtq = CLT, - gears = 0, - geartable = { - [ 0 ] = 1, - [ 1 ] = 1, - [ -1 ] = 1 - } -} ) + ACF.RegisterGearbox("Clutch-S-M", "Clutch", { + Name = "Clutch, Straight, Medium", + Description = CDesc, + Model = "models/engines/flywheelclutchm.mdl", + Mass = CMW, + Switch = 0.2, + MaxTorque = CMT, + }) + + ACF.RegisterGearbox("Clutch-S-L", "Clutch", { + Name = "Clutch, Straight, Large", + Description = CDesc, + Model = "models/engines/flywheelclutchb.mdl", + Mass = CLW, + Switch = 0.3, + MaxTorque = CLT, + }) +end + +ACF.SetCustomAttachments("models/engines/flywheelclutchb.mdl", { + { Name = "input", Pos = Vector(), Ang = Angle(0, 0, 90) }, + { Name = "driveshaftR", Pos = Vector(0, 6), Ang = Angle(0, 180, 90) }, + { Name = "driveshaftL", Pos = Vector(0, 6), Ang = Angle(0, 180, 90) }, +}) +ACF.SetCustomAttachments("models/engines/flywheelclutchm.mdl", { + { Name = "input", Pos = Vector(), Ang = Angle(0, 0, 90) }, + { Name = "driveshaftR", Pos = Vector(0, 4), Ang = Angle(0, 180, 90) }, + { Name = "driveshaftL", Pos = Vector(0, 4), Ang = Angle(0, 180, 90) }, +}) +ACF.SetCustomAttachments("models/engines/flywheelclutchs.mdl", { + { Name = "input", Pos = Vector(), Ang = Angle(0, 0, 90) }, + { Name = "driveshaftR", Pos = Vector(0, 3), Ang = Angle(0, 180, 90) }, + { Name = "driveshaftL", Pos = Vector(0, 3), Ang = Angle(0, 180, 90) }, +}) +ACF.SetCustomAttachments("models/engines/flywheelclutcht.mdl", { + { Name = "input", Pos = Vector(), Ang = Angle(0, 0, 90) }, + { Name = "driveshaftR", Pos = Vector(0, 2), Ang = Angle(0, 180, 90) }, + { Name = "driveshaftL", Pos = Vector(0, 2), Ang = Angle(0, 180, 90) }, +}) diff --git a/lua/acf/shared/gearboxes/cvt.lua b/lua/acf/shared/gearboxes/cvt.lua index 911f32632..626c50329 100644 --- a/lua/acf/shared/gearboxes/cvt.lua +++ b/lua/acf/shared/gearboxes/cvt.lua @@ -9,316 +9,233 @@ local GearCVTST = 175 local GearCVTMT = 650 local GearCVTLT = 6000 local StTB = 1.25 --straight torque bonus multiplier --- general description -local CVTDesc = "\n\nA CVT will adjust the ratio its first gear to keep an engine within a target rpm range, allowing constant peak performance. However, this comes at the cost of increased weight and limited torque ratings." --- Inline -ACF_DefineGearbox("CVT-L-S", { - name = "CVT, Inline, Small", - desc = "A light duty inline CVT." .. CVTDesc, - model = "models/engines/linear_s.mdl", - category = "CVT", - weight = GearCVTSW, - switch = 0.15, - maxtq = GearCVTST, - gears = 2, - cvt = true, - geartable = { - [-3] = 3000, --target min rpm - [-2] = 5000, --target max rpm - [-1] = 1, --final drive - [0] = 0, - [1] = 0, - [2] = -0.1 - } -}) +local function InitGearbox(Gearbox) + local Gears = Gearbox.Gears -ACF_DefineGearbox("CVT-L-M", { - name = "CVT, Inline, Medium", - desc = "A medium inline CVT." .. CVTDesc, - model = "models/engines/linear_m.mdl", - category = "CVT", - weight = GearCVTMW, - switch = 0.2, - maxtq = GearCVTMT, - gears = 2, - cvt = true, - geartable = { - [-3] = 3000, --target min rpm - [-2] = 5000, --target max rpm - [-1] = 1, --final drive - [0] = 0, - [1] = 0, - [2] = -0.1 - } -}) + Gearbox.CVT = true + Gearbox.CVTRatio = 0 -ACF_DefineGearbox("CVT-L-L", { - name = "CVT, Inline, Large", - desc = "A massive inline CVT designed for high torque applications." .. CVTDesc, - model = "models/engines/linear_l.mdl", - category = "CVT", - weight = GearCVTLW, - switch = 0.3, - maxtq = GearCVTLT, - gears = 2, - cvt = true, - geartable = { - [-3] = 3000, --target min rpm - [-2] = 5000, --target max rpm - [-1] = 1, --final drive - [0] = 0, - [1] = 0, - [2] = -0.1 - } -}) + WireLib.TriggerOutput(Gearbox, "Min Target RPM", Gears.MinRPM) + WireLib.TriggerOutput(Gearbox, "Max Target RPM", Gears.MaxRPM) +end --- Inline Dual Clutch -ACF_DefineGearbox("CVT-LD-S", { - name = "CVT, Inline, Small, Dual Clutch", - desc = "A light duty inline CVT. The dual clutch allows you to apply power and brake each side independently." .. CVTDesc, - model = "models/engines/linear_s.mdl", - category = "CVT", - weight = GearCVTSW, - switch = 0.15, - maxtq = GearCVTST, - gears = 2, - doubleclutch = true, - cvt = true, - geartable = { - [-3] = 3000, --target min rpm - [-2] = 5000, --target max rpm - [-1] = 1, --final drive - [0] = 0, - [1] = 0, - [2] = -0.1 - } -}) +ACF.RegisterGearboxClass("CVT", { + Name = "CVT", + CreateMenu = ACF.CVTGearboxMenu, + Gears = { + Min = 0, + Max = 2, + }, + OnSpawn = InitGearbox, + OnUpdate = InitGearbox, + VerifyData = function(Data) + local Min, Max = Data.MinRPM, Data.MaxRPM -ACF_DefineGearbox("CVT-LD-M", { - name = "CVT, Inline, Medium, Dual Clutch", - desc = "A medium inline CVT. The dual clutch allows you to apply power and brake each side independently." .. CVTDesc, - model = "models/engines/linear_m.mdl", - category = "CVT", - weight = GearCVTMW, - switch = 0.2, - maxtq = GearCVTMT, - gears = 2, - doubleclutch = true, - cvt = true, - geartable = { - [-3] = 3000, --target min rpm - [-2] = 5000, --target max rpm - [-1] = 1, --final drive - [0] = 0, - [1] = 0, - [2] = -0.1 - } -}) + Data.Gears[1] = 0.01 -ACF_DefineGearbox("CVT-LD-L", { - name = "CVT, Inline, Large, Dual Clutch", - desc = "A massive inline CVT designed for high torque applications. The dual clutch allows you to apply power and brake each side independently." .. CVTDesc, - model = "models/engines/linear_l.mdl", - category = "CVT", - weight = GearCVTLW, - switch = 0.3, - maxtq = GearCVTLT, - gears = 2, - doubleclutch = true, - cvt = true, - geartable = { - [-3] = 3000, --target min rpm - [-2] = 5000, --target max rpm - [-1] = 1, --final drive - [0] = 0, - [1] = 0, - [2] = -0.1 - } -}) + if not Min then + Min = ACF.CheckNumber(Data.Gear3, 3000) --- Transaxial -ACF_DefineGearbox("CVT-T-S", { - name = "CVT, Transaxial, Small", - desc = "A light duty CVT." .. CVTDesc, - model = "models/engines/transaxial_s.mdl", - category = "CVT", - weight = GearCVTSW, - switch = 0.15, - maxtq = GearCVTST, - gears = 2, - cvt = true, - geartable = { - [-3] = 3000, --target min rpm - [-2] = 5000, --target max rpm - [-1] = 1, --final drive - [0] = 0, - [1] = 0, - [2] = -0.1 - } -}) + Data.Gear3 = nil + end -ACF_DefineGearbox("CVT-T-M", { - name = "CVT, Transaxial, Medium", - desc = "A medium CVT." .. CVTDesc, - model = "models/engines/transaxial_m.mdl", - category = "CVT", - weight = GearCVTMW, - switch = 0.2, - maxtq = GearCVTMT, - gears = 2, - cvt = true, - geartable = { - [-3] = 3000, --target min rpm - [-2] = 5000, --target max rpm - [-1] = 1, --final drive - [0] = 0, - [1] = 0, - [2] = -0.1 - } -}) + if not Max then + Max = ACF.CheckNumber(Data.Gear4, 5000) -ACF_DefineGearbox("CVT-T-L", { - name = "CVT, Transaxial, Large", - desc = "A massive CVT designed for high torque applications." .. CVTDesc, - model = "models/engines/transaxial_l.mdl", - category = "CVT", - weight = GearCVTLW, - switch = 0.3, - maxtq = GearCVTLT, - gears = 2, - cvt = true, - geartable = { - [-3] = 3000, --target min rpm - [-2] = 5000, --target max rpm - [-1] = 1, --final drive - [0] = 0, - [1] = 0, - [2] = -0.1 - } -}) + Data.Gear4 = nil + end --- Transaxial Dual Clutch -ACF_DefineGearbox("CVT-TD-S", { - name = "CVT, Transaxial, Small, Dual Clutch", - desc = "A light duty CVT. The dual clutch allows you to apply power and brake each side independently." .. CVTDesc, - model = "models/engines/transaxial_s.mdl", - category = "CVT", - weight = GearCVTSW, - switch = 0.15, - maxtq = GearCVTST, - gears = 2, - doubleclutch = true, - cvt = true, - geartable = { - [-3] = 3000, --target min rpm - [-2] = 5000, --target max rpm - [-1] = 1, --final drive - [0] = 0, - [1] = 0, - [2] = -0.1 - } -}) + Data.MinRPM = math.Clamp(Min, 1, 9900) + Data.MaxRPM = math.Clamp(Max, Data.MinRPM + 100, 10000) + end, + SetupInputs = function(List) + List[#List + 1] = "CVT Ratio" + end, + SetupOutputs = function(List) + local Count = #List -ACF_DefineGearbox("CVT-TD-M", { - name = "CVT, Transaxial, Medium, Dual Clutch", - desc = "A medium CVT. The dual clutch allows you to apply power and brake each side independently." .. CVTDesc, - model = "models/engines/transaxial_m.mdl", - category = "CVT", - weight = GearCVTMW, - switch = 0.2, - maxtq = GearCVTMT, - gears = 2, - doubleclutch = true, - cvt = true, - geartable = { - [-3] = 3000, --target min rpm - [-2] = 5000, --target max rpm - [-1] = 1, --final drive - [0] = 0, - [1] = 0, - [2] = -0.1 - } -}) + List[Count + 1] = "Min Target RPM" + List[Count + 2] = "Max Target RPM" + end, + OnLast = function(Gearbox) + Gearbox.CVT = nil + Gearbox.CVTRatio = nil + end, + GetGearsText = function(Gearbox) + local Text = "Reverse Gear: %s\nTarget: %s - %s RPM" + local Gears = Gearbox.Gears + local Reverse = math.Round(Gears[2], 2) + local Min = math.Round(Gearbox.MinRPM) + local Max = math.Round(Gearbox.MaxRPM) -ACF_DefineGearbox("CVT-TD-L", { - name = "CVT, Transaxial, Large, Dual Clutch", - desc = "A massive CVT designed for high torque applications. The dual clutch allows you to apply power and brake each side independently." .. CVTDesc, - model = "models/engines/transaxial_l.mdl", - category = "CVT", - weight = GearCVTLW, - switch = 0.3, - maxtq = GearCVTLT, - gears = 2, - doubleclutch = true, - cvt = true, - geartable = { - [-3] = 3000, --target min rpm - [-2] = 5000, --target max rpm - [-1] = 1, --final drive - [0] = 0, - [1] = 0, - [2] = -0.1 - } + return Text:format(Reverse, Min, Max) + end, }) --- Straight-through gearboxes -ACF_DefineGearbox("CVT-ST-S", { - name = "CVT, Straight, Small", - desc = "A light duty straight-through CVT." .. CVTDesc, - model = "models/engines/t5small.mdl", - category = "CVT", - weight = math.floor(GearCVTSW * StWB), - switch = 0.15, - maxtq = math.floor(GearCVTST * StTB), - gears = 2, - cvt = true, - geartable = { - [-3] = 3000, --target min rpm - [-2] = 5000, --target max rpm - [-1] = 1, --final drive - [0] = 0, - [1] = 0, - [2] = -0.1 - } -}) +do -- Inline Gearboxes + ACF.RegisterGearbox("CVT-L-S", "CVT", { + Name = "CVT, Inline, Small", + Description = "A light duty inline CVT.", + Model = "models/engines/linear_s.mdl", + Mass = GearCVTSW, + Switch = 0.15, + MaxTorque = GearCVTST, + }) -ACF_DefineGearbox("CVT-ST-M", { - name = "CVT, Straight, Medium", - desc = "A medium straight-through CVT." .. CVTDesc, - model = "models/engines/t5med.mdl", - category = "CVT", - weight = math.floor(GearCVTMW * StWB), - switch = 0.2, - maxtq = math.floor(GearCVTMT * StTB), - gears = 2, - cvt = true, - geartable = { - [-3] = 3000, --target min rpm - [-2] = 5000, --target max rpm - [-1] = 1, --final drive - [0] = 0, - [1] = 0, - [2] = -0.1 - } -}) + ACF.RegisterGearbox("CVT-L-M", "CVT", { + Name = "CVT, Inline, Medium", + Description = "A medium inline CVT.", + Model = "models/engines/linear_m.mdl", + Mass = GearCVTMW, + Switch = 0.2, + MaxTorque = GearCVTMT, + }) + + ACF.RegisterGearbox("CVT-L-L", "CVT", { + Name = "CVT, Inline, Large", + Description = "A massive inline CVT designed for high torque applications.", + Model = "models/engines/linear_l.mdl", + Mass = GearCVTLW, + Switch = 0.3, + MaxTorque = GearCVTLT, + }) +end + +do -- Inline Dual Clutch Gearboxes + ACF.RegisterGearbox("CVT-LD-S", "CVT", { + Name = "CVT, Inline, Small, Dual Clutch", + Description = "A light duty inline CVT. The dual clutch allows you to apply power and brake each side independently.", + Model = "models/engines/linear_s.mdl", + Mass = GearCVTSW, + Switch = 0.15, + MaxTorque = GearCVTST, + DualClutch = true, + }) -ACF_DefineGearbox("CVT-ST-L", { - name = "CVT, Straight, Large", - desc = "A massive straight-through CVT designed for high torque applications." .. CVTDesc, - model = "models/engines/t5large.mdl", - category = "CVT", - weight = math.floor(GearCVTLW * StWB), - switch = 0.3, - maxtq = math.floor(GearCVTLT * StTB), - gears = 2, - cvt = true, - geartable = { - [-3] = 3000, --target min rpm - [-2] = 5000, --target max rpm - [-1] = 1, --final drive - [0] = 0, - [1] = 0, - [2] = -0.1 - } -}) \ No newline at end of file + ACF.RegisterGearbox("CVT-LD-M", "CVT", { + Name = "CVT, Inline, Medium, Dual Clutch", + Description = "A medium inline CVT. The dual clutch allows you to apply power and brake each side independently.", + Model = "models/engines/linear_m.mdl", + Mass = GearCVTMW, + Switch = 0.2, + MaxTorque = GearCVTMT, + DualClutch = true, + }) + + ACF.RegisterGearbox("CVT-LD-L", "CVT", { + Name = "CVT, Inline, Large, Dual Clutch", + Description = "A massive inline CVT designed for high torque applications. The dual clutch allows you to apply power and brake each side independently.", + Model = "models/engines/linear_l.mdl", + Mass = GearCVTLW, + Switch = 0.3, + MaxTorque = GearCVTLT, + DualClutch = true, + }) +end + +do -- Transaxial Gearboxes + ACF.RegisterGearbox("CVT-T-S", "CVT", { + Name = "CVT, Transaxial, Small", + Description = "A light duty CVT.", + Model = "models/engines/transaxial_s.mdl", + Mass = GearCVTSW, + Switch = 0.15, + MaxTorque = GearCVTST, + }) + + ACF.RegisterGearbox("CVT-T-M", "CVT", { + Name = "CVT, Transaxial, Medium", + Description = "A medium CVT.", + Model = "models/engines/transaxial_m.mdl", + Mass = GearCVTMW, + Switch = 0.2, + MaxTorque = GearCVTMT, + }) + + ACF.RegisterGearbox("CVT-T-L", "CVT", { + Name = "CVT, Transaxial, Large", + Description = "A massive CVT designed for high torque applications.", + Model = "models/engines/transaxial_l.mdl", + Mass = GearCVTLW, + Switch = 0.3, + MaxTorque = GearCVTLT, + }) +end + +do -- Transaxial Dual Clutch Gearboxes + ACF.RegisterGearbox("CVT-TD-S", "CVT", { + Name = "CVT, Transaxial, Small, Dual Clutch", + Description = "A light duty CVT. The dual clutch allows you to apply power and brake each side independently.", + Model = "models/engines/transaxial_s.mdl", + Mass = GearCVTSW, + Switch = 0.15, + MaxTorque = GearCVTST, + DualClutch = true, + }) + + ACF.RegisterGearbox("CVT-TD-M", "CVT", { + Name = "CVT, Transaxial, Medium, Dual Clutch", + Description = "A medium CVT. The dual clutch allows you to apply power and brake each side independently.", + Model = "models/engines/transaxial_m.mdl", + Mass = GearCVTMW, + Switch = 0.2, + MaxTorque = GearCVTMT, + DualClutch = true, + }) + + ACF.RegisterGearbox("CVT-TD-L", "CVT", { + Name = "CVT, Transaxial, Large, Dual Clutch", + Description = "A massive CVT designed for high torque applications. The dual clutch allows you to apply power and brake each side independently.", + Model = "models/engines/transaxial_l.mdl", + Mass = GearCVTLW, + Switch = 0.3, + MaxTorque = GearCVTLT, + DualClutch = true, + }) +end + +do -- Straight-through Gearboxes + ACF.RegisterGearbox("CVT-ST-S", "CVT", { + Name = "CVT, Straight, Small", + Description = "A light duty straight-through CVT.", + Model = "models/engines/t5small.mdl", + Mass = math.floor(GearCVTSW * StWB), + Switch = 0.15, + MaxTorque = math.floor(GearCVTST * StTB), + }) + + ACF.RegisterGearbox("CVT-ST-M", "CVT", { + Name = "CVT, Straight, Medium", + Description = "A medium straight-through CVT.", + Model = "models/engines/t5med.mdl", + Mass = math.floor(GearCVTMW * StWB), + Switch = 0.2, + MaxTorque = math.floor(GearCVTMT * StTB), + }) + + ACF.RegisterGearbox("CVT-ST-L", "CVT", { + Name = "CVT, Straight, Large", + Description = "A massive straight-through CVT designed for high torque applications.", + Model = "models/engines/t5large.mdl", + Mass = math.floor(GearCVTLW * StWB), + Switch = 0.3, + MaxTorque = math.floor(GearCVTLT * StTB), + }) +end + +ACF.SetCustomAttachments("models/engines/t5large.mdl", { + { Name = "input", Pos = Vector(), Ang = Angle(0, 0, 90) }, + { Name = "driveshaftR", Pos = Vector(0, 30), Ang = Angle(0, -180, 90) }, + { Name = "driveshaftL", Pos = Vector(0, 30), Ang = Angle(0, -180, 90) }, +}) +ACF.SetCustomAttachments("models/engines/t5med.mdl", { + { Name = "input", Pos = Vector(), Ang = Angle(0, 0, 90) }, + { Name = "driveshaftR", Pos = Vector(0, 25), Ang = Angle(0, -180, 90) }, + { Name = "driveshaftL", Pos = Vector(0, 25), Ang = Angle(0, -180, 90) }, +}) +ACF.SetCustomAttachments("models/engines/t5small.mdl", { + { Name = "input", Pos = Vector(), Ang = Angle(0, 0, 90) }, + { Name = "driveshaftR", Pos = Vector(0, 20), Ang = Angle(0, -180, 90) }, + { Name = "driveshaftL", Pos = Vector(0, 20), Ang = Angle(0, -180, 90) }, +}) diff --git a/lua/acf/shared/gearboxes/differential.lua b/lua/acf/shared/gearboxes/differential.lua index 2ca8e6fff..f5c575dd2 100644 --- a/lua/acf/shared/gearboxes/differential.lua +++ b/lua/acf/shared/gearboxes/differential.lua @@ -5,220 +5,164 @@ local Gear1SW = 10 local Gear1MW = 20 local Gear1LW = 40 --- Inline - -ACF_DefineGearbox( "1Gear-L-S", { - name = "Differential, Inline, Small", - desc = "Small differential, used to connect power from gearbox to wheels", - model = "models/engines/linear_s.mdl", - category = "Differential", - weight = Gear1SW, - parentable = true, - switch = 0.3, - maxtq = 25000, - gears = 1, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.5, - [ -1 ] = 0.5 +ACF.RegisterGearboxClass("Differential", { + Name = "Differential", + CreateMenu = ACF.ManualGearboxMenu, + Gears = { + Min = 0, + Max = 1, } -} ) - -ACF_DefineGearbox( "1Gear-L-M", { - name = "Differential, Inline, Medium", - desc = "Medium duty differential", - model = "models/engines/linear_m.mdl", - category = "Differential", - weight = Gear1MW, - parentable = true, - switch = 0.4, - maxtq = 50000, - gears = 1, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.5, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "1Gear-L-L", { - name = "Differential, Inline, Large", - desc = "Heavy duty differential, for the heaviest of engines", - model = "models/engines/linear_l.mdl", - category = "Differential", - weight = Gear1LW, - parentable = true, - switch = 0.6, - maxtq = 100000, - gears = 1, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.5, - [ -1 ] = 1 - } -} ) - --- Inline Dual Clutch - -ACF_DefineGearbox( "1Gear-LD-S", { - name = "Differential, Inline, Small, Dual Clutch", - desc = "Small differential, used to connect power from gearbox to wheels", - model = "models/engines/linear_s.mdl", - category = "Differential", - weight = Gear1SW, - parentable = true, - switch = 0.3, - maxtq = 25000, - gears = 1, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.5, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "1Gear-LD-M", { - name = "Differential, Inline, Medium, Dual Clutch", - desc = "Medium duty differential", - model = "models/engines/linear_m.mdl", - category = "Differential", - weight = Gear1MW, - parentable = true, - switch = 0.4, - maxtq = 50000, - gears = 1, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.5, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "1Gear-LD-L", { - name = "Differential, Inline, Large, Dual Clutch", - desc = "Heavy duty differential, for the heaviest of engines", - model = "models/engines/linear_l.mdl", - category = "Differential", - weight = Gear1LW, - parentable = true, - switch = 0.6, - maxtq = 100000, - gears = 1, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.5, - [ -1 ] = 1 - } -} ) - --- Transaxial - -ACF_DefineGearbox( "1Gear-T-S", { - name = "Differential, Small", - desc = "Small differential, used to connect power from gearbox to wheels", - model = "models/engines/transaxial_s.mdl", - category = "Differential", - weight = Gear1SW, - parentable = true, - switch = 0.3, - maxtq = 25000, - gears = 1, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.5, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "1Gear-T-M", { - name = "Differential, Medium", - desc = "Medium duty differential", - model = "models/engines/transaxial_m.mdl", - category = "Differential", - weight = Gear1MW, - parentable = true, - switch = 0.4, - maxtq = 50000, - gears = 1, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.5, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "1Gear-T-L", { - name = "Differential, Large", - desc = "Heavy duty differential, for the heaviest of engines", - model = "models/engines/transaxial_l.mdl", - category = "Differential", - parentable = true, - weight = Gear1LW, - switch = 0.6, - maxtq = 100000, - gears = 1, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.5, - [ -1 ] = 1 - } -} ) - --- Transaxial Dual Clutch - -ACF_DefineGearbox( "1Gear-TD-S", { - name = "Differential, Small, Dual Clutch", - desc = "Small differential, used to connect power from gearbox to wheels", - model = "models/engines/transaxial_s.mdl", - category = "Differential", - weight = Gear1SW, - parentable = true, - switch = 0.3, - maxtq = 25000, - gears = 1, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.5, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "1Gear-TD-M", { - name = "Differential, Medium, Dual Clutch", - desc = "Medium duty differential", - model = "models/engines/transaxial_m.mdl", - category = "Differential", - weight = Gear1MW, - parentable = true, - switch = 0.4, - maxtq = 50000, - gears = 1, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.5, - [ -1 ] = 0.5 - } -} ) - -ACF_DefineGearbox( "1Gear-TD-L", { - name = "Differential, Large, Dual Clutch", - desc = "Heavy duty differential, for the heaviest of engines", - model = "models/engines/transaxial_l.mdl", - category = "Differential", - weight = Gear1LW, - parentable = true, - switch = 0.6, - maxtq = 100000, - gears = 1, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.5, - [ -1 ] = 1 - } -} ) +}) + +do -- Inline Gearboxes + ACF.RegisterGearbox("1Gear-L-S", "Differential", { + Name = "Differential, Inline, Small", + Description = "Small differential, used to connect power from gearbox to wheels", + Model = "models/engines/linear_s.mdl", + Mass = Gear1SW, + Switch = 0.3, + MaxTorque = 25000, + }) + + ACF.RegisterGearbox("1Gear-L-M", "Differential", { + Name = "Differential, Inline, Medium", + Description = "Medium duty differential", + Model = "models/engines/linear_m.mdl", + Mass = Gear1MW, + Switch = 0.4, + MaxTorque = 50000, + }) + + ACF.RegisterGearbox("1Gear-L-L", "Differential", { + Name = "Differential, Inline, Large", + Description = "Heavy duty differential, for the heaviest of engines", + Model = "models/engines/linear_l.mdl", + Mass = Gear1LW, + Switch = 0.6, + MaxTorque = 100000, + }) +end + +do -- Inline Dual Clutch Gearboxes + ACF.RegisterGearbox("1Gear-LD-S", "Differential", { + Name = "Differential, Inline, Small, Dual Clutch", + Description = "Small differential, used to connect power from gearbox to wheels", + Model = "models/engines/linear_s.mdl", + Mass = Gear1SW, + Switch = 0.3, + MaxTorque = 25000, + DualClutch = true, + }) + + ACF.RegisterGearbox("1Gear-LD-M", "Differential", { + Name = "Differential, Inline, Medium, Dual Clutch", + Description = "Medium duty differential", + Model = "models/engines/linear_m.mdl", + Mass = Gear1MW, + Switch = 0.4, + MaxTorque = 50000, + DualClutch = true, + }) + + ACF.RegisterGearbox("1Gear-LD-L", "Differential", { + Name = "Differential, Inline, Large, Dual Clutch", + Description = "Heavy duty differential, for the heaviest of engines", + Model = "models/engines/linear_l.mdl", + Mass = Gear1LW, + Switch = 0.6, + MaxTorque = 100000, + DualClutch = true, + }) +end + +do -- Transaxial Gearboxes + ACF.RegisterGearbox("1Gear-T-S", "Differential", { + Name = "Differential, Small", + Description = "Small differential, used to connect power from gearbox to wheels", + Model = "models/engines/transaxial_s.mdl", + Mass = Gear1SW, + Switch = 0.3, + MaxTorque = 25000, + }) + + ACF.RegisterGearbox("1Gear-T-M", "Differential", { + Name = "Differential, Medium", + Description = "Medium duty differential", + Model = "models/engines/transaxial_m.mdl", + Mass = Gear1MW, + Switch = 0.4, + MaxTorque = 50000, + }) + + ACF.RegisterGearbox("1Gear-T-L", "Differential", { + Name = "Differential, Large", + Description = "Heavy duty differential, for the heaviest of engines", + Model = "models/engines/transaxial_l.mdl", + Mass = Gear1LW, + Switch = 0.6, + MaxTorque = 100000, + }) +end + +do -- Transaxial Dual Clutch Gearboxes + ACF.RegisterGearbox("1Gear-TD-S", "Differential", { + Name = "Differential, Small, Dual Clutch", + Description = "Small differential, used to connect power from gearbox to wheels", + Model = "models/engines/transaxial_s.mdl", + Mass = Gear1SW, + Switch = 0.3, + MaxTorque = 25000, + DualClutch = true, + }) + + ACF.RegisterGearbox("1Gear-TD-M", "Differential", { + Name = "Differential, Medium, Dual Clutch", + Description = "Medium duty differential", + Model = "models/engines/transaxial_m.mdl", + Mass = Gear1MW, + Switch = 0.4, + MaxTorque = 50000, + DualClutch = true, + }) + + ACF.RegisterGearbox("1Gear-TD-L", "Differential", { + Name = "Differential, Large, Dual Clutch", + Description = "Heavy duty differential, for the heaviest of engines", + Model = "models/engines/transaxial_l.mdl", + Mass = Gear1LW, + Switch = 0.6, + MaxTorque = 100000, + DualClutch = true, + }) +end + +ACF.SetCustomAttachments("models/engines/transaxial_l.mdl", { + { Name = "driveshaftR", Pos = Vector(0, 20, 8), Ang = Angle(0, 90, 90) }, + { Name = "driveshaftL", Pos = Vector(0, -20, 8), Ang = Angle(0, -90, 90) }, + { Name = "input", Pos = Vector(20, 0, 8), Ang = Angle(0, 0, 90) }, +}) +ACF.SetCustomAttachments("models/engines/transaxial_m.mdl", { + { Name = "driveshaftR", Pos = Vector(0, 12, 4.8), Ang = Angle(0, 90, 90) }, + { Name = "driveshaftL", Pos = Vector(0, -12, 4.8), Ang = Angle(0, -90, 90) }, + { Name = "input", Pos = Vector(12, 0, 4.8), Ang = Angle(0, 0, 90) }, +}) +ACF.SetCustomAttachments("models/engines/transaxial_s.mdl", { + { Name = "driveshaftR", Pos = Vector(0, 8, 3.2), Ang = Angle(0, 90, 90) }, + { Name = "driveshaftL", Pos = Vector(0, -8, 3.2), Ang = Angle(0, -90, 90) }, + { Name = "input", Pos = Vector(8, 0, 3.2), Ang = Angle(0, 0, 90) }, +}) +ACF.SetCustomAttachments("models/engines/linear_l.mdl", { + { Name = "driveshaftR", Pos = Vector(0, 20, 8), Ang = Angle(0, 90, 90) }, + { Name = "driveshaftL", Pos = Vector(0, -24, 8), Ang = Angle(0, -90, 90) }, + { Name = "input", Pos = Vector(0, 4, 29), Ang = Angle(0, -90, 90) }, +}) +ACF.SetCustomAttachments("models/engines/linear_m.mdl", { + { Name = "driveshaftR", Pos = Vector(0, 12, 4.8), Ang = Angle(0, 90, 90) }, + { Name = "driveshaftL", Pos = Vector(0, -14.4, 4.8), Ang = Angle(0, -90, 90) }, + { Name = "input", Pos = Vector(0, 2.4, 17.4), Ang = Angle(0, -90, 90) }, +}) +ACF.SetCustomAttachments("models/engines/linear_s.mdl", { + { Name = "driveshaftR", Pos = Vector(0, 8, 3.2), Ang = Angle(0, 90, 90) }, + { Name = "driveshaftL", Pos = Vector(0, -9.6, 3.2), Ang = Angle(0, -90, 90) }, + { Name = "input", Pos = Vector(0, 1.6, 11.6), Ang = Angle(0, -90, 90) }, +}) diff --git a/lua/acf/shared/gearboxes/doublediff.lua b/lua/acf/shared/gearboxes/doublediff.lua index c398315cc..ab08e2bac 100644 --- a/lua/acf/shared/gearboxes/doublediff.lua +++ b/lua/acf/shared/gearboxes/doublediff.lua @@ -7,63 +7,62 @@ local GearDDLW = 180 local GearDDST = 20000 local GearDDMT = 45000 local GearDDLT = 100000 --- general description -local DDDesc = "\n\nA Double Differential transmission allows for a multitude of radii as well as a neutral steer." --- Inline -ACF_DefineGearbox("DoubleDiff-T-S", { - name = "Double Differential, Small", - desc = "A light duty regenerative steering transmission." .. DDDesc, - model = "models/engines/transaxial_s.mdl", - category = "Regenerative Steering", - weight = GearDDSW, - parentable = true, - switch = 0.2, - maxtq = GearDDST, - gears = 1, - doublediff = true, - doubleclutch = true, - geartable = { - [0] = 0, - [1] = 1, - [-1] = 1 - } -}) +local function InitGearbox(Gearbox) + Gearbox.DoubleDiff = true + Gearbox.SteerRate = 0 + + Gearbox:SetBodygroup(1, 1) +end + +ACF.RegisterGearboxClass("DoubleDiff", { + Name = "Double Differential", + CreateMenu = ACF.ManualGearboxMenu, + Gears = { + Min = 0, + Max = 1, + }, + OnSpawn = InitGearbox, + OnUpdate = InitGearbox, + SetupInputs = function(List) + List[#List + 1] = "Steer Rate" + end, + OnLast = function(Gearbox) + Gearbox.DoubleDiff = nil + Gearbox.SteerRate = nil -ACF_DefineGearbox("DoubleDiff-T-M", { - name = "Double Differential, Medium", - desc = "A medium regenerative steering transmission." .. DDDesc, - model = "models/engines/transaxial_m.mdl", - category = "Regenerative Steering", - weight = GearDDMW, - parentable = true, - switch = 0.35, - maxtq = GearDDMT, - gears = 1, - doublediff = true, - doubleclutch = true, - geartable = { - [0] = 0, - [1] = 1, - [-1] = 1 - } + Gearbox:SetBodygroup(1, 0) + end, }) -ACF_DefineGearbox("DoubleDiff-T-L", { - name = "Double Differential, Large", - desc = "A heavy regenerative steering transmission." .. DDDesc, - model = "models/engines/transaxial_l.mdl", - category = "Regenerative Steering", - weight = GearDDLW, - parentable = true, - switch = 0.5, - maxtq = GearDDLT, - gears = 1, - doublediff = true, - doubleclutch = true, - geartable = { - [0] = 0, - [1] = 1, - [-1] = 1 - } -}) \ No newline at end of file +do -- Transaxial Gearboxes + ACF.RegisterGearbox("DoubleDiff-T-S", "DoubleDiff", { + Name = "Double Differential, Small", + Description = "A light duty regenerative steering transmission.", + Model = "models/engines/transaxial_s.mdl", + Mass = GearDDSW, + Switch = 0.2, + MaxTorque = GearDDST, + DualClutch = true, + }) + + ACF.RegisterGearbox("DoubleDiff-T-M", "DoubleDiff", { + Name = "Double Differential, Medium", + Description = "A medium regenerative steering transmission.", + Model = "models/engines/transaxial_m.mdl", + Mass = GearDDMW, + Switch = 0.35, + MaxTorque = GearDDMT, + DualClutch = true, + }) + + ACF.RegisterGearbox("DoubleDiff-T-L", "DoubleDiff", { + Name = "Double Differential, Large", + Description = "A heavy regenerative steering transmission.", + Model = "models/engines/transaxial_l.mdl", + Mass = GearDDLW, + Switch = 0.5, + MaxTorque = GearDDLT, + DualClutch = true, + }) +end diff --git a/lua/acf/shared/gearboxes/transfer.lua b/lua/acf/shared/gearboxes/transfer.lua index 853d16875..578ddafc9 100644 --- a/lua/acf/shared/gearboxes/transfer.lua +++ b/lua/acf/shared/gearboxes/transfer.lua @@ -5,120 +5,75 @@ local Gear2SW = 20 local Gear2MW = 40 local Gear2LW = 80 --- Inline - -ACF_DefineGearbox( "2Gear-L-S", { - name = "Transfer case, Inline, Small", - desc = "2 speed gearbox, useful for low/high range and tank turning", - model = "models/engines/linear_s.mdl", - category = "Transfer", - weight = Gear2SW, - parentable = true, - switch = 0.3, - maxtq = 25000, - gears = 2, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.5, - [ 2 ] = -0.5, - [ -1 ] = 0.5 +ACF.RegisterGearboxClass("Transfer", { + Name = "Transfer Case", + CreateMenu = ACF.ManualGearboxMenu, + Gears = { + Min = 0, + Max = 2, } -} ) +}) -ACF_DefineGearbox( "2Gear-L-M", { - name = "Transfer case, Inline, Medium", - desc = "2 speed gearbox, useful for low/high range and tank turning", - model = "models/engines/linear_m.mdl", - category = "Transfer", - weight = Gear2MW, - parentable = true, - switch = 0.4, - maxtq = 50000, - gears = 2, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.5, - [ 2 ] = -0.5, - [ -1 ] = 0.5 - } -} ) +do -- Inline Gearboxes + ACF.RegisterGearbox("2Gear-L-S", "Transfer", { + Name = "Transfer case, Inline, Small", + Description = "2 speed gearbox, useful for low/high range and tank turning", + Model = "models/engines/linear_s.mdl", + Mass = Gear2SW, + Switch = 0.3, + MaxTorque = 25000, + DualClutch = true, + }) -ACF_DefineGearbox( "2Gear-L-L", { - name = "Transfer case, Inline, Large", - desc = "2 speed gearbox, useful for low/high range and tank turning", - model = "models/engines/linear_l.mdl", - category = "Transfer", - weight = Gear2LW, - parentable = true, - switch = 0.6, - maxtq = 100000, - gears = 2, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.5, - [ 2 ] = -0.5, - [ -1 ] = 1 - } -} ) + ACF.RegisterGearbox("2Gear-L-M", "Transfer", { + Name = "Transfer case, Inline, Medium", + Description = "2 speed gearbox, useful for low/high range and tank turning", + Model = "models/engines/linear_m.mdl", + Mass = Gear2MW, + Switch = 0.4, + MaxTorque = 50000, + DualClutch = true, + }) --- Transaxial + ACF.RegisterGearbox("2Gear-L-L", "Transfer", { + Name = "Transfer case, Inline, Large", + Description = "2 speed gearbox, useful for low/high range and tank turning", + Model = "models/engines/linear_l.mdl", + Mass = Gear2LW, + Switch = 0.6, + MaxTorque = 100000, + DualClutch = true, + }) +end -ACF_DefineGearbox( "2Gear-T-S", { - name = "Transfer case, Small", - desc = "2 speed gearbox, useful for low/high range and tank turning", - model = "models/engines/transaxial_s.mdl", - category = "Transfer", - weight = Gear2SW, - parentable = true, - switch = 0.3, - maxtq = 25000, - gears = 2, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.5, - [ 2 ] = -0.5, - [ -1 ] = 0.5 - } -} ) +do -- Transaxial Gearboxes + ACF.RegisterGearbox("2Gear-T-S", "Transfer", { + Name = "Transfer case, Small", + Description = "2 speed gearbox, useful for low/high range and tank turning", + Model = "models/engines/transaxial_s.mdl", + Mass = Gear2SW, + Switch = 0.3, + MaxTorque = 25000, + DualClutch = true, + }) -ACF_DefineGearbox( "2Gear-T-M", { - name = "Transfer case, Medium", - desc = "2 speed gearbox, useful for low/high range and tank turning", - model = "models/engines/transaxial_m.mdl", - category = "Transfer", - weight = Gear2MW, - parentable = true, - switch = 0.4, - maxtq = 50000, - gears = 2, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.5, - [ 2 ] = -0.5, - [ -1 ] = 0.5 - } -} ) + ACF.RegisterGearbox("2Gear-T-M", "Transfer", { + Name = "Transfer case, Medium", + Description = "2 speed gearbox, useful for low/high range and tank turning", + Model = "models/engines/transaxial_m.mdl", + Mass = Gear2MW, + Switch = 0.4, + MaxTorque = 50000, + DualClutch = true, + }) -ACF_DefineGearbox( "2Gear-T-L", { - name = "Transfer case, Large", - desc = "2 speed gearbox, useful for low/high range and tank turning", - model = "models/engines/transaxial_l.mdl", - category = "Transfer", - weight = Gear2LW, - parentable = true, - switch = 0.6, - maxtq = 100000, - gears = 2, - doubleclutch = true, - geartable = { - [ 0 ] = 0, - [ 1 ] = 0.5, - [ 2 ] = -0.5, - [ -1 ] = 1 - } -} ) + ACF.RegisterGearbox("2Gear-T-L", "Transfer", { + Name = "Transfer case, Large", + Description = "2 speed gearbox, useful for low/high range and tank turning", + Model = "models/engines/transaxial_l.mdl", + Mass = Gear2LW, + Switch = 0.6, + MaxTorque = 100000, + DualClutch = true, + }) +end diff --git a/lua/acf/shared/guns/autocannon.lua b/lua/acf/shared/guns/autocannon.lua deleted file mode 100644 index 8659bfca8..000000000 --- a/lua/acf/shared/guns/autocannon.lua +++ /dev/null @@ -1,85 +0,0 @@ ---define the class -ACF_defineGunClass("AC", { - spread = 0.2, - name = "Autocannon", - desc = "Autocannons have a rather high weight and bulk for the ammo they fire, but they can fire it extremely fast.", - muzzleflash = "auto_muzzleflash_noscale", - rofmod = 0.85, - sound = "acf_base/weapons/ac_fire4.mp3", - soundDistance = " ", - soundNormal = " " -}) - ---add a gun to the class ---id -ACF_defineGun("20mmAC", { - name = "20mm Autocannon", - desc = "The 20mm AC is the smallest of the family; having a good rate of fire but a tiny shell.", - model = "models/autocannon/autocannon_20mm.mdl", - caliber = 2.0, - gunclass = "AC", - weight = 500, - year = 1930, - rofmod = 0.7, - magsize = 100, - magreload = 15, - Cyclic = 250, - round = { - maxlength = 32, - propweight = 0.13 - } -}) - -ACF_defineGun("30mmAC", { - name = "30mm Autocannon", - desc = "The 30mm AC can fire shells with sufficient space for a small payload, and has modest anti-armor capability", - model = "models/autocannon/autocannon_30mm.mdl", - gunclass = "AC", - caliber = 3.0, - weight = 1000, - year = 1935, - rofmod = 0.5, - magsize = 75, - magreload = 20, - Cyclic = 225, - round = { - maxlength = 39, - propweight = 0.350 - } -}) - -ACF_defineGun("40mmAC", { - name = "40mm Autocannon", - desc = "The 40mm AC can fire shells with sufficient space for a useful payload, and can get decent penetration with proper rounds.", - model = "models/autocannon/autocannon_40mm.mdl", - gunclass = "AC", - caliber = 4.0, - weight = 1500, - year = 1940, - rofmod = 0.48, - magsize = 30, - magreload = 25, - Cyclic = 200, - round = { - maxlength = 45, - propweight = 0.9 - } -}) - -ACF_defineGun("50mmAC", { - name = "50mm Autocannon", - desc = "The 50mm AC fires shells comparable with the 50mm Cannon, making it capable of destroying light armour quite quickly.", - model = "models/autocannon/autocannon_50mm.mdl", - gunclass = "AC", - caliber = 5.0, - weight = 2000, - year = 1965, - rofmod = 0.4, - magsize = 25, - Cyclic = 175, - magreload = 30, - round = { - maxlength = 52, - propweight = 1.2 - } -}) \ No newline at end of file diff --git a/lua/acf/shared/guns/autoloader.lua b/lua/acf/shared/guns/autoloader.lua deleted file mode 100644 index ec1edefc1..000000000 --- a/lua/acf/shared/guns/autoloader.lua +++ /dev/null @@ -1,104 +0,0 @@ ---define the class -ACF_defineGunClass("AL", { - spread = 0.08, - name = "Autoloader", - desc = "A cannon with attached autoloading mechanism. While it allows for several quick shots, the mechanism adds considerable bulk, weight, and magazine reload time.", - muzzleflash = "cannon_muzzleflash_noscale", - rofmod = 0.64, - sound = "acf_base/weapons/autoloader.mp3", - soundDistance = "Cannon.Fire", - soundNormal = " " -}) - ---add a gun to the class ---id -ACF_defineGun("75mmAL", { - name = "75mm Autoloading Cannon", - desc = "A quick-firing 75mm gun, pops off a number of rounds in relatively short order.", - model = "models/tankgun/tankgun_al_75mm.mdl", - gunclass = "AL", - caliber = 7.5, - weight = 1892, - year = 1946, - rofmod = 1, - magsize = 8, - magreload = 15, - Cyclic = 30, - round = { - maxlength = 78, - propweight = 3.8 - } -}) - -ACF_defineGun("100mmAL", { - name = "100mm Autoloading Cannon", - desc = "The 100mm is good for rapidly hitting medium armor, then running like your ass is on fire to reload.", - model = "models/tankgun/tankgun_al_100mm.mdl", - gunclass = "AL", - caliber = 10.0, - weight = 3325, - year = 1956, - rofmod = 0.85, - magsize = 6, - magreload = 21, - Cyclic = 18, - round = { - maxlength = 93, - propweight = 9.5 - } -}) - -ACF_defineGun("120mmAL", { - name = "120mm Autoloading Cannon", - desc = "The 120mm autoloader can do serious damage before reloading, but the reload time is killer.", - model = "models/tankgun/tankgun_al_120mm.mdl", - gunclass = "AL", - caliber = 12.0, - weight = 6050, - year = 1956, - rofmod = 0.757, - magsize = 5, - magreload = 27, - Cyclic = 11, - round = { - maxlength = 110, - propweight = 18 - } -}) - -ACF_defineGun("140mmAL", { - name = "140mm Autoloading Cannon", - desc = "The 140mm can shred a medium tank's armor with one magazine, and even function as shoot & scoot artillery, with its useful HE payload.", - model = "models/tankgun/tankgun_al_140mm.mdl", - gunclass = "AL", - caliber = 14.0, - weight = 8830, - year = 1970, - rofmod = 0.743, - magsize = 5, - magreload = 35, - Cyclic = 8, - round = { - maxlength = 127, - propweight = 28 - } -}) ---[[ -ACF_defineGun("170mmAL", { - name = "170mm Autoloading Cannon", - desc = "The 170mm can shred an average 40ton tank's armor with one magazine.", - model = "models/tankgun/tankgun_al_170mm.mdl", - gunclass = "AL", - caliber = 17.0, - weight = 13350, - year = 1970, - rofmod = 0.8, - magsize = 4, - magreload = 40, - round = { - maxlength = 154, - propweight = 34 - } -} ) -]] --- \ No newline at end of file diff --git a/lua/acf/shared/guns/cannon.lua b/lua/acf/shared/guns/cannon.lua deleted file mode 100644 index 1511963e0..000000000 --- a/lua/acf/shared/guns/cannon.lua +++ /dev/null @@ -1,116 +0,0 @@ ---define the class -ACF_defineGunClass("C", { - spread = 0.08, - name = "Cannon", - desc = "High velocity guns that can fire very powerful ammunition, but are rather slow to reload.", - muzzleflash = "cannon_muzzleflash_noscale", - rofmod = 2, - sound = "acf_base/weapons/cannon_new.mp3", - soundDistance = "Cannon.Fire", - soundNormal = " " -}) - ---add a gun to the class ---id -ACF_defineGun("37mmC", { - name = "37mm Cannon", - desc = "A light and fairly weak cannon with good accuracy.", - model = "models/tankgun/tankgun_37mm.mdl", - gunclass = "C", - caliber = 3.7, - weight = 350, - year = 1919, - rofmod = 1.4, - sound = "acf_base/weapons/ac_fire4.mp3", - round = { - maxlength = 48, - propweight = 1.125 - } -}) - -ACF_defineGun("50mmC", { - name = "50mm Cannon", - desc = "The 50mm is surprisingly fast-firing, with good effectiveness against light armor, but a pea-shooter compared to its bigger cousins", - model = "models/tankgun/tankgun_50mm.mdl", - gunclass = "C", - caliber = 5.0, - weight = 665, - year = 1935, - sound = "acf_base/weapons/ac_fire4.mp3", - round = { - maxlength = 63, - propweight = 2.1 - } -}) - -ACF_defineGun("75mmC", { - name = "75mm Cannon", - desc = "The 75mm is still rather respectable in rate of fire, but has only modest payload. Often found on the Eastern Front, and on cold war light tanks.", - model = "models/tankgun/tankgun_75mm.mdl", - gunclass = "C", - caliber = 7.5, - weight = 1420, - year = 1942, - round = { - maxlength = 78, - propweight = 3.8 - } -}) - -ACF_defineGun("100mmC", { - name = "100mm Cannon", - desc = "The 100mm was a benchmark for the early cold war period, and has great muzzle velocity and hitting power, while still boasting a respectable, if small, payload.", - model = "models/tankgun/tankgun_100mm.mdl", - gunclass = "C", - caliber = 10.0, - weight = 2750, - year = 1944, - round = { - maxlength = 93, - propweight = 9.5 - } -}) - -ACF_defineGun("120mmC", { - name = "120mm Cannon", - desc = "Often found in MBTs, the 120mm shreds lighter armor with utter impunity, and is formidable against even the big boys.", - model = "models/tankgun/tankgun_120mm.mdl", - gunclass = "C", - caliber = 12.0, - weight = 5200, - year = 1955, - round = { - maxlength = 110, - propweight = 18 - } -}) - -ACF_defineGun("140mmC", { - name = "140mm Cannon", - desc = "The 140mm fires a massive shell with enormous penetrative capability, but has a glacial reload speed and a very hefty weight.", - model = "models/tankgun/tankgun_140mm.mdl", - gunclass = "C", - caliber = 14.0, - weight = 8180, - year = 1990, - round = { - maxlength = 127, - propweight = 28 - } -}) ---[[ -ACF_defineGun("170mmC", { - name = "170mm Cannon", - desc = "The 170mm fires a gigantic shell with ginormous penetrative capability, but has a glacial reload speed and an extremely hefty weight.", - model = "models/tankgun/tankgun_170mm.mdl", - gunclass = "C", - caliber = 17.0, - weight = 12350, - year = 1990, - round = { - maxlength = 154, - propweight = 34 - } -} ) -]] --- \ No newline at end of file diff --git a/lua/acf/shared/guns/grenadelauncher.lua b/lua/acf/shared/guns/grenadelauncher.lua deleted file mode 100644 index 687da76d7..000000000 --- a/lua/acf/shared/guns/grenadelauncher.lua +++ /dev/null @@ -1,29 +0,0 @@ ---define the class -ACF_defineGunClass("GL", { - spread = 0.28, - name = "Grenade Launcher", - desc = "Grenade Launchers can fire shells with relatively large payloads at a fast rate, but with very limited velocities and poor accuracy.", - muzzleflash = "gl_muzzleflash_noscale", - rofmod = 1, - sound = "acf_base/weapons/grenadelauncher.mp3", - soundDistance = " ", - soundNormal = " " -} ) - ---add a gun to the class -ACF_defineGun("40mmGL", { --id - name = "40mm Grenade Launcher", - desc = "The 40mm chews up infantry but is about as useful as tits on a nun for fighting armor. Often found on 4x4s rolling through the third world.", - model = "models/launcher/40mmgl.mdl", - gunclass = "GL", - caliber = 4.0, - weight = 55, - magsize = 30, - magreload = 7.5, - year = 1970, - Cyclic = 200, - round = { - maxlength = 7.5, - propweight = 0.01 - } -} ) diff --git a/lua/acf/shared/guns/heavymachinegun.lua b/lua/acf/shared/guns/heavymachinegun.lua deleted file mode 100644 index 2c717799a..000000000 --- a/lua/acf/shared/guns/heavymachinegun.lua +++ /dev/null @@ -1,89 +0,0 @@ ---define the class -ACF_defineGunClass("HMG", { - spread = 1.2, - name = "Heavy Machinegun", - desc = "Designed as autocannons for aircraft, HMGs are rapid firing, lightweight, and compact but sacrifice accuracy, magazine size, and reload times. They excel at strafing and dogfighting.\nBecause of their long reload times and high rate of fire, it is best to aim BEFORE pushing the almighty fire switch.", - muzzleflash = "mg_muzzleflash_noscale", - rofmod = 0.14, - sound = "acf_base/weapons/mg_fire3.mp3", - soundDistance = " ", - soundNormal = " ", - longbarrel = { - index = 2, - submodel = 4, - newpos = "muzzle2" - } -}) - ---add a gun to the class -ACF_defineGun("13mmHMG", { - name = "13mm Heavy Machinegun", - desc = "The lightest of the HMGs, the 13mm has a rapid fire rate but suffers from poor payload size. Often used to strafe ground troops or annoy low-flying aircraft.", - model = "models/machinegun/machinegun_20mm.mdl", - gunclass = "HMG", - caliber = 1.3, - weight = 90, - year = 1935, - rofmod = 3.3, - magsize = 35, - magreload = 6, - Cyclic = 550, - round = { - maxlength = 22, - propweight = 0.09 - } -}) - -ACF_defineGun("20mmHMG", { - name = "20mm Heavy Machinegun", - desc = "The 20mm has a rapid fire rate but suffers from poor payload size. Often used to strafe ground troops or annoy low-flying aircraft.", - model = "models/machinegun/machinegun_20mm_compact.mdl", - gunclass = "HMG", - caliber = 2.0, - weight = 160, - year = 1935, - rofmod = 1.9, --at 1.5, 675rpm; at 2.0, 480rpm - magsize = 30, - magreload = 6, - Cyclic = 525, - round = { - maxlength = 30, - propweight = 0.12 - } -}) - -ACF_defineGun("30mmHMG", { - name = "30mm Heavy Machinegun", - desc = "30mm shell chucker, light and compact. Your average cold war dogfight go-to.", - model = "models/machinegun/machinegun_30mm_compact.mdl", - gunclass = "HMG", - caliber = 3.0, - weight = 480, - year = 1941, - rofmod = 1.1, --at 1.05, 495rpm; - magsize = 25, - magreload = 6, - Cyclic = 500, - round = { - maxlength = 37, - propweight = 0.35 - } -}) - -ACF_defineGun("40mmHMG", { - name = "40mm Heavy Machinegun", - desc = "The heaviest of the heavy machineguns. Massively powerful with a killer reload and hefty ammunition requirements, it can pop even relatively heavy targets with ease.", - model = "models/machinegun/machinegun_40mm_compact.mdl", - gunclass = "HMG", - caliber = 4.0, - weight = 780, - year = 1955, - rofmod = 0.95, --at 0.75, 455rpm - magsize = 20, - magreload = 8, - Cyclic = 475, - round = { - maxlength = 42, - propweight = 0.9 - } -}) diff --git a/lua/acf/shared/guns/howitzer.lua b/lua/acf/shared/guns/howitzer.lua deleted file mode 100644 index 271e6eadf..000000000 --- a/lua/acf/shared/guns/howitzer.lua +++ /dev/null @@ -1,113 +0,0 @@ ---define the class -ACF_defineGunClass("HW", { - spread = 0.1, - name = "Howitzer", - desc = "Howitzers are limited to rather mediocre muzzle velocities, but can fire extremely heavy projectiles with large useful payload capacities.", - muzzleflash = "howie_muzzleflash_noscale", - rofmod = 1.8, - sound = "acf_base/weapons/howitzer_new2.mp3", - soundDistance = "Howitzer.Fire", - soundNormal = " " -}) - ---add a gun to the class ---id -ACF_defineGun("75mmHW", { - name = "75mm Howitzer", - desc = "Often found being towed by large smelly animals, the 75mm has a high rate of fire, and is surprisingly lethal against light armor. Great for a sustained barrage against someone you really don't like.", - model = "models/howitzer/howitzer_75mm.mdl", - gunclass = "HW", - caliber = 7.5, - weight = 530, - year = 1900, - round = { - maxlength = 60, - propweight = 1.8 - } -}) - -ACF_defineGun("105mmHW", { - name = "105mm Howitzer", - desc = "The 105 lobs a big shell far, and its HEAT rounds can be extremely effective against even heavier armor.", - model = "models/howitzer/howitzer_105mm.mdl", - gunclass = "HW", - caliber = 10.5, - weight = 1480, - year = 1900, - round = { - maxlength = 86, - propweight = 3.75 - } -}) - -ACF_defineGun("122mmHW", { - name = "122mm Howitzer", - desc = "The 122mm bridges the gap between the 105 and the 155, providing a lethal round with a big splash radius.", - model = "models/howitzer/howitzer_122mm.mdl", - gunclass = "HW", - caliber = 12.2, - weight = 3420, - year = 1900, - round = { - maxlength = 106, - propweight = 7 - } -}) - -ACF_defineGun("155mmHW", { - name = "155mm Howitzer", - desc = "The 155 is a classic heavy artillery round, with good reason. A versatile weapon, it's found on most modern SPGs.", - model = "models/howitzer/howitzer_155mm.mdl", - gunclass = "HW", - caliber = 15.5, - weight = 5340, - year = 1900, - round = { - maxlength = 124, - propweight = 13.5 - } -}) - -ACF_defineGun("203mmHW", { - name = "203mm Howitzer", - desc = "An 8-inch deck gun, found on siege artillery and cruisers.", - model = "models/howitzer/howitzer_203mm.mdl", - gunclass = "HW", - caliber = 20.3, - weight = 10280, - year = 1900, - round = { - maxlength = 162.4, - propweight = 28.5 - } -}) ---[[ -ACF_defineGun("240mmHW", { - name = "240mm Howitzer", - desc = "A 9.4-inch deck gun, found on heavy siege artillery and cruisers.", - model = "models/howitzer/howitzer_240mm.mdl", - gunclass = "HW", - caliber = 24.0, - weight = 12980, - year = 1900, - round = { - maxlength = 192.0, - propweight = 33.7 - } -} ) - -ACF_defineGun("290mmHW", { - name = "290mm Howitzer", - desc = " Mother of all howitzers. This 12in beast can be found on battleships. It WILL fuck your day up... when it reloads.", - model = "models/howitzer/howitzer_406mm.mdl", - gunclass = "HW", - caliber = 29, - weight = 24960, - year = 1900, - round = { - maxlength = 325, - propweight = 57.0 - } -} ) -]] --- \ No newline at end of file diff --git a/lua/acf/shared/guns/machinegun.lua b/lua/acf/shared/guns/machinegun.lua deleted file mode 100644 index 98993d96f..000000000 --- a/lua/acf/shared/guns/machinegun.lua +++ /dev/null @@ -1,67 +0,0 @@ ---define the class -ACF_defineGunClass("MG", { - spread = 0.16, - name = "Machinegun", - desc = "Machineguns are light guns that fire equally light bullets at a fast rate.", - muzzleflash = "mg_muzzleflash_noscale", - rofmod = 0.9, - sound = "acf_base/weapons/mg_fire4.mp3", - soundNormal = "acf_base/weapons/mg_fire4.mp3", - soundDistance = "" -}) - ---add a gun to the class ---id -ACF_defineGun("7.62mmMG", { - name = "7.62mm Machinegun", - desc = "The 7.62mm is effective against infantry, but its usefulness against armor is laughable at best.", - model = "models/machinegun/machinegun_762mm.mdl", - gunclass = "MG", - caliber = 0.762, - weight = 15, - year = 1930, - rofmod = 1.59, - magsize = 250, - magreload = 6, -- Time to reload in seconds - Cyclic = 700, -- Rounds per minute - round = { - maxlength = 13, - propweight = 0.04 - } -}) - -ACF_defineGun("12.7mmMG", { - name = "12.7mm Machinegun", - desc = "The 12.7mm MG is still light, finding its way into a lot of mountings, including on top of tanks.", - model = "models/machinegun/machinegun_127mm.mdl", - gunclass = "MG", - caliber = 1.27, - weight = 30, - year = 1910, - rofmod = 1, --0.766 - magsize = 150, - magreload = 6, - Cyclic = 600, - round = { - maxlength = 15.8, - propweight = 0.03 - } -}) - -ACF_defineGun("14.5mmMG", { - name = "14.5mm Machinegun", - desc = "The 14.5mm MG trades its smaller stablemates' rate of fire for more armor penetration and damage.", - model = "models/machinegun/machinegun_145mm.mdl", - gunclass = "MG", - caliber = 1.45, - weight = 45, - year = 1932, - rofmod = 1, --0.722 - magsize = 90, - magreload = 5, - Cyclic = 500, - round = { - maxlength = 19.5, - propweight = 0.04 - } -}) \ No newline at end of file diff --git a/lua/acf/shared/guns/mortar.lua b/lua/acf/shared/guns/mortar.lua deleted file mode 100644 index c300d420b..000000000 --- a/lua/acf/shared/guns/mortar.lua +++ /dev/null @@ -1,100 +0,0 @@ ---define the class -ACF_defineGunClass("MO", { - spread = 0.72, - name = "Mortar", - desc = "Mortars are able to fire shells with usefull payloads from a light weight gun, at the price of limited velocities.", - muzzleflash = "mortar_muzzleflash_noscale", - rofmod = 2.5, - sound = "acf_base/weapons/mortar_new.mp3", - soundDistance = "Mortar.Fire", - soundNormal = " " -}) - ---add a gun to the class ---id -ACF_defineGun("60mmM", { - name = "60mm Mortar", - desc = "The 60mm is a common light infantry support weapon, with a high rate of fire but a puny payload.", - model = "models/mortar/mortar_60mm.mdl", - gunclass = "MO", - caliber = 6.0, - weight = 60, - rofmod = 1.25, - year = 1930, - round = { - maxlength = 20, - propweight = 0.037 - } -}) - -ACF_defineGun("80mmM", { - name = "80mm Mortar", - desc = "The 80mm is a common infantry support weapon, with a good bit more boom than its little cousin.", - model = "models/mortar/mortar_80mm.mdl", - gunclass = "MO", - caliber = 8.0, - weight = 120, - year = 1930, - round = { - maxlength = 28, - propweight = 0.055 - } -}) - -ACF_defineGun("120mmM", { - name = "120mm Mortar", - desc = "The versatile 120 is sometimes vehicle-mounted to provide quick boomsplat to support the infantry. Carries more boom in its boomsplat, has good HEAT performance, and is more accurate in high-angle firing.", - model = "models/mortar/mortar_120mm.mdl", - gunclass = "MO", - caliber = 12.0, - weight = 640, - year = 1935, - round = { - maxlength = 45, - propweight = 0.175 - } -}) - -ACF_defineGun("150mmM", { - name = "150mm Mortar", - desc = "The perfect balance between the 120mm and the 200mm. Can prove a worthy main gun weapon, as well as a mighty good mortar emplacement", - model = "models/mortar/mortar_150mm.mdl", - gunclass = "MO", - caliber = 15.0, - weight = 1255, - year = 1945, - round = { - maxlength = 58, - propweight = 0.235 - } -}) - -ACF_defineGun("200mmM", { - name = "200mm Mortar", - desc = "The 200mm is a beast, often used against fortifications. Though enormously powerful, feel free to take a nap while it reloads", - model = "models/mortar/mortar_200mm.mdl", - gunclass = "MO", - caliber = 20.0, - weight = 2850, - year = 1940, - round = { - maxlength = 80, - propweight = 0.330 - } -}) ---[[ -ACF_defineGun("280mmM", { - name = "280mm Mortar", - desc = "Massive payload, with a reload time to match. Found in rare WW2 siege artillery pieces. It's the perfect size for a jeep.", - model = "models/mortar/mortar_280mm.mdl", - gunclass = "MO", - caliber = 28.0, - weight = 9035, - year = 1945, - round = { - maxlength = 138, - propweight = 0.462 - } -} ) -]] --- \ No newline at end of file diff --git a/lua/acf/shared/guns/rotaryautocannon.lua b/lua/acf/shared/guns/rotaryautocannon.lua deleted file mode 100644 index 38c278a2f..000000000 --- a/lua/acf/shared/guns/rotaryautocannon.lua +++ /dev/null @@ -1,66 +0,0 @@ ---define the class -ACF_defineGunClass("RAC", { - spread = 0.48, - name = "Rotary Autocannon", - desc = "Rotary Autocannons sacrifice weight, bulk and accuracy over classic Autocannons to get the highest rate of fire possible.", - muzzleflash = "mg_muzzleflash_noscale", - rofmod = 0.07, - sound = "acf_base/weapons/mg_fire3.mp3", - soundDistance = " ", - soundNormal = " ", - color = {135, 135, 135} -} ) - -ACF_defineGun("14.5mmRAC", { --id - name = "14.5mm Rotary Autocannon", - desc = "A lightweight rotary autocannon, used primarily against infantry and light vehicles.", - model = "models/rotarycannon/kw/14_5mmrac.mdl", - gunclass = "RAC", - caliber = 1.45, - weight = 400, - year = 1962, - magsize = 250, - magreload = 15, - rofmod = 5.4, - Cyclic = 2250, - round = { - maxlength = 25, - propweight = 0.06 - } -} ) - -ACF_defineGun("20mmRAC", { - name = "20mm Rotary Autocannon", - desc = "The 20mm is able to chew up light armor with decent penetration or put up a big flak screen.", - model = "models/rotarycannon/kw/20mmrac.mdl", - gunclass = "RAC", - caliber = 2.0, - weight = 760, - year = 1965, - magsize = 200, - magreload = 20, - rofmod = 2.1, - Cyclic = 2000, - round = { - maxlength = 30, - propweight = 0.12 - } -} ) - -ACF_defineGun("30mmRAC", { - name = "30mm Rotary Autocannon", - desc = "The 30mm is the bane of ground-attack aircraft, able to tear up light armor without giving one single fuck. Also seen in the skies above dead T-72s.", - model = "models/rotarycannon/kw/30mmrac.mdl", - gunclass = "RAC", - caliber = 3.0, - weight = 1500, - year = 1975, - magsize = 100, - magreload = 30, - rofmod = 1, - Cyclic = 1500, - round = { - maxlength = 40, - propweight = 0.350 - } -} ) \ No newline at end of file diff --git a/lua/acf/shared/guns/semiauto.lua b/lua/acf/shared/guns/semiauto.lua deleted file mode 100644 index 07a9a5c83..000000000 --- a/lua/acf/shared/guns/semiauto.lua +++ /dev/null @@ -1,103 +0,0 @@ ---define the class -ACF_defineGunClass("SA", { - spread = 0.12, - name = "Semiautomatic Cannon", - desc = "Semiautomatic cannons offer light weight, small size, and high rates of fire at the cost of often reloading and low accuracy.", - muzzleflash = "semi_muzzleflash_noscale", - rofmod = 0.36, - sound = "acf_base/weapons/sa_fire1.mp3", - soundDistance = " ", - soundNormal = " " -}) - ---add a gun to the class ---id -ACF_defineGun("25mmSA", { - name = "25mm Semiautomatic Cannon", - desc = "The 25mm semiauto can quickly put five rounds downrange, being lethal, yet light.", - model = "models/autocannon/semiautocannon_25mm.mdl", - gunclass = "SA", - caliber = 2.5, - weight = 250, - year = 1935, - rofmod = 0.7, - magsize = 5, - magreload = 2.5, - Cyclic = 300, - round = { - maxlength = 39, - propweight = 0.5 - } -}) - -ACF_defineGun("37mmSA", { - name = "37mm Semiautomatic Cannon", - desc = "The 37mm is surprisingly powerful, its five-round clips boasting a respectable payload and a high muzzle velocity.", - model = "models/autocannon/semiautocannon_37mm.mdl", - gunclass = "SA", - caliber = 3.7, - weight = 500, - year = 1940, - rofmod = 0.7, - magsize = 5, - magreload = 3.7, - Cyclic = 250, - round = { - maxlength = 42, - propweight = 1.125 - } -}) - -ACF_defineGun("45mmSA", { - name = "45mm Semiautomatic Cannon", - desc = "The 45mm can easily shred light armor, with a respectable rate of fire, but its armor penetration pales in comparison to regular cannons.", - model = "models/autocannon/semiautocannon_45mm.mdl", - gunclass = "SA", - caliber = 4.5, - weight = 750, - year = 1965, - rofmod = 0.72, - magsize = 5, - magreload = 4.5, - Cyclic = 225, - round = { - maxlength = 52, - propweight = 1.8 - } -}) - -ACF_defineGun("57mmSA", { - name = "57mm Semiautomatic Cannon", - desc = "The 57mm is a respectable light armament, offering considerable penetration and moderate fire rate.", - model = "models/autocannon/semiautocannon_57mm.mdl", - gunclass = "SA", - caliber = 5.7, - weight = 1000, - year = 1965, - rofmod = 0.8, - magsize = 5, - magreload = 5.7, - Cyclic = 200, - round = { - maxlength = 62, - propweight = 2 - } -}) - -ACF_defineGun("76mmSA", { - name = "76mm Semiautomatic Cannon", - desc = "The 76mm semiauto is a fearsome weapon, able to put 5 76mm rounds downrange in 8 seconds.", - model = "models/autocannon/semiautocannon_76mm.mdl", - gunclass = "SA", - caliber = 7.62, - weight = 2000, - year = 1984, - rofmod = 0.85, - magsize = 5, - magreload = 7.6, - Cyclic = 150, - round = { - maxlength = 70, - propweight = 4.75 - } -}) \ No newline at end of file diff --git a/lua/acf/shared/guns/shortcannon.lua b/lua/acf/shared/guns/shortcannon.lua deleted file mode 100644 index 6dda59ed3..000000000 --- a/lua/acf/shared/guns/shortcannon.lua +++ /dev/null @@ -1,100 +0,0 @@ ---define the class -ACF_defineGunClass("SC", { - spread = 0.16, - name = "Short-Barrel Cannon", - desc = "Short cannons trade muzzle velocity and accuracy for lighter weight and smaller size, with more penetration than howitzers and lighter than cannons.", - muzzleflash = "cannon_muzzleflash_noscale", - rofmod = 1.7, - sound = "acf_base/weapons/cannon_new.mp3", - soundDistance = "Cannon.Fire", - soundNormal = " " -} ) - ---add a gun to the class -ACF_defineGun("37mmSC", { - name = "37mm Short Cannon", - desc = "Quick-firing and light, but penetration is laughable. You're better off throwing rocks.", - model = "models/tankgun/tankgun_short_37mm.mdl", - gunclass = "SC", - caliber = 3.7, - weight = 200, - rofmod = 1.4, - year = 1915, - sound = "acf_base/weapons/ac_fire4.mp3", - round = { - maxlength = 45, - propweight = 0.29 - } -} ) - -ACF_defineGun("50mmSC", { - name = "50mm Short Cannon", - desc = "The 50mm is a quick-firing pea-shooter, good for scouts, and common on old interwar tanks.", - model = "models/tankgun/tankgun_short_50mm.mdl", - gunclass = "SC", - caliber = 5.0, - weight = 330, - rofmod = 1.4, - year = 1915, - sound = "acf_base/weapons/ac_fire4.mp3", - round = { - maxlength = 63, - propweight = 0.6, - } -} ) - -ACF_defineGun("75mmSC", { - name = "75mm Short Cannon", - desc = "The 75mm is common WW2 medium tank armament, and still useful in many other applications.", - model = "models/tankgun/tankgun_short_75mm.mdl", - gunclass = "SC", - caliber = 7.5, - weight = 750, - year = 1936, - round = { - maxlength = 76, - propweight = 2 - } -} ) - -ACF_defineGun("100mmSC", { - name = "100mm Short Cannon", - desc = "The 100mm is an effective infantry-support or antitank weapon, with a lot of uses and surprising lethality.", - model = "models/tankgun/tankgun_short_100mm.mdl", - gunclass = "SC", - caliber = 10.0, - weight = 1750, - year = 1940, - round = { - maxlength = 93, - propweight = 4.5 - } -} ) - -ACF_defineGun("120mmSC", { - name = "120mm Short Cannon", - desc = "The 120mm is a formidable yet lightweight weapon, with excellent performance against larger vehicles.", - model = "models/tankgun/tankgun_short_120mm.mdl", - gunclass = "SC", - caliber = 12.0, - weight = 3800, - year = 1944, - round = { - maxlength = 110, - propweight = 8.5 - } -} ) - -ACF_defineGun("140mmSC", { - name = "140mm Short Cannon", - desc = "A specialized weapon, developed from dark magic and anti-heavy tank hatred. Deal with it.", - model = "models/tankgun/tankgun_short_140mm.mdl", - gunclass = "SC", - caliber = 14.0, - weight = 6040, - year = 1999, - round = { - maxlength = 127, - propweight = 12.8 - } -} ) diff --git a/lua/acf/shared/guns/smokelauncher.lua b/lua/acf/shared/guns/smokelauncher.lua deleted file mode 100644 index b87569c77..000000000 --- a/lua/acf/shared/guns/smokelauncher.lua +++ /dev/null @@ -1,52 +0,0 @@ ---define the class -ACF_defineGunClass("SL", { - spread = 0.32, - name = "Smoke Launcher", - desc = "Smoke launcher to block an attacker's line of sight.", - muzzleflash = "gl_muzzleflash_noscale", - rofmod = 22.5, - sound = "acf_base/weapons/smoke_launch.mp3", - soundDistance = "Mortar.Fire", - soundNormal = " " -}) - ---add a gun to the class ---id -ACF_defineGun("40mmSL", { - name = "40mm Smoke Launcher", - desc = "", - model = "models/launcher/40mmsl.mdl", - gunclass = "SL", - canparent = true, - caliber = 4.0, - weight = 1, - year = 1941, - magsize = 1, - magreload = 30, - Cyclic = 600, - round = { - maxlength = 17.5, - propweight = 0.000075 - } -}) - ---add a gun to the class ---id -ACF_defineGun("40mmCL", { - name = "40mm Countermeasure Launcher", - desc = "A revolver-style launcher capable of firing off several smoke or flare rounds.", - model = "models/launcher/40mmgl.mdl", - gunclass = "SL", - canparent = true, - caliber = 4.0, - weight = 20, - rofmod = 0.015, - magsize = 6, - magreload = 15, - Cyclic = 200, - year = 1950, - round = { - maxlength = 17.5, - propweight = 0.001 - } -}) diff --git a/lua/acf/shared/guns/smoothbore.lua b/lua/acf/shared/guns/smoothbore.lua deleted file mode 100644 index c47e43434..000000000 --- a/lua/acf/shared/guns/smoothbore.lua +++ /dev/null @@ -1,72 +0,0 @@ ---define the class -ACF_defineGunClass("SB", { - spread = 0.08, - name = "Smoothbore Cannon", - desc = "More modern smoothbore cannons that can only fire munitions that do not rely on spinning for accuracy.", - muzzleflash = "cannon_muzzleflash_noscale", - rofmod = 1.72, - sound = "acf_base/weapons/cannon_new.mp3", - soundDistance = "Cannon.Fire", - soundNormal = " " -} ) - ---add a gun to the class - - -ACF_defineGun("105mmSB", { - name = "105mm Smoothbore Cannon", - desc = "The 105mm was a benchmark for the early cold war period, and has great muzzle velocity and hitting power, while still boasting a respectable, if small, payload.", - model = "models/tankgun_old/tankgun_100mm.mdl", - gunclass = "SB", - caliber = 10.5, - weight = 3550, - year = 1970, - round = { - maxlength = 101, - propweight = 9 - } -} ) - -ACF_defineGun("120mmSB", { - name = "120mm Smoothbore Cannon", - desc = "Often found in MBTs, the 120mm shreds lighter armor with utter impunity, and is formidable against even the big boys.", - model = "models/tankgun_old/tankgun_120mm.mdl", - gunclass = "SB", - caliber = 12.0, - weight = 6000, - year = 1975, - round = { - maxlength = 123, - propweight = 18 - } -} ) - -ACF_defineGun("140mmSB", { - name = "140mm Smoothbore Cannon", - desc = "The 140mm fires a massive shell with enormous penetrative capability, but has a glacial reload speed and a very hefty weight.", - model = "models/tankgun_old/tankgun_140mm.mdl", - gunclass = "SB", - caliber = 14.0, - weight = 8980, - year = 1990, - round = { - maxlength = 145, - propweight = 28 - } -} ) - ---[[ -ACF_defineGun("170mmC", { - name = "170mm Cannon", - desc = "The 170mm fires a gigantic shell with ginormous penetrative capability, but has a glacial reload speed and an extremely hefty weight.", - model = "models/tankgun/tankgun_170mm.mdl", - gunclass = "C", - caliber = 17.0, - weight = 12350, - year = 1990, - round = { - maxlength = 154, - propweight = 34 - } -} ) -]]-- \ No newline at end of file diff --git a/lua/acf/shared/hitboxes.lua b/lua/acf/shared/hitboxes.lua index ce96977c7..f6675a5b9 100644 --- a/lua/acf/shared/hitboxes.lua +++ b/lua/acf/shared/hitboxes.lua @@ -151,7 +151,6 @@ local AnimLookup = { -- defaults to Passenger, catchall for any wierd fucking an ["default"] = "Passenger" } -local function switch(cases,arg) local Var = (cases[arg] or cases["default"]) return Var end hook.Add("PlayerEnteredVehicle","ACF_HitboxUpdate",function(ply,ent) timer.Simple(3,function() -- 3 seconds is a safe amount of time for all of the seats (including those stupid fucking PHX ones that take 3 years to move the camera) to get the camera into position if IsValid(ply) and IsValid(ent) and ply:GetVehicle() == ent then diff --git a/lua/acf/shared/killicons.lua b/lua/acf/shared/killicons.lua index 5bbfdd1fc..5f9776930 100644 --- a/lua/acf/shared/killicons.lua +++ b/lua/acf/shared/killicons.lua @@ -25,87 +25,77 @@ ------------------------------------------------------------------------------]] -if SERVER and ACF.EnableKillicons then - util.AddNetworkString( "ACF_PlayerKilledNPC" ) - util.AddNetworkString( "ACF_NPCKilledNPC" ) +local function ServerSideActions(Enabled) + if not SERVER then return end + if not Enabled then return end - local function ACF_OnNPCKilled( ent, attacker, inflictor ) - -- Don't spam the killfeed with scripted stuff - if ( ent:GetClass() == "npc_bullseye" or ent:GetClass() == "npc_launcher" ) then return end + util.AddNetworkString("ACF_PlayerKilledNPC") + util.AddNetworkString("ACF_NPCKilledNPC") + util.AddNetworkString("ACF_PlayerKilled") + util.AddNetworkString("ACF_PlayerKilledSelf") + util.AddNetworkString("ACF_PlayerKilledByPlayer") - if ( IsValid( attacker ) and attacker:GetClass() == "trigger_hurt" ) then attacker = ent end + hook.Add("OnNPCKilled", "ACF_OnNPCKilled", function(ent, attacker, inflictor) + -- Don't spam the killfeed with scripted stuff + if ent:GetClass() == "npc_bullseye" or ent:GetClass() == "npc_launcher" then return end + if IsValid(attacker) and attacker:GetClass() == "trigger_hurt" then attacker = ent end - if ( IsValid( attacker ) and attacker:IsVehicle() and IsValid( attacker:GetDriver() ) ) then + if IsValid(attacker) and attacker:IsVehicle() and IsValid(attacker:GetDriver()) then attacker = attacker:GetDriver() end - if ( !IsValid( inflictor ) and IsValid( attacker ) ) then + if not IsValid(inflictor) and IsValid(attacker) then inflictor = attacker end -- Convert the inflictor to the weapon that they're holding if we can. - if ( IsValid( inflictor ) and attacker == inflictor and ( inflictor:IsPlayer() or inflictor:IsNPC() ) ) then - + if IsValid(inflictor) and attacker == inflictor and (inflictor:IsPlayer() or inflictor:IsNPC()) then inflictor = inflictor:GetActiveWeapon() - if ( !IsValid( attacker ) ) then inflictor = attacker end + if not IsValid(attacker) then inflictor = attacker end end local InflictorClass = "worldspawn" - local AttackerClass = "worldspawn" + local AttackerClass = "worldspawn" - if ( IsValid( inflictor ) ) then - if inflictor.ACF and inflictor.Class and inflictor:GetClass() != "acf_ammo" then + if IsValid(inflictor) then + if inflictor.ACF and inflictor.Class and inflictor:GetClass() ~= "acf_ammo" then InflictorClass = "acf_" .. inflictor.Class else InflictorClass = inflictor:GetClass() end end - if ( IsValid( attacker ) ) then - + if IsValid(attacker) then AttackerClass = attacker:GetClass() - if ( attacker:IsPlayer() ) then - - net.Start( "ACF_PlayerKilledNPC" ) - - net.WriteString( ent:GetClass() ) - net.WriteString( InflictorClass ) - net.WriteEntity( attacker ) - + if attacker:IsPlayer() then + net.Start("ACF_PlayerKilledNPC") + net.WriteString(ent:GetClass()) + net.WriteString(InflictorClass) + net.WriteEntity(attacker) net.Broadcast() return end - end - if ( ent:GetClass() == "npc_turret_floor" ) then AttackerClass = ent:GetClass() end - - net.Start( "ACF_NPCKilledNPC" ) - - net.WriteString( ent:GetClass() ) - net.WriteString( InflictorClass ) - net.WriteString( AttackerClass ) + if ent:GetClass() == "npc_turret_floor" then AttackerClass = ent:GetClass() end + net.Start("ACF_NPCKilledNPC") + net.WriteString(ent:GetClass()) + net.WriteString(InflictorClass) + net.WriteString(AttackerClass) net.Broadcast() - end - hook.Add( "OnNPCKilled", "ACF_OnNPCKilled", ACF_OnNPCKilled ) - - util.AddNetworkString( "ACF_PlayerKilled" ) - util.AddNetworkString( "ACF_PlayerKilledSelf" ) - util.AddNetworkString( "ACF_PlayerKilledByPlayer" ) - - local function ACF_PlayerDeath( ply, inflictor, attacker ) + end) - if ( IsValid( attacker ) and attacker:GetClass() == "trigger_hurt" ) then attacker = ply end - - if ( IsValid( attacker ) and attacker:IsVehicle() and IsValid( attacker:GetDriver() ) ) then + hook.Add("PlayerDeath", "ACF_PlayerDeath", function(ply, inflictor, attacker) + if IsValid(attacker) and attacker:GetClass() == "trigger_hurt" then attacker = ply end + if IsValid(attacker) and attacker:IsVehicle() and IsValid(attacker:GetDriver()) then attacker = attacker:GetDriver() end - if ( !IsValid( inflictor ) and IsValid( attacker ) ) then + if not IsValid(inflictor) and IsValid(attacker) then inflictor = attacker end @@ -114,151 +104,141 @@ if SERVER and ACF.EnableKillicons then -- pistol but kill you by hitting you with their arm. local InflictorClass = "worldspawn" - if ( IsValid( inflictor ) and inflictor == attacker and ( inflictor:IsPlayer() or inflictor:IsNPC() ) ) then - + if IsValid(inflictor) and inflictor == attacker and (inflictor:IsPlayer() or inflictor:IsNPC()) then inflictor = inflictor:GetActiveWeapon() - if ( !IsValid( inflictor ) ) then inflictor = attacker end + + if not IsValid(inflictor) then inflictor = attacker end end - if inflictor.ACF and inflictor.Class and inflictor:GetClass() != "acf_ammo" then + if inflictor.ACF and inflictor.Class and inflictor:GetClass() ~= "acf_ammo" then InflictorClass = "acf_" .. inflictor.Class else InflictorClass = inflictor:GetClass() end - if ( attacker == ply ) then return end - - if ( attacker:IsPlayer() ) then - - net.Start( "ACF_PlayerKilledByPlayer" ) - - net.WriteEntity( ply ) - net.WriteString( InflictorClass ) - net.WriteEntity( attacker ) + if attacker == ply then return end + if attacker:IsPlayer() then + net.Start("ACF_PlayerKilledByPlayer") + net.WriteEntity(ply) + net.WriteString(InflictorClass) + net.WriteEntity(attacker) net.Broadcast() return end - net.Start( "ACF_PlayerKilled" ) - - net.WriteEntity( ply ) - net.WriteString( InflictorClass ) - net.WriteString( attacker:GetClass() ) - + net.Start("ACF_PlayerKilled") + net.WriteEntity(ply) + net.WriteString(InflictorClass) + net.WriteString(attacker:GetClass()) net.Broadcast() - end - hook.Add( "PlayerDeath", "ACF_PlayerDeath", ACF_PlayerDeath ) + end) end -if CLIENT then - local IconColor = Color( 200, 200, 48, 255 ) - - killicon.Add( "acf_gun", "HUD/killicons/acf_gun", IconColor ) - killicon.Add( "acf_ammo", "HUD/killicons/acf_ammo", IconColor ) - killicon.Add( "torch", "HUD/killicons/torch", IconColor ) - - if ACF.EnableKillicons then - killicon.Add( "acf_AC", "HUD/killicons/acf_AC", IconColor ) - killicon.Add( "acf_AL", "HUD/killicons/acf_AL", IconColor ) - killicon.Add( "acf_C", "HUD/killicons/acf_C", IconColor ) - killicon.Add( "acf_GL", "HUD/killicons/acf_GL", IconColor ) - killicon.Add( "acf_HMG", "HUD/killicons/acf_HMG", IconColor ) - killicon.Add( "acf_HW", "HUD/killicons/acf_HW", IconColor ) - killicon.Add( "acf_MG", "HUD/killicons/acf_MG", IconColor ) - killicon.Add( "acf_MO", "HUD/killicons/acf_MO", IconColor ) - killicon.Add( "acf_RAC", "HUD/killicons/acf_RAC", IconColor ) - killicon.Add( "acf_SA", "HUD/killicons/acf_SA", IconColor ) - - local function doNothing() - return false - end - - net.Receive( "PlayerKilledByPlayer", doNothing ) - net.Receive( "PlayerKilled", doNothing ) - - net.Receive( "PlayerKilledNPC", doNothing ) - net.Receive( "NPCKilledNPC", doNothing ) +local function ClientSideActions(Enabled) + if not CLIENT then return end - local function RecvPlayerKilledByPlayer() + local IconColor = Color(200, 200, 48) - local victim = net.ReadEntity() - local inflictor = net.ReadString() - local attacker = net.ReadEntity() + killicon.Add("acf_ammo", "HUD/killicons/acf_ammo", IconColor) + killicon.Add("acf_gun", "HUD/killicons/acf_gun", IconColor) + killicon.Add("torch", "HUD/killicons/torch", IconColor) - if ( !IsValid( attacker ) ) then return end - if ( !IsValid( victim ) ) then return end + if not Enabled then return end - GAMEMODE:AddDeathNotice( attacker:Name(), attacker:Team(), inflictor, victim:Name(), victim:Team() ) + killicon.Add("acf_AC", "HUD/killicons/acf_AC", IconColor) + killicon.Add("acf_AL", "HUD/killicons/acf_AL", IconColor) + killicon.Add("acf_C", "HUD/killicons/acf_C", IconColor) + killicon.Add("acf_GL", "HUD/killicons/acf_GL", IconColor) + killicon.Add("acf_HMG", "HUD/killicons/acf_HMG", IconColor) + killicon.Add("acf_HW", "HUD/killicons/acf_HW", IconColor) + killicon.Add("acf_MG", "HUD/killicons/acf_MG", IconColor) + killicon.Add("acf_MO", "HUD/killicons/acf_MO", IconColor) + killicon.Add("acf_RAC", "HUD/killicons/acf_RAC", IconColor) + killicon.Add("acf_SA", "HUD/killicons/acf_SA", IconColor) - end - net.Receive( "ACF_PlayerKilledByPlayer", RecvPlayerKilledByPlayer ) + local function doNothing() + return false + end - local function RecvPlayerKilledSelf() + -- TODO: Overwrite these instead of creating new net messages. + net.Receive("PlayerKilledByPlayer", doNothing) + net.Receive("PlayerKilledNPC", doNothing) + net.Receive("PlayerKilled", doNothing) + net.Receive("NPCKilledNPC", doNothing) - local victim = net.ReadEntity() - if ( !IsValid( victim ) ) then return end - GAMEMODE:AddDeathNotice( nil, 0, "suicide", victim:Name(), victim:Team() ) + net.Receive("ACF_PlayerKilledByPlayer", function() + local victim = net.ReadEntity() + local inflictor = net.ReadString() + local attacker = net.ReadEntity() - end - net.Receive( "ACF_PlayerKilledSelf", RecvPlayerKilledSelf ) + if not IsValid(attacker) then return end + if not IsValid(victim) then return end - local function RecvPlayerKilled() + GAMEMODE:AddDeathNotice(attacker:Name(), attacker:Team(), inflictor, victim:Name(), victim:Team()) + end) - local victim = net.ReadEntity() - if ( !IsValid( victim ) ) then return end - local inflictor = net.ReadString() - local attacker = "#" .. net.ReadString() + net.Receive("ACF_PlayerKilledSelf", function() + local victim = net.ReadEntity() - GAMEMODE:AddDeathNotice( attacker, -1, inflictor, victim:Name(), victim:Team() ) + if not IsValid(victim) then return end - end - net.Receive( "ACF_PlayerKilled", RecvPlayerKilled ) + GAMEMODE:AddDeathNotice(nil, 0, "suicide", victim:Name(), victim:Team()) + end) - local function RecvPlayerKilledNPC() + net.Receive("ACF_PlayerKilled", function() + local victim = net.ReadEntity() + local inflictor = net.ReadString() + local attacker = "#" .. net.ReadString() - local victimtype = net.ReadString() - local victim = "#" .. victimtype - local inflictor = net.ReadString() - local attacker = net.ReadEntity() + if not IsValid(victim) then return end - -- - -- For some reason the killer isn't known to us, so don't proceed. - -- - if ( !IsValid( attacker ) ) then return end + GAMEMODE:AddDeathNotice(attacker, -1, inflictor, victim:Name(), victim:Team()) + end) - GAMEMODE:AddDeathNotice( attacker:Name(), attacker:Team(), inflictor, victim, -1 ) + net.Receive("ACF_PlayerKilledNPC", function() + local victimtype = net.ReadString() + local victim = "#" .. victimtype + local inflictor = net.ReadString() + local attacker = net.ReadEntity() - local bIsLocalPlayer = ( IsValid(attacker) and attacker == LocalPlayer() ) + -- For some reason the killer isn't known to us, so don't proceed. + if not IsValid(attacker) then return end - local bIsEnemy = IsEnemyEntityName( victimtype ) - local bIsFriend = IsFriendEntityName( victimtype ) + GAMEMODE:AddDeathNotice(attacker:Name(), attacker:Team(), inflictor, victim, -1) - if ( bIsLocalPlayer and bIsEnemy ) then - achievements.IncBaddies() - end + local bIsLocalPlayer = IsValid(attacker) and attacker == LocalPlayer() + local bIsEnemy = IsEnemyEntityName(victimtype) + local bIsFriend = IsFriendEntityName(victimtype) - if ( bIsLocalPlayer and bIsFriend ) then - achievements.IncGoodies() - end + if bIsLocalPlayer and bIsEnemy then + achievements.IncBaddies() + end - if ( bIsLocalPlayer and ( !bIsFriend and !bIsEnemy ) ) then - achievements.IncBystander() - end + if bIsLocalPlayer and bIsFriend then + achievements.IncGoodies() + end + if bIsLocalPlayer and not (bIsFriend or bIsEnemy) then + achievements.IncBystander() end - net.Receive( "ACF_PlayerKilledNPC", RecvPlayerKilledNPC ) + end) - local function RecvNPCKilledNPC() + net.Receive("ACF_NPCKilledNPC", function() + local victim = "#" .. net.ReadString() + local inflictor = net.ReadString() + local attacker = "#" .. net.ReadString() - local victim = "#" .. net.ReadString() - local inflictor = net.ReadString() - local attacker = "#" .. net.ReadString() + GAMEMODE:AddDeathNotice(attacker, -1, inflictor, victim, -1) + end) +end - GAMEMODE:AddDeathNotice( attacker, -1, inflictor, victim, -1 ) +hook.Add("InitPostEntity", "ACF Custom Killicons", function() + local Enabled = ACF.GetServerBool("UseKillicons") - end - net.Receive( "ACF_NPCKilledNPC", RecvNPCKilledNPC ) - end -end + ClientSideActions(Enabled) + ServerSideActions(Enabled) + + hook.Remove("InitPostEntity", "ACF Custom Killicons") +end) diff --git a/lua/acf/shared/rounds/acf_roundfunctions.lua b/lua/acf/shared/rounds/acf_roundfunctions.lua deleted file mode 100644 index c1779c735..000000000 --- a/lua/acf/shared/rounds/acf_roundfunctions.lua +++ /dev/null @@ -1,61 +0,0 @@ - -function ACF_RoundBaseGunpowder(PlayerData, Data, ServerData, GUIData) - local BulletMax = ACF.Weapons.Guns[PlayerData.Id].round - GUIData.MaxTotalLength = BulletMax.maxlength * (Data.LengthAdj or 1) - Data.Caliber = ACF.Weapons.Guns[PlayerData.Id].caliber - Data.FrArea = 3.1416 * (Data.Caliber / 2) ^ 2 - Data.Tracer = 0 - - --Check for tracer - if PlayerData.Data10 * 1 > 0 then - Data.Tracer = math.min(5 / Data.Caliber, 2.5) --Tracer space calcs - end - - local PropMax = (BulletMax.propweight * 1000 / ACF.PDensity) / Data.FrArea --Current casing absolute max propellant capacity - local CurLength = (PlayerData.ProjLength + math.min(PlayerData.PropLength, PropMax) + Data.Tracer) - GUIData.MinPropLength = 0.01 - GUIData.MaxPropLength = math.max(math.min(GUIData.MaxTotalLength - CurLength + PlayerData.PropLength, PropMax), GUIData.MinPropLength) --Check if the desired prop lenght fits in the case and doesn't exceed the gun max - GUIData.MinProjLength = Data.Caliber * 1.5 - GUIData.MaxProjLength = math.max(GUIData.MaxTotalLength - CurLength + PlayerData.ProjLength, GUIData.MinProjLength) --Check if the desired proj lenght fits in the case - local Ratio = math.min((GUIData.MaxTotalLength - Data.Tracer) / (PlayerData.ProjLength + math.min(PlayerData.PropLength, PropMax)), 1) --This is to check the current ratio between elements if i need to clamp it - Data.ProjLength = math.Clamp(PlayerData.ProjLength * Ratio, GUIData.MinProjLength, GUIData.MaxProjLength) - Data.PropLength = math.Clamp(PlayerData.PropLength * Ratio, GUIData.MinPropLength, GUIData.MaxPropLength) - Data.PropMass = Data.FrArea * (ACF.AmmoCaseScale ^ 2) * (Data.PropLength * ACF.PDensity / 1000) --Volume of the case as a cylinder * Powder density converted from g to kg - GUIData.ProjVolume = Data.FrArea * Data.ProjLength - Data.RoundVolume = Data.FrArea * ACF.AmmoCaseScale ^ 2 * (Data.ProjLength + Data.PropLength) - - return PlayerData, Data, ServerData, GUIData -end - -function ACF_RoundShellCapacity(Momentum, FrArea, Caliber, ProjLength) - local MinWall = 0.2 + ((Momentum / FrArea) ^ 0.7) / 50 --The minimal shell wall thickness required to survive firing at the current energy level - local Length = math.max(ProjLength - MinWall, 0) - local Radius = math.max((Caliber / 2) - MinWall, 0) - local Volume = 3.1416 * Radius ^ 2 * Length - --Returning the cavity volume and the minimum wall thickness - - return Volume, Length, Radius -end - -function ACF_RicoProbability(Rico, Speed) - local MinAngle = math.min(Rico - Speed / 15, 89) - - return { - Min = math.Round(math.max(MinAngle, 0.1), 1), - Mean = math.Round(math.max(MinAngle + (90 - MinAngle) / 2, 0.1), 1), - Max = 90 - } -end - ---Formula from https://mathscinotes.wordpress.com/2013/10/03/parameter-determination-for-pejsa-velocity-model/ ---not terribly accurate for acf, particularly small caliber (7.62mm off by 120 m/s at 800m), but is good enough for quick indicator ---range in m, vel is m/s -function ACF_PenRanging(MuzzleVel, DragCoef, ProjMass, PenArea, LimitVel, Range) - local V0 = (MuzzleVel * 39.37 * ACF.Scale) --initial velocity - local D0 = (DragCoef * V0 ^ 2 / ACF.DragDiv) --initial drag - local K1 = (D0 / (V0 ^ (3 / 2))) ^ -1 --estimated drag coefficient - local Vel = (math.sqrt(V0) - ((Range * 39.37) / (2 * K1))) ^ 2 - local Pen = (ACF_Kinetic(Vel, ProjMass, LimitVel).Penetration / PenArea) * ACF.KEtoRHA - - return Vel * 0.0254, Pen -end \ No newline at end of file diff --git a/lua/acf/shared/rounds/ap.lua b/lua/acf/shared/rounds/ap.lua deleted file mode 100644 index 158d04093..000000000 --- a/lua/acf/shared/rounds/ap.lua +++ /dev/null @@ -1,211 +0,0 @@ -ACF.AmmoBlacklist.AP = {"MO", "SL", "SB"} -local Round = {} -Round.type = "Ammo" --Tells the spawn menu what entity to spawn -Round.name = "Armour Piercing (AP)" --Human readable name -Round.model = "models/munitions/round_100mm_shot.mdl" --Shell flight model -Round.desc = "A shell made out of a solid piece of steel, meant to penetrate armour" - -function Round.create(_, BulletData) - ACF_CreateBullet(BulletData) -end - --- Function to convert the player's slider data into the complete round data -function Round.convert(_, PlayerData) - local Data = {} - local ServerData = {} - local GUIData = {} - - if not PlayerData.PropLength then - PlayerData.PropLength = 0 - end - - if not PlayerData.ProjLength then - PlayerData.ProjLength = 0 - end - - if not PlayerData.Data10 then - PlayerData.Data10 = 0 - end - - PlayerData, Data, ServerData, GUIData = ACF_RoundBaseGunpowder(PlayerData, Data, ServerData, GUIData) - Data.ProjMass = Data.FrArea * (Data.ProjLength * 7.9 / 1000) --Volume of the projectile as a cylinder * density of steel - Data.ShovePower = 0.2 - Data.PenArea = Data.FrArea ^ ACF.PenAreaMod - Data.DragCoef = ((Data.FrArea / 10000) / Data.ProjMass) - Data.LimitVel = 800 --Most efficient penetration speed in m/s - Data.KETransfert = 0.1 --Kinetic energy transfert to the target for movement purposes - Data.Ricochet = 60 --Base ricochet angle - Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, Data.ProjMass, Data.Caliber) - Data.CartMass = Data.PropMass + Data.ProjMass - - --Only the crates need this part - if SERVER then - ServerData.Id = PlayerData.Id - ServerData.Type = PlayerData.Type - - return table.Merge(Data, ServerData) - end - - --Only the GUI needs this part - if CLIENT then - GUIData = table.Merge(GUIData, Round.getDisplayData(Data)) - - return table.Merge(Data, GUIData) - end -end - -function Round.getDisplayData(Data) - local GUIData = {} - local Energy = ACF_Kinetic(Data.MuzzleVel * 39.37, Data.ProjMass, Data.LimitVel) - GUIData.MaxPen = (Energy.Penetration / Data.PenArea) * ACF.KEtoRHA - - return GUIData -end - -function Round.network(Crate, BulletData) - Crate:SetNWString("AmmoType", "AP") - Crate:SetNWString("AmmoID", BulletData.Id) - Crate:SetNWFloat("Caliber", BulletData.Caliber) - Crate:SetNWFloat("ProjMass", BulletData.ProjMass) - Crate:SetNWFloat("PropMass", BulletData.PropMass) - Crate:SetNWFloat("DragCoef", BulletData.DragCoef) - Crate:SetNWFloat("MuzzleVel", BulletData.MuzzleVel) - Crate:SetNWFloat("Tracer", BulletData.Tracer) -end - -function Round.cratetxt(BulletData) - --local FrArea = BulletData.FrArea - local DData = Round.getDisplayData(BulletData) - - local str = {"Muzzle Velocity: ", math.Round(BulletData.MuzzleVel, 1), " m/s\n", "Max Penetration: ", math.floor(DData.MaxPen), " mm"} --"Cartridge Mass: ", math.Round(TotalMass, 2), MassUnit, "\n", --"Max Pen. Damage: ", math.Round(MaxHP.Damage, 1), " HP\n", - - return table.concat(str) -end - -function Round.propimpact(_, Bullet, Target, HitNormal, HitPos, Bone) - if ACF_Check(Target) then - local Speed = Bullet.Flight:Length() / ACF.Scale - local Energy = ACF_Kinetic(Speed, Bullet.ProjMass, Bullet.LimitVel) - local HitRes = ACF_RoundImpact(Bullet, Speed, Energy, Target, HitPos, HitNormal, Bone) - - if HitRes.Overkill > 0 then - table.insert(Bullet.Filter, Target) --"Penetrate" (Ingoring the prop for the retry trace) - Bullet.Flight = Bullet.Flight:GetNormalized() * (Energy.Kinetic * (1 - HitRes.Loss) * 2000 / Bullet.ProjMass) ^ 0.5 * 39.37 - - return "Penetrated" - elseif HitRes.Ricochet then - return "Ricochet" - else - return false - end - else - table.insert(Bullet.Filter, Target) - - return "Penetrated" - end -end - -function Round.worldimpact(_, Bullet, HitPos, HitNormal) - local Energy = ACF_Kinetic(Bullet.Flight:Length() / ACF.Scale, Bullet.ProjMass, Bullet.LimitVel) - local HitRes = ACF_PenetrateGround(Bullet, Energy, HitPos, HitNormal) - - if HitRes.Penetrated then - return "Penetrated" - elseif HitRes.Ricochet then - return "Ricochet" - else - return false - end -end - -function Round.endflight(Index) - ACF_RemoveBullet(Index) -end - -local DecalIndex = ACF.GetAmmoDecalIndex - -function Round.endeffect(_, Bullet) - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetRadius(Bullet.Caliber) - Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) - - util.Effect("ACF_Impact", Effect) -end - -function Round.pierceeffect(_, Bullet) - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetScale(Bullet.SimFlight:Length()) - Effect:SetMagnitude(Bullet.RoundMass) - Effect:SetRadius(Bullet.Caliber) - Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) - - util.Effect("ACF_Penetration", Effect) -end - -function Round.ricocheteffect(_, Bullet) - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetScale(Bullet.SimFlight:Length()) - Effect:SetMagnitude(Bullet.RoundMass) - Effect:SetRadius(Bullet.Caliber) - Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) - - util.Effect("ACF_Ricochet", Effect) -end - -function Round.guicreate(Panel, Table) - acfmenupanel:AmmoSelect(ACF.AmmoBlacklist.AP) - - acfmenupanel:CPanelText("Desc", "") --Description (Name, Desc) - acfmenupanel:CPanelText("LengthDisplay", "") --Total round length (Name, Desc) - acfmenupanel:AmmoSlider("PropLength", 0, 0, 1000, 3, "Propellant Length", "") --Propellant Length Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("ProjLength", 0, 0, 1000, 3, "Projectile Length", "") --Projectile Length Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoCheckbox("Tracer", "Tracer", "") --Tracer checkbox (Name, Title, Desc) - acfmenupanel:CPanelText("VelocityDisplay", "") --Proj muzzle velocity (Name, Desc) - --acfmenupanel:CPanelText("RicoDisplay", "") --estimated rico chance - acfmenupanel:CPanelText("PenetrationDisplay", "") --Proj muzzle penetration (Name, Desc) - Round.guiupdate(Panel, Table) -end - -function Round.guiupdate(Panel) - local PlayerData = {} - PlayerData.Id = acfmenupanel.AmmoData.Data.id --AmmoSelect GUI - PlayerData.Type = "AP" --Hardcoded, match ACFRoundTypes table index - PlayerData.PropLength = acfmenupanel.AmmoData.PropLength --PropLength slider - PlayerData.ProjLength = acfmenupanel.AmmoData.ProjLength --ProjLength slider - local Tracer = 0 - - if acfmenupanel.AmmoData.Tracer then - Tracer = 1 - end - - PlayerData.Data10 = Tracer --Tracer - local Data = Round.convert(Panel, PlayerData) - RunConsoleCommand("acfmenu_data1", acfmenupanel.AmmoData.Data.id) - RunConsoleCommand("acfmenu_data2", PlayerData.Type) - RunConsoleCommand("acfmenu_data3", Data.PropLength) --For Gun ammo, Data3 should always be Propellant - RunConsoleCommand("acfmenu_data4", Data.ProjLength) --And Data4 total round mass - RunConsoleCommand("acfmenu_data10", Data.Tracer) - - acfmenupanel:AmmoUpdate() - acfmenupanel:AmmoSlider("PropLength", Data.PropLength, Data.MinPropLength, Data.MaxTotalLength, 3, "Propellant Length", "Propellant Mass : " .. (math.floor(Data.PropMass * 1000)) .. " g") --Propellant Length Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("ProjLength", Data.ProjLength, Data.MinProjLength, Data.MaxTotalLength, 3, "Projectile Length", "Projectile Mass : " .. (math.floor(Data.ProjMass * 1000)) .. " g") --Projectile Length Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoCheckbox("Tracer", "Tracer : " .. (math.floor(Data.Tracer * 10) / 10) .. "cm\n", "") --Tracer checkbox (Name, Title, Desc) - acfmenupanel:CPanelText("Desc", ACF.RoundTypes[PlayerData.Type].desc) --Description (Name, Desc) - acfmenupanel:CPanelText("LengthDisplay", "Cartridge Length : " .. (math.floor((Data.PropLength + Data.ProjLength + Data.Tracer) * 100) / 100) .. "/" .. Data.MaxTotalLength .. " cm") --Total round length (Name, Desc) - acfmenupanel:CPanelText("VelocityDisplay", "Muzzle Velocity : " .. math.floor(Data.MuzzleVel * ACF.Scale) .. " m\\s") --Proj muzzle velocity (Name, Desc) - --local RicoAngs = ACF_RicoProbability( Data.Ricochet, Data.MuzzleVel*ACF.Scale ) - --acfmenupanel:CPanelText("RicoDisplay", "Ricochet probability vs impact angle:\n".." 0% @ "..RicoAngs.Min.." degrees\n 50% @ "..RicoAngs.Mean.." degrees\n100% @ "..RicoAngs.Max.." degrees") - local R1V, R1P = ACF_PenRanging(Data.MuzzleVel, Data.DragCoef, Data.ProjMass, Data.PenArea, Data.LimitVel, 300) - local R2V, R2P = ACF_PenRanging(Data.MuzzleVel, Data.DragCoef, Data.ProjMass, Data.PenArea, Data.LimitVel, 800) - acfmenupanel:CPanelText("PenetrationDisplay", "Maximum Penetration : " .. math.floor(Data.MaxPen) .. " mm RHA\n\n300m pen: " .. math.Round(R1P, 0) .. "mm @ " .. math.Round(R1V, 0) .. " m\\s\n800m pen: " .. math.Round(R2P, 0) .. "mm @ " .. math.Round(R2V, 0) .. " m\\s\n\nThe range data is an approximation and may not be entirely accurate.") --Proj muzzle penetration (Name, Desc) -end - -ACF.RoundTypes.AP = Round --Set the round properties - -ACF.RegisterAmmoDecal("AP", "damage/ap_pen", "damage/ap_rico") \ No newline at end of file diff --git a/lua/acf/shared/rounds/apcr.lua b/lua/acf/shared/rounds/apcr.lua deleted file mode 100644 index 6b2d5ed5a..000000000 --- a/lua/acf/shared/rounds/apcr.lua +++ /dev/null @@ -1,216 +0,0 @@ -ACF.AmmoBlacklist.APCR = { "MO", "SL", "HW", "MG", "SB", "GL", "AAM", "ARTY", "ASM", "BOMB", "GBU", "POD", "SAM", "UAR", "FFAR", "FGL" } - -local Round = {} - -Round.type = "Ammo" --Tells the spawn menu what entity to spawn -Round.name = "Armor Piercing, Composite Rigid (APCR)" --Human readable name -Round.model = "models/munitions/round_100mm_shot.mdl" --Shell flight model -Round.desc = "A hardened core munition designed for weapons in the 1940s. Short Cannons only." - -function Round.create(_, BulletData) - ACF_CreateBullet(BulletData) -end - --- Function to convert the player's slider data into the complete round data -function Round.convert(_, PlayerData) - local Data = {} - local ServerData = {} - local GUIData = {} - - if not PlayerData.PropLength then - PlayerData.PropLength = 0 - end - - if not PlayerData.ProjLength then - PlayerData.ProjLength = 0 - end - - if not PlayerData.Data10 then - PlayerData.Data10 = 0 - end - - PlayerData, Data, ServerData, GUIData = ACF_RoundBaseGunpowder(PlayerData, Data, ServerData, GUIData) - - Data.ProjMass = (Data.FrArea / 0.9) * (Data.ProjLength * 7.9 / 1000) * 0.75 --Volume of the projectile as a cylinder * density of steel - Data.ShovePower = 0.2 - Data.PenArea = (Data.FrArea * 0.7) ^ ACF.PenAreaMod -- APCR has a smaller penetrator - Data.DragCoef = (Data.FrArea * 1.25 / 10000) / Data.ProjMass -- But worse drag (Manually fudged to make a meaningful difference) - Data.LimitVel = 900 --Most efficient penetration speed in m/s - Data.KETransfert = 0.1 --Kinetic energy transfert to the target for movement purposes - Data.Ricochet = 55 --Base ricochet angle - Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, Data.ProjMass) - Data.CartMass = Data.PropMass + Data.ProjMass - - --Only the crates need this part - if SERVER then - ServerData.Id = PlayerData.Id - ServerData.Type = PlayerData.Type - return table.Merge(Data,ServerData) - end - - --Only the GUI needs this part - if CLIENT then - GUIData = table.Merge(GUIData, Round.getDisplayData(Data)) - return table.Merge(Data,GUIData) - end -end - -function Round.getDisplayData(Data) - local GUIData = {} - local Energy = ACF_Kinetic(Data.MuzzleVel * 39.37, Data.ProjMass, Data.LimitVel) - GUIData.MaxPen = (Energy.Penetration / Data.PenArea) * ACF.KEtoRHA - return GUIData -end - -function Round.network(Crate, BulletData) - Crate:SetNWString("AmmoType", "APCR") - Crate:SetNWString("AmmoID", BulletData.Id) - Crate:SetNWFloat("Caliber", BulletData.Caliber) - Crate:SetNWFloat("ProjMass", BulletData.ProjMass) - Crate:SetNWFloat("PropMass", BulletData.PropMass) - Crate:SetNWFloat("DragCoef", BulletData.DragCoef) - Crate:SetNWFloat("MuzzleVel", BulletData.MuzzleVel) - Crate:SetNWFloat("Tracer", BulletData.Tracer) -end - -function Round.cratetxt(BulletData) - local DData = Round.getDisplayData(BulletData) - - local str = - { - "Muzzle Velocity: ", math.Round(BulletData.MuzzleVel, 1), " m/s\n", - "Max Penetration: ", math.floor(DData.MaxPen), " mm" - } - - return table.concat(str) -end - -function Round.propimpact(_, Bullet, Target, HitNormal, HitPos, Bone) - if ACF_Check(Target) then - local Speed = Bullet.Flight:Length() / ACF.Scale - local Energy = ACF_Kinetic(Speed, Bullet.ProjMass, Bullet.LimitVel) - local HitRes = ACF_RoundImpact(Bullet, Speed, Energy, Target, HitPos, HitNormal, Bone) - - if HitRes.Overkill > 0 then - table.insert(Bullet.Filter, Target) --"Penetrate" (Ingoring the prop for the retry trace) - Bullet.Flight = Bullet.Flight:GetNormalized() * (Energy.Kinetic * (1-HitRes.Loss) * 2000 / Bullet.ProjMass) ^ 0.5 * 39.37 - return "Penetrated" - elseif HitRes.Ricochet then - return "Ricochet" - else - return false - end - end - - table.insert(Bullet.Filter, Target) - - return "Penetrated" -end - -function Round.worldimpact(_, Bullet, HitPos, HitNormal) - local Energy = ACF_Kinetic(Bullet.Flight:Length() / ACF.Scale, Bullet.ProjMass, Bullet.LimitVel) - local HitRes = ACF_PenetrateGround(Bullet, Energy, HitPos, HitNormal) - - if HitRes.Penetrated then - return "Penetrated" - elseif HitRes.Ricochet then - return "Ricochet" - else - return false - end -end - -function Round.endflight(Index) - ACF_RemoveBullet(Index) -end - -local DecalIndex = ACF.GetAmmoDecalIndex - -function Round.endeffect(_, Bullet) - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetRadius(Bullet.Caliber) - Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) - - util.Effect("ACF_Impact", Effect) -end - -function Round.pierceeffect(_, Bullet) - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetScale(Bullet.SimFlight:Length()) - Effect:SetMagnitude(Bullet.RoundMass) - Effect:SetRadius(Bullet.Caliber) - Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) - - util.Effect("ACF_Penetration", Effect) -end - -function Round.ricocheteffect(_, Bullet) - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetScale(Bullet.SimFlight:Length()) - Effect:SetMagnitude(Bullet.RoundMass) - Effect:SetRadius(Bullet.Caliber) - Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) - - util.Effect("ACF_Ricochet", Effect) -end - -function Round.guicreate(Panel, Table) - acfmenupanel:AmmoSelect(ACF.AmmoBlacklist.APCR) - - acfmenupanel:CPanelText("Desc", "") --Description (Name, Desc) - acfmenupanel:CPanelText("LengthDisplay", "") --Total round length (Name, Desc) - acfmenupanel:AmmoSlider("PropLength", 0, 0, 1000, 3, "Propellant Length", "") --Propellant Length Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("ProjLength", 0, 0, 1000, 3, "Penetrator Length", "") --Projectile Length Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoCheckbox("Tracer", "Tracer", "") --Tracer checkbox (Name, Title, Desc) - acfmenupanel:CPanelText("VelocityDisplay", "") --Proj muzzle velocity (Name, Desc) - acfmenupanel:CPanelText("PenetrationDisplay", "") --Proj muzzle penetration (Name, Desc) - - Round.guiupdate(Panel, Table) -end - -function Round.guiupdate(Panel) - local PlayerData = {} - PlayerData.Id = acfmenupanel.AmmoData.Data.id --AmmoSelect GUI - PlayerData.Type = "APCR" --Hardcoded, match ACFRoundTypes table index - PlayerData.PropLength = acfmenupanel.AmmoData.PropLength --PropLength slider - PlayerData.ProjLength = acfmenupanel.AmmoData.ProjLength --ProjLength slider - local Tracer = 0 - - if acfmenupanel.AmmoData.Tracer then - Tracer = 1 - end - - PlayerData.Data10 = Tracer --Tracer - - local Data = Round.convert(Panel, PlayerData) - RunConsoleCommand("acfmenu_data1", acfmenupanel.AmmoData.Data.id) - RunConsoleCommand("acfmenu_data2", PlayerData.Type) - RunConsoleCommand("acfmenu_data3", Data.PropLength) --For Gun ammo, Data3 should always be Propellant - RunConsoleCommand("acfmenu_data4", Data.ProjLength) --And Data4 total round mass - RunConsoleCommand("acfmenu_data10", Data.Tracer) - - acfmenupanel:AmmoUpdate() - acfmenupanel:AmmoSlider("PropLength", Data.PropLength, Data.MinPropLength, Data.MaxTotalLength, 3, "Propellant Length", "Propellant Mass : " .. (math.floor(Data.PropMass * 1000)) .. " g") --Propellant Length Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("ProjLength", Data.ProjLength, Data.MinProjLength, Data.MaxTotalLength, 3, "Penetrator Length", "Projectile Mass : " .. (math.floor(Data.ProjMass * 1000)) .. " g") --Projectile Length Slider (Name, Min, Max, Decimals, Title, Desc) - - acfmenupanel:AmmoCheckbox("Tracer", "Tracer : " .. (math.floor(Data.Tracer * 10) / 10) .. "cm\n", "") --Tracer checkbox (Name, Title, Desc) - - acfmenupanel:CPanelText("Desc", ACF.RoundTypes[PlayerData.Type].desc) --Description (Name, Desc) - acfmenupanel:CPanelText("LengthDisplay", "Cartridge Length : " .. (math.floor((Data.PropLength + Data.ProjLength + Data.Tracer) * 100) / 100) .. "/" .. Data.MaxTotalLength .. " cm") --Total round length (Name, Desc) - acfmenupanel:CPanelText("VelocityDisplay", "Muzzle Velocity : " .. math.floor(Data.MuzzleVel * ACF.Scale) .. " m\\s") --Proj muzzle velocity (Name, Desc) - - local R1V, R1P = ACF_PenRanging(Data.MuzzleVel, Data.DragCoef, Data.ProjMass, Data.PenArea, Data.LimitVel, 300) - local R2V, R2P = ACF_PenRanging(Data.MuzzleVel, Data.DragCoef, Data.ProjMass, Data.PenArea, Data.LimitVel, 800) - - acfmenupanel:CPanelText("PenetrationDisplay", "Maximum Penetration : " .. math.floor(Data.MaxPen) .. " mm RHA\n\n300m pen: " .. math.Round(R1P,0) .. "mm @ " .. math.Round(R1V,0) .. " m\\s\n800m pen: " .. math.Round(R2P,0) .. "mm @ " .. math.Round(R2V,0) .. " m\\s\n\nThe range data is an approximation and may not be entirely accurate.") --Proj muzzle penetration (Name, Desc) -end - -ACF.RoundTypes.APCR = Round --Set the round properties - -ACF.RegisterAmmoDecal("APCR", "damage/apcr_pen", "damage/apcr_rico") diff --git a/lua/acf/shared/rounds/apds.lua b/lua/acf/shared/rounds/apds.lua deleted file mode 100644 index 43d29fde1..000000000 --- a/lua/acf/shared/rounds/apds.lua +++ /dev/null @@ -1,223 +0,0 @@ -ACF.AmmoBlacklist.APDS = { "MO", "SL", "HW", "SC", "MG", "SB", "GL", "HMG", "AAM", "ARTY", "ASM", "BOMB", "GBU", "POD", "SAM", "UAR", "FFAR", "FGL" } - -local Round = {} - -Round.type = "Ammo" --Tells the spawn menu what entity to spawn -Round.name = "Armor Piercing, Discarding Sabot (APDS)" --Human readable name -Round.model = "models/munitions/round_100mm_shot.mdl" --Shell flight model -Round.desc = "A subcaliber munition designed to trade damage for penetration. Loses energy quickly over distance." - -function Round.create(_, BulletData) - ACF_CreateBullet(BulletData) -end - --- Function to convert the player's slider data into the complete round data -function Round.convert(_, PlayerData) - local Data = {} - local ServerData = {} - local GUIData = {} - - if not PlayerData.PropLength then - PlayerData.PropLength = 0 - end - - if not PlayerData.ProjLength then - PlayerData.ProjLength = 0 - end - - if not PlayerData.Data10 then - PlayerData.Data10 = 0 - end - - PlayerData, Data, ServerData, GUIData = ACF_RoundBaseGunpowder(PlayerData, Data, ServerData, GUIData) - - local SubCaliberRatio = 0.375 -- Ratio of projectile to gun caliber - local Area = 3.1416 * (Data.Caliber * 0.5 * SubCaliberRatio) ^ 2 - - local DartMass = (Area / 1.5) * (Data.ProjLength * 7.9 / 1000) -- Volume of the projectile as a cylinder * density of steel - - local Cylinder = (3.1416 * (Data.Caliber * 0.5) ^ 2) * Data.ProjLength * 0.5 -- A cylinder 1/2 the length of the projectile - local Hole = Area * Data.ProjLength * 0.25 -- Volume removed by the hole the dart passes through - local SabotMass = (Cylinder - Hole) * 2.7 * 0.65 * 0.001 -- Aluminum sabot - - Data.ProjMass = DartMass - Data.ShovePower = 0.2 - Data.PenArea = Area ^ ACF.PenAreaMod - Data.DragCoef = (Area / 10000) / Data.ProjMass - Data.LimitVel = 950 --Most efficient penetration speed in m/s - Data.KETransfert = 0.1 --Kinetic energy transfert to the target for movement purposes - Data.Ricochet = 80 --Base ricochet angle - Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, DartMass + SabotMass) - Data.CartMass = Data.PropMass + DartMass + SabotMass - - --Only the crates need this part - if SERVER then - ServerData.Id = PlayerData.Id - ServerData.Type = PlayerData.Type - return table.Merge(Data,ServerData) - end - - --Only the GUI needs this part - if CLIENT then - GUIData = table.Merge(GUIData, Round.getDisplayData(Data)) - return table.Merge(Data,GUIData) - end -end - -function Round.getDisplayData(Data) - local GUIData = {} - local Energy = ACF_Kinetic(Data.MuzzleVel * 39.37, Data.ProjMass, Data.LimitVel) - GUIData.MaxPen = (Energy.Penetration / Data.PenArea) * ACF.KEtoRHA - return GUIData -end - -function Round.network(Crate, BulletData) - Crate:SetNWString("AmmoType", "APDS") - Crate:SetNWString("AmmoID", BulletData.Id) - Crate:SetNWFloat("Caliber", BulletData.Caliber) - Crate:SetNWFloat("ProjMass", BulletData.ProjMass) - Crate:SetNWFloat("PropMass", BulletData.PropMass) - Crate:SetNWFloat("DragCoef", BulletData.DragCoef) - Crate:SetNWFloat("MuzzleVel", BulletData.MuzzleVel) - Crate:SetNWFloat("Tracer", BulletData.Tracer) -end - -function Round.cratetxt(BulletData) - local DData = Round.getDisplayData(BulletData) - - local str = - { - "Muzzle Velocity: ", math.Round(BulletData.MuzzleVel, 1), " m/s\n", - "Max Penetration: ", math.floor(DData.MaxPen), " mm" - } - - return table.concat(str) -end - -function Round.propimpact(_, Bullet, Target, HitNormal, HitPos, Bone) - if ACF_Check(Target) then - local Speed = Bullet.Flight:Length() / ACF.Scale - local Energy = ACF_Kinetic(Speed, Bullet.ProjMass, Bullet.LimitVel) - local HitRes = ACF_RoundImpact(Bullet, Speed, Energy, Target, HitPos, HitNormal, Bone) - - if HitRes.Overkill > 0 then - table.insert(Bullet.Filter, Target) --"Penetrate" (Ingoring the prop for the retry trace) - Bullet.Flight = Bullet.Flight:GetNormalized() * (Energy.Kinetic * (1 - HitRes.Loss) * 2000 / Bullet.ProjMass) ^ 0.5 * 39.37 - return "Penetrated" - elseif HitRes.Ricochet then - return "Ricochet" - else - return false - end - end - - table.insert(Bullet.Filter, Target) - - return "Penetrated" -end - -function Round.worldimpact(_, Bullet, HitPos, HitNormal) - local Energy = ACF_Kinetic(Bullet.Flight:Length() / ACF.Scale, Bullet.ProjMass, Bullet.LimitVel) - local HitRes = ACF_PenetrateGround(Bullet, Energy, HitPos, HitNormal) - - if HitRes.Penetrated then - return "Penetrated" - elseif HitRes.Ricochet then - return "Ricochet" - else - return false - end -end - -function Round.endflight(Index) - ACF_RemoveBullet(Index) -end - -local DecalIndex = ACF.GetAmmoDecalIndex - -function Round.endeffect(_, Bullet) - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetRadius(Bullet.Caliber) - Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) - - util.Effect("ACF_Impact", Effect) -end - -function Round.pierceeffect(_, Bullet) - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetScale(Bullet.SimFlight:Length()) - Effect:SetMagnitude(Bullet.RoundMass) - Effect:SetRadius(Bullet.Caliber) - Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) - - util.Effect("ACF_Penetration", Effect) -end - -function Round.ricocheteffect(_, Bullet) - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetScale(Bullet.SimFlight:Length()) - Effect:SetMagnitude(Bullet.RoundMass) - Effect:SetRadius(Bullet.Caliber) - Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) - - util.Effect("ACF_Ricochet", Effect) -end - -function Round.guicreate(Panel, Table) - acfmenupanel:AmmoSelect(ACF.AmmoBlacklist.APDS) - - acfmenupanel:CPanelText("Desc", "") --Description (Name, Desc) - acfmenupanel:CPanelText("LengthDisplay", "") --Total round length (Name, Desc) - acfmenupanel:AmmoSlider("PropLength", 0, 0, 1000, 3, "Propellant Length", "") --Propellant Length Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("ProjLength", 0, 0, 1000, 3, "Penetrator Length", "") --Projectile Length Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoCheckbox("Tracer", "Tracer", "") --Tracer checkbox (Name, Title, Desc) - acfmenupanel:CPanelText("VelocityDisplay", "") --Proj muzzle velocity (Name, Desc) - acfmenupanel:CPanelText("PenetrationDisplay", "") --Proj muzzle penetration (Name, Desc) - - Round.guiupdate(Panel, Table) -end - -function Round.guiupdate(Panel) - local PlayerData = {} - PlayerData.Id = acfmenupanel.AmmoData.Data.id --AmmoSelect GUI - PlayerData.Type = "APDS" --Hardcoded, match ACFRoundTypes table index - PlayerData.PropLength = acfmenupanel.AmmoData.PropLength --PropLength slider - PlayerData.ProjLength = acfmenupanel.AmmoData.ProjLength --ProjLength slider - local Tracer = 0 - - if acfmenupanel.AmmoData.Tracer then - Tracer = 1 - end - - PlayerData.Data10 = Tracer --Tracer - - local Data = Round.convert(Panel, PlayerData) - RunConsoleCommand("acfmenu_data1", acfmenupanel.AmmoData.Data.id) - RunConsoleCommand("acfmenu_data2", PlayerData.Type) - RunConsoleCommand("acfmenu_data3", Data.PropLength) --For Gun ammo, Data3 should always be Propellant - RunConsoleCommand("acfmenu_data4", Data.ProjLength) --And Data4 total round mass - RunConsoleCommand("acfmenu_data10", Data.Tracer) - - acfmenupanel:AmmoUpdate() - acfmenupanel:AmmoSlider("PropLength", Data.PropLength, Data.MinPropLength, Data.MaxTotalLength, 3, "Propellant Length", "Propellant Mass : " .. (math.floor(Data.PropMass * 1000)) .. " g") --Propellant Length Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("ProjLength", Data.ProjLength, Data.MinProjLength, Data.MaxTotalLength, 3, "Penetrator Length", "Projectile Mass : " .. (math.floor(Data.ProjMass * 1000)) .. " g") --Projectile Length Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoCheckbox("Tracer", "Tracer : " .. (math.floor(Data.Tracer * 10) / 10) .. "cm\n", "") --Tracer checkbox (Name, Title, Desc) - acfmenupanel:CPanelText("Desc", ACF.RoundTypes[PlayerData.Type].desc) --Description (Name, Desc) - acfmenupanel:CPanelText("LengthDisplay", "Cartridge Length : " .. (math.floor((Data.PropLength + Data.ProjLength + Data.Tracer) * 100) / 100) .. "/" .. Data.MaxTotalLength .. " cm") --Total round length (Name, Desc) - acfmenupanel:CPanelText("VelocityDisplay", "Muzzle Velocity : " .. math.floor(Data.MuzzleVel * ACF.Scale) .. " m\\s") --Proj muzzle velocity (Name, Desc) - - local R1V, R1P = ACF_PenRanging(Data.MuzzleVel, Data.DragCoef, Data.ProjMass, Data.PenArea, Data.LimitVel, 300) - local R2V, R2P = ACF_PenRanging(Data.MuzzleVel, Data.DragCoef, Data.ProjMass, Data.PenArea, Data.LimitVel, 800) - - acfmenupanel:CPanelText("PenetrationDisplay", "Maximum Penetration : " .. math.floor(Data.MaxPen) .. " mm RHA\n\n300m pen: " .. math.Round(R1P,0) .. "mm @ " .. math.Round(R1V,0) .. " m\\s\n800m pen: " .. math.Round(R2P,0) .. "mm @ " .. math.Round(R2V,0) .. " m\\s\n\nThe range data is an approximation and may not be entirely accurate.") --Proj muzzle penetration (Name, Desc) -end - -ACF.RoundTypes.APDS = Round --Set the round properties - -ACF.RegisterAmmoDecal("APDS", "damage/apcr_pen", "damage/apcr_rico") diff --git a/lua/acf/shared/rounds/apfsds.lua b/lua/acf/shared/rounds/apfsds.lua deleted file mode 100644 index 855c2598a..000000000 --- a/lua/acf/shared/rounds/apfsds.lua +++ /dev/null @@ -1,223 +0,0 @@ -ACF.AmmoBlacklist.APFSDS = { "MO", "SL", "C", "HW", "AC", "SC", "SA", "MG", "AL", "RAC", "GL", "HMG", "AAM", "ARTY", "ASM", "BOMB", "GBU", "POD", "SAM", "UAR", "FFAR", "FGL" } - -local Round = {} - -Round.type = "Ammo" --Tells the spawn menu what entity to spawn -Round.name = "AP Fin Stabilized, Discarding Sabot (APFSDS)" --Human readable name -Round.model = "models/munitions/dart_100mm.mdl" --Shell flight model -Round.desc = "A fin stabilized sabot munition designed to trade damage for superior penetration and long range effectiveness." - -function Round.create(_, BulletData) - ACF_CreateBullet(BulletData) -end - --- Function to convert the player's slider data into the complete round data -function Round.convert(_, PlayerData) - local Data = {} - local ServerData = {} - local GUIData = {} - - if not PlayerData.PropLength then - PlayerData.PropLength = 0 - end - - if not PlayerData.ProjLength then - PlayerData.ProjLength = 0 - end - - if not PlayerData.Data10 then - PlayerData.Data10 = 0 - end - - PlayerData, Data, ServerData, GUIData = ACF_RoundBaseGunpowder(PlayerData, Data, ServerData, GUIData) - - local SubCaliberRatio = 0.29 -- Ratio of projectile to gun caliber - local Area = 3.1416 * (Data.Caliber * 0.5 * SubCaliberRatio) ^ 2 - - local DartMass = (Area / 1.5) * (Data.ProjLength * 7.9 / 1000) -- Volume of the projectile as a cylinder * density of steel - - local Cylinder = (3.1416 * (Data.Caliber * 0.5) ^ 2) * Data.ProjLength * 0.25 -- A cylinder 1/4 the length of the projectile - local Hole = Area * Data.ProjLength * 0.25 -- Volume removed by the hole the dart passes through - local SabotMass = (Cylinder - Hole) * 2.7 * 0.25 * 0.001 -- A cylinder with a hole the size of the dart in it and im no math wizard so we're just going to take off 3/4 of the mass for the cutout since sabots are shaped like this: ][ - - Data.ProjMass = DartMass - Data.ShovePower = 0.2 - Data.PenArea = Area ^ ACF.PenAreaMod - Data.DragCoef = (Area / 10000) / Data.ProjMass - Data.LimitVel = 1000 --Most efficient penetration speed in m/s - Data.KETransfert = 0.1 --Kinetic energy transfert to the target for movement purposes - Data.Ricochet = 80 --Base ricochet angle - Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, DartMass + SabotMass) - Data.CartMass = Data.PropMass + DartMass + SabotMass - - --Only the crates need this part - if SERVER then - ServerData.Id = PlayerData.Id - ServerData.Type = PlayerData.Type - return table.Merge(Data, ServerData) - end - - --Only the GUI needs this part - if CLIENT then - GUIData = table.Merge(GUIData, Round.getDisplayData(Data)) - return table.Merge(Data, GUIData) - end -end - -function Round.getDisplayData(Data) - local GUIData = {} - local Energy = ACF_Kinetic(Data.MuzzleVel * 39.37, Data.ProjMass, Data.LimitVel) - GUIData.MaxPen = (Energy.Penetration / Data.PenArea) * ACF.KEtoRHA - return GUIData -end - -function Round.network(Crate, BulletData) - Crate:SetNWString("AmmoType", "APFSDS") - Crate:SetNWString("AmmoID", BulletData.Id) - Crate:SetNWFloat("Caliber", BulletData.Caliber) - Crate:SetNWFloat("ProjMass", BulletData.ProjMass) - Crate:SetNWFloat("PropMass", BulletData.PropMass) - Crate:SetNWFloat("DragCoef", BulletData.DragCoef) - Crate:SetNWFloat("MuzzleVel", BulletData.MuzzleVel) - Crate:SetNWFloat("Tracer", BulletData.Tracer) -end - -function Round.cratetxt(BulletData) - local DData = Round.getDisplayData(BulletData) - - local str = - { - "Muzzle Velocity: ", math.Round(BulletData.MuzzleVel, 1), " m/s\n", - "Max Penetration: ", math.floor(DData.MaxPen), " mm" - } - - return table.concat(str) -end - -function Round.propimpact(_, Bullet, Target, HitNormal, HitPos, Bone) - if ACF_Check(Target) then - local Speed = Bullet.Flight:Length() / ACF.Scale - local Energy = ACF_Kinetic(Speed, Bullet.ProjMass, Bullet.LimitVel) - local HitRes = ACF_RoundImpact(Bullet, Speed, Energy, Target, HitPos, HitNormal, Bone) - - if HitRes.Overkill > 0 then - table.insert(Bullet.Filter, Target) --"Penetrate" (Ingoring the prop for the retry trace) - Bullet.Flight = Bullet.Flight:GetNormalized() * (Energy.Kinetic * (1 - HitRes.Loss) * 2000 / Bullet.ProjMass) ^ 0.5 * 39.37 - return "Penetrated" - elseif HitRes.Ricochet then - return "Ricochet" - else - return false - end - end - - table.insert(Bullet.Filter, Target) - - return "Penetrated" -end - -function Round.worldimpact(_, Bullet, HitPos, HitNormal) - local Energy = ACF_Kinetic(Bullet.Flight:Length() / ACF.Scale, Bullet.ProjMass, Bullet.LimitVel) - local HitRes = ACF_PenetrateGround(Bullet, Energy, HitPos, HitNormal) - - if HitRes.Penetrated then - return "Penetrated" - elseif HitRes.Ricochet then - return "Ricochet" - else - return false - end -end - -function Round.endflight(Index) - ACF_RemoveBullet(Index) -end - -local DecalIndex = ACF.GetAmmoDecalIndex - -function Round.endeffect(_, Bullet) - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetRadius(Bullet.Caliber) - Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) - - util.Effect("ACF_Impact", Effect) -end - -function Round.pierceeffect(_, Bullet) - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetScale(Bullet.SimFlight:Length()) - Effect:SetMagnitude(Bullet.RoundMass) - Effect:SetRadius(Bullet.Caliber) - Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) - - util.Effect("ACF_Penetration", Effect) -end - -function Round.ricocheteffect(_, Bullet) - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetScale(Bullet.SimFlight:Length()) - Effect:SetMagnitude(Bullet.RoundMass) - Effect:SetRadius(Bullet.Caliber) - Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) - - util.Effect("ACF_Ricochet", Effect) -end - -function Round.guicreate(Panel, Table) - acfmenupanel:AmmoSelect(ACF.AmmoBlacklist.APFSDS) - - acfmenupanel:CPanelText("Desc", "") --Description (Name, Desc) - acfmenupanel:CPanelText("LengthDisplay", "") --Total round length (Name, Desc) - acfmenupanel:AmmoSlider("PropLength", 0, 0, 1000, 3, "Propellant Length", "") --Propellant Length Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("ProjLength", 0, 0, 1000, 3, "Penetrator Length", "") --Projectile Length Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoCheckbox("Tracer", "Tracer", "") --Tracer checkbox (Name, Title, Desc) - acfmenupanel:CPanelText("VelocityDisplay", "") --Proj muzzle velocity (Name, Desc) - acfmenupanel:CPanelText("PenetrationDisplay", "") --Proj muzzle penetration (Name, Desc) - - Round.guiupdate(Panel, Table) -end - -function Round.guiupdate(Panel) - local PlayerData = {} - PlayerData.Id = acfmenupanel.AmmoData.Data.id --AmmoSelect GUI - PlayerData.Type = "APFSDS" --Hardcoded, match ACFRoundTypes table index - PlayerData.PropLength = acfmenupanel.AmmoData.PropLength --PropLength slider - PlayerData.ProjLength = acfmenupanel.AmmoData.ProjLength --ProjLength slider - local Tracer = 0 - - if acfmenupanel.AmmoData.Tracer then - Tracer = 1 - end - - PlayerData.Data10 = Tracer --Tracer - - local Data = Round.convert(Panel, PlayerData) - RunConsoleCommand("acfmenu_data1", acfmenupanel.AmmoData.Data.id) - RunConsoleCommand("acfmenu_data2", PlayerData.Type) - RunConsoleCommand("acfmenu_data3", Data.PropLength) --For Gun ammo, Data3 should always be Propellant - RunConsoleCommand("acfmenu_data4", Data.ProjLength) --And Data4 total round mass - RunConsoleCommand("acfmenu_data10", Data.Tracer) - - acfmenupanel:AmmoUpdate() - acfmenupanel:AmmoSlider("PropLength", Data.PropLength, Data.MinPropLength, Data.MaxTotalLength, 3, "Propellant Length", "Propellant Mass : " .. (math.floor(Data.PropMass * 1000)) .. " g") --Propellant Length Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("ProjLength", Data.ProjLength, Data.MinProjLength, Data.MaxTotalLength, 3, "Penetrator Length", "Projectile Mass : " .. (math.floor(Data.ProjMass * 1000)) .. " g") --Projectile Length Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoCheckbox("Tracer", "Tracer : " .. (math.floor(Data.Tracer * 10) / 10) .. "cm\n", "") --Tracer checkbox (Name, Title, Desc) - acfmenupanel:CPanelText("Desc", ACF.RoundTypes[PlayerData.Type].desc) --Description (Name, Desc) - acfmenupanel:CPanelText("LengthDisplay", "Cartridge Length : " .. (math.floor((Data.PropLength + Data.ProjLength + Data.Tracer) * 100) / 100) .. "/" .. Data.MaxTotalLength .. " cm") --Total round length (Name, Desc) - acfmenupanel:CPanelText("VelocityDisplay", "Muzzle Velocity : " .. math.floor(Data.MuzzleVel * ACF.Scale) .. " m\\s") --Proj muzzle velocity (Name, Desc) - - local R1V, R1P = ACF_PenRanging(Data.MuzzleVel, Data.DragCoef, Data.ProjMass, Data.PenArea, Data.LimitVel, 300) - local R2V, R2P = ACF_PenRanging(Data.MuzzleVel, Data.DragCoef, Data.ProjMass, Data.PenArea, Data.LimitVel, 800) - - acfmenupanel:CPanelText("PenetrationDisplay", "Maximum Penetration : " .. math.floor(Data.MaxPen) .. " mm RHA\n\n300m pen: " .. math.Round(R1P,0) .. "mm @ " .. math.Round(R1V,0) .. " m\\s\n800m pen: " .. math.Round(R2P,0) .. "mm @ " .. math.Round(R2V,0) .. " m\\s\n\nThe range data is an approximation and may not be entirely accurate.") --Proj muzzle penetration (Name, Desc) -end - -ACF.RoundTypes.APFSDS = Round --Set the round properties - -ACF.RegisterAmmoDecal("APFSDS", "damage/apcr_pen", "damage/apcr_rico") diff --git a/lua/acf/shared/rounds/aphe.lua b/lua/acf/shared/rounds/aphe.lua deleted file mode 100644 index 2139a5dd0..000000000 --- a/lua/acf/shared/rounds/aphe.lua +++ /dev/null @@ -1,246 +0,0 @@ -ACF.AmmoBlacklist.APHE = {"MO", "MG", "RAC", "SL"} -local Round = {} -Round.type = "Ammo" --Tells the spawn menu what entity to spawn -Round.name = "Armour Piercing Explosive (APHE)" --Human readable name -Round.model = "models/munitions/round_100mm_shot.mdl" --Shell flight model -Round.desc = "An armour piercing round with a cavity for High explosives. Less capable of defeating armour than plain Armour Piercing, but will explode after penetration" - -function Round.create(_, BulletData) - ACF_CreateBullet(BulletData) -end - --- Function to convert the player's slider data into the complete round data -function Round.convert(_, PlayerData) - local Data = {} - local ServerData = {} - local GUIData = {} - - if not PlayerData.PropLength then - PlayerData.PropLength = 0 - end - - if not PlayerData.ProjLength then - PlayerData.ProjLength = 0 - end - - PlayerData.Data5 = math.max(PlayerData.Data5 or 0, 0) - - if not PlayerData.Data10 then - PlayerData.Data10 = 0 - end - - PlayerData, Data, ServerData, GUIData = ACF_RoundBaseGunpowder(PlayerData, Data, ServerData, GUIData) - --Shell sturdiness calcs - Data.ProjMass = math.max(GUIData.ProjVolume - PlayerData.Data5, 0) * 7.9 / 1000 + math.min(PlayerData.Data5, GUIData.ProjVolume) * ACF.HEDensity / 1000 --Volume of the projectile as a cylinder - Volume of the filler * density of steel + Volume of the filler * density of TNT - Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, Data.ProjMass, Data.Caliber) - local Energy = ACF_Kinetic(Data.MuzzleVel * 39.37, Data.ProjMass, Data.LimitVel) - local MaxVol = ACF_RoundShellCapacity(Energy.Momentum, Data.FrArea, Data.Caliber, Data.ProjLength) - GUIData.MinFillerVol = 0 - GUIData.MaxFillerVol = math.min(GUIData.ProjVolume, MaxVol * 0.9) - GUIData.FillerVol = math.min(PlayerData.Data5, GUIData.MaxFillerVol) - Data.FillerMass = GUIData.FillerVol * ACF.HEDensity / 1000 - Data.ProjMass = math.max(GUIData.ProjVolume - GUIData.FillerVol, 0) * 7.9 / 1000 + Data.FillerMass - Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, Data.ProjMass, Data.Caliber) - --Random bullshit left - Data.ShovePower = 0.1 - Data.PenArea = Data.FrArea ^ ACF.PenAreaMod - Data.DragCoef = ((Data.FrArea / 10000) / Data.ProjMass) - Data.LimitVel = 700 --Most efficient penetration speed in m/s - Data.KETransfert = 0.1 --Kinetic energy transfert to the target for movement purposes - Data.Ricochet = 65 --Base ricochet angle - Data.CartMass = Data.PropMass + Data.ProjMass - - --Only the crates need this part - if SERVER then - ServerData.Id = PlayerData.Id - ServerData.Type = PlayerData.Type - - return table.Merge(Data, ServerData) - end - - --Only the GUI needs this part - if CLIENT then - GUIData = table.Merge(GUIData, Round.getDisplayData(Data)) - - return table.Merge(Data, GUIData) - end -end - -function Round.getDisplayData(Data) - local GUIData = {} - local Energy = ACF_Kinetic(Data.MuzzleVel * 39.37, Data.ProjMass, Data.LimitVel) - GUIData.MaxPen = (Energy.Penetration / Data.PenArea) * ACF.KEtoRHA - GUIData.BlastRadius = Data.FillerMass ^ 0.33 * 8 - local FragMass = Data.ProjMass - Data.FillerMass - GUIData.Fragments = math.max(math.floor((Data.FillerMass / FragMass) * ACF.HEFrag), 2) - GUIData.FragMass = FragMass / GUIData.Fragments - GUIData.FragVel = (Data.FillerMass * ACF.HEPower * 1000 / GUIData.FragMass / GUIData.Fragments) ^ 0.5 - - return GUIData -end - -function Round.network(Crate, BulletData) - Crate:SetNWString("AmmoType", "APHE") - Crate:SetNWString("AmmoID", BulletData.Id) - Crate:SetNWFloat("Caliber", BulletData.Caliber) - Crate:SetNWFloat("ProjMass", BulletData.ProjMass) - Crate:SetNWFloat("FillerMass", BulletData.FillerMass) - Crate:SetNWFloat("PropMass", BulletData.PropMass) - Crate:SetNWFloat("DragCoef", BulletData.DragCoef) - Crate:SetNWFloat("MuzzleVel", BulletData.MuzzleVel) - Crate:SetNWFloat("Tracer", BulletData.Tracer) -end - -function Round.cratetxt(BulletData) - local DData = Round.getDisplayData(BulletData) - local str = { - "Muzzle Velocity: ", math.Round(BulletData.MuzzleVel, 1), " m/s\n", - "Max Penetration: ", math.floor(DData.MaxPen), "mm\n", - "Blast Radius: ", math.Round(DData.BlastRadius, 1), "m\n", - "Blast Energy: ", math.floor(BulletData.FillerMass * ACF.HEPower), "kJ\n", - "Filler Mass: ", math.Round(BulletData.FillerMass * 1000, 2), "g\n", - "Avg. Frag Mass: ", math.Round(DData.FragMass * 1000, 2), "g\n", - "Case Mass: ", math.Round((BulletData.ProjMass - BulletData.FillerMass) * 1000, 2), "g", - } - - return table.concat(str) -end - -function Round.propimpact(_, Bullet, Target, HitNormal, HitPos, Bone) - if ACF_Check(Target) then - local Speed = Bullet.Flight:Length() / ACF.Scale - local Energy = ACF_Kinetic(Speed, Bullet.ProjMass, Bullet.LimitVel) - local HitRes = ACF_RoundImpact(Bullet, Speed, Energy, Target, HitPos, HitNormal, Bone) - - if HitRes.Overkill > 0 then - table.insert(Bullet.Filter, Target) --"Penetrate" (Ingoring the prop for the retry trace) - Bullet.Flight = Bullet.Flight:GetNormalized() * (Energy.Kinetic * (1 - HitRes.Loss) * 2000 / Bullet.ProjMass) ^ 0.5 * 39.37 - - return "Penetrated" - elseif HitRes.Ricochet then - return "Ricochet" - else - return false - end - else - table.insert(Bullet.Filter, Target) - - return "Penetrated" - end -end - -function Round.worldimpact(_, Bullet, HitPos, HitNormal) - local Energy = ACF_Kinetic(Bullet.Flight:Length() / ACF.Scale, Bullet.ProjMass, Bullet.LimitVel) - local HitRes = ACF_PenetrateGround(Bullet, Energy, HitPos, HitNormal) - - if HitRes.Penetrated then - return "Penetrated" - elseif HitRes.Ricochet then - return "Ricochet" - else - return false - end -end - -function Round.endflight(Index, Bullet, HitPos) - ACF_HE(HitPos, Bullet.FillerMass, Bullet.ProjMass - Bullet.FillerMass, Bullet.Owner, nil, Bullet.Gun) - ACF_RemoveBullet(Index) -end - -local DecalIndex = ACF.GetAmmoDecalIndex - -function Round.endeffect(_, Bullet) - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetScale(math.max(Bullet.FillerMass ^ 0.33 * 8 * 39.37, 1)) - Effect:SetRadius(Bullet.Caliber) - - util.Effect("ACF_Explosion", Effect) -end - -function Round.pierceeffect(_, Bullet) - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetScale(Bullet.SimFlight:Length()) - Effect:SetMagnitude(Bullet.RoundMass) - Effect:SetRadius(Bullet.Caliber) - Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) - - util.Effect("ACF_Penetration", Effect) -end - -function Round.ricocheteffect(_, Bullet) - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetScale(Bullet.SimFlight:Length()) - Effect:SetMagnitude(Bullet.RoundMass) - Effect:SetRadius(Bullet.Caliber) - Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) - - util.Effect("ACF_Ricochet", Effect) -end - -function Round.guicreate(Panel, Table) - acfmenupanel:AmmoSelect(ACF.AmmoBlacklist.APHE) - - acfmenupanel:CPanelText("Desc", "") --Description (Name, Desc) - acfmenupanel:CPanelText("LengthDisplay", "") --Total round length (Name, Desc) - acfmenupanel:AmmoSlider("PropLength", 0, 0, 1000, 3, "Propellant Length", "") --Propellant Length Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("ProjLength", 0, 0, 1000, 3, "Projectile Length", "") --Projectile Length Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("FillerVol", 0, 0, 1000, 3, "HE Filler", "") --Hollow Point Cavity Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoCheckbox("Tracer", "Tracer", "") --Tracer checkbox (Name, Title, Desc) - acfmenupanel:CPanelText("VelocityDisplay", "") --Proj muzzle velocity (Name, Desc) - acfmenupanel:CPanelText("PenetrationDisplay", "") --Proj muzzle penetration (Name, Desc) - acfmenupanel:CPanelText("BlastDisplay", "") --HE Blast data (Name, Desc) - acfmenupanel:CPanelText("FragDisplay", "") --HE Fragmentation data (Name, Desc) - --acfmenupanel:CPanelText("RicoDisplay", "") --estimated rico chance - acfmenupanel:CPanelText("PenetrationRanging", "") --penetration ranging (Name, Desc) - Round.guiupdate(Panel, Table) -end - -function Round.guiupdate(Panel) - local PlayerData = {} - PlayerData.Id = acfmenupanel.AmmoData.Data.id --AmmoSelect GUI - PlayerData.Type = "APHE" --Hardcoded, match ACFRoundTypes table index - PlayerData.PropLength = acfmenupanel.AmmoData.PropLength --PropLength slider - PlayerData.ProjLength = acfmenupanel.AmmoData.ProjLength --ProjLength slider - PlayerData.Data5 = acfmenupanel.AmmoData.FillerVol - local Tracer = 0 - - if acfmenupanel.AmmoData.Tracer then - Tracer = 1 - end - - PlayerData.Data10 = Tracer --Tracer - local Data = Round.convert(Panel, PlayerData) - RunConsoleCommand("acfmenu_data1", acfmenupanel.AmmoData.Data.id) - RunConsoleCommand("acfmenu_data2", PlayerData.Type) - RunConsoleCommand("acfmenu_data3", Data.PropLength) --For Gun ammo, Data3 should always be Propellant - RunConsoleCommand("acfmenu_data4", Data.ProjLength) --And Data4 total round mass - RunConsoleCommand("acfmenu_data5", Data.FillerVol) - RunConsoleCommand("acfmenu_data10", Data.Tracer) - - acfmenupanel:AmmoUpdate() - acfmenupanel:AmmoSlider("PropLength", Data.PropLength, Data.MinPropLength, Data.MaxTotalLength, 3, "Propellant Length", "Propellant Mass : " .. (math.floor(Data.PropMass * 1000)) .. " g") --Propellant Length Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("ProjLength", Data.ProjLength, Data.MinProjLength, Data.MaxTotalLength, 3, "Projectile Length", "Projectile Mass : " .. (math.floor(Data.ProjMass * 1000)) .. " g") --Projectile Length Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("FillerVol", Data.FillerVol, Data.MinFillerVol, Data.MaxFillerVol, 3, "HE Filler Volume", "HE Filler Mass : " .. (math.floor(Data.FillerMass * 1000)) .. " g") --HE Filler Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoCheckbox("Tracer", "Tracer : " .. (math.floor(Data.Tracer * 10) / 10) .. "cm\n", "") --Tracer checkbox (Name, Title, Desc) - acfmenupanel:CPanelText("Desc", ACF.RoundTypes[PlayerData.Type].desc) --Description (Name, Desc) - acfmenupanel:CPanelText("LengthDisplay", "Cartridge Length : " .. (math.floor((Data.PropLength + Data.ProjLength + Data.Tracer) * 100) / 100) .. "/" .. Data.MaxTotalLength .. " cm") --Total round length (Name, Desc) - acfmenupanel:CPanelText("VelocityDisplay", "Muzzle Velocity : " .. math.floor(Data.MuzzleVel * ACF.Scale) .. " m/s") --Proj muzzle velocity (Name, Desc) - acfmenupanel:CPanelText("PenetrationDisplay", "Maximum Penetration : " .. math.floor(Data.MaxPen) .. " mm RHA") --Proj muzzle penetration (Name, Desc) - acfmenupanel:CPanelText("BlastDisplay", "Blast Radius : " .. (math.floor(Data.BlastRadius * 100) / 100) .. " m") --Proj muzzle velocity (Name, Desc) - acfmenupanel:CPanelText("FragDisplay", "Fragments : " .. Data.Fragments .. "\n Average Fragment Weight : " .. (math.floor(Data.FragMass * 10000) / 10) .. " g \n Average Fragment Velocity : " .. math.floor(Data.FragVel) .. " m/s") --Proj muzzle penetration (Name, Desc) - --local RicoAngs = ACF_RicoProbability( Data.Ricochet, Data.MuzzleVel*ACF.Scale ) - --acfmenupanel:CPanelText("RicoDisplay", "Ricochet probability vs impact angle:\n".." 0% @ "..RicoAngs.Min.." degrees\n 50% @ "..RicoAngs.Mean.." degrees\n100% @ "..RicoAngs.Max.." degrees") - local R1V, R1P = ACF_PenRanging(Data.MuzzleVel, Data.DragCoef, Data.ProjMass, Data.PenArea, Data.LimitVel, 300) - local R2V, R2P = ACF_PenRanging(Data.MuzzleVel, Data.DragCoef, Data.ProjMass, Data.PenArea, Data.LimitVel, 800) - acfmenupanel:CPanelText("PenetrationRanging", "\n300m pen: " .. math.Round(R1P, 0) .. "mm @ " .. math.Round(R1V, 0) .. " m\\s\n800m pen: " .. math.Round(R2P, 0) .. "mm @ " .. math.Round(R2V, 0) .. " m\\s\n\nThe range data is an approximation and may not be entirely accurate.") --Proj muzzle penetration (Name, Desc) -end - -ACF.RoundTypes.APHE = Round --Set the round properties - -ACF.RegisterAmmoDecal("APHE", "damage/ap_pen", "damage/ap_rico") \ No newline at end of file diff --git a/lua/acf/shared/rounds/fl.lua b/lua/acf/shared/rounds/fl.lua deleted file mode 100644 index 9580e5485..000000000 --- a/lua/acf/shared/rounds/fl.lua +++ /dev/null @@ -1,317 +0,0 @@ -ACF.AmmoBlacklist["FL"] = {"AC", "RAC", "MG", "HMG", "GL", "SL"} -local Round = {} -Round.type = "Ammo" --Tells the spawn menu what entity to spawn -Round.name = "Flechette (FL)" --Human readable name -Round.model = "models/munitions/dart_100mm.mdl" --Shell flight model -Round.desc = "Flechette rounds contain several long thin steel spikes, functioning as a shotgun shell for cannons. While it seems like the spikes would penetrate well, they tend to tumble in flight and impact at less than ideal angles, causing only minor penetration and structural damage. They are best used against infantry or lightly armored mobile targets such as aircraft or light tanks, since flechettes trade brute damage for a better chance to hit." - -function Round.create(Gun, BulletData) - --setup flechettes - local FlechetteData = {} - FlechetteData["Caliber"] = math.Round(BulletData["FlechetteRadius"] * 0.2, 2) - FlechetteData["Id"] = BulletData["Id"] - FlechetteData["Type"] = "AP" --BulletData["Type"] - FlechetteData["Owner"] = BulletData["Owner"] - FlechetteData["Crate"] = BulletData["Crate"] - FlechetteData["Gun"] = BulletData["Gun"] - FlechetteData["Pos"] = BulletData["Pos"] - FlechetteData["FrArea"] = BulletData["FlechetteArea"] - FlechetteData["ProjMass"] = BulletData["FlechetteMass"] - FlechetteData["DragCoef"] = BulletData["FlechetteDragCoef"] - FlechetteData["Tracer"] = BulletData["Tracer"] - FlechetteData["LimitVel"] = BulletData["LimitVel"] - FlechetteData["Ricochet"] = BulletData["Ricochet"] - FlechetteData["PenArea"] = BulletData["FlechettePenArea"] - FlechetteData["ShovePower"] = BulletData["ShovePower"] - FlechetteData["KETransfert"] = BulletData["KETransfert"] - - local MuzzleVec - - --if ammo is cooking off, shoot in random direction - if Gun:GetClass() == "acf_ammo" then - local Inaccuracy - MuzzleVec = VectorRand() - - for _ = 1, BulletData["Flechettes"] do - Inaccuracy = VectorRand() / 360 * ((Gun.Spread or 0) + BulletData["FlechetteSpread"]) - FlechetteData["Flight"] = (MuzzleVec + Inaccuracy):GetNormalized() * BulletData["MuzzleVel"] * 39.37 + Gun:GetVelocity() - ACF_CreateBullet(FlechetteData) - end - else - local BaseInaccuracy = math.tan(math.rad(Gun:GetSpread())) - local AddInaccuracy = math.tan(math.rad(BulletData["FlechetteSpread"])) - MuzzleVec = Gun:GetForward() - - for _ = 1, BulletData["Flechettes"] do - BaseSpread = BaseInaccuracy * (math.random() ^ (1 / math.Clamp(ACF.GunInaccuracyBias, 0.5, 4))) * (Gun:GetUp() * (2 * math.random() - 1) + Gun:GetRight() * (2 * math.random() - 1)):GetNormalized() - AddSpread = AddInaccuracy * (math.random() ^ (1 / math.Clamp(ACF.GunInaccuracyBias, 0.5, 4))) * (Gun:GetUp() * (2 * math.random() - 1) + Gun:GetRight() * (2 * math.random() - 1)):GetNormalized() - FlechetteData["Flight"] = (MuzzleVec + BaseSpread + AddSpread):GetNormalized() * BulletData["MuzzleVel"] * 39.37 + Gun:GetVelocity() - ACF_CreateBullet(FlechetteData) - end - end -end - --- Function to convert the player's slider data into the complete round data -function Round.convert(_, PlayerData) - local Data = {} - local ServerData = {} - local GUIData = {} - Data["LengthAdj"] = 0.5 - - if not PlayerData["PropLength"] then - PlayerData["PropLength"] = 0 - end - - if not PlayerData["ProjLength"] then - PlayerData["ProjLength"] = 0 - end - - --flechette count - if not PlayerData["Data5"] then - PlayerData["Data5"] = 3 - end - - --flechette spread - if not PlayerData["Data6"] then - PlayerData["Data6"] = 5 - end - - --tracer - if not PlayerData["Data10"] then - PlayerData["Data10"] = 0 - end - - PlayerData, Data, ServerData, GUIData = ACF_RoundBaseGunpowder(PlayerData, Data, ServerData, GUIData) - local GunClass = ACF.Weapons["Guns"][Data["Id"] or PlayerData["Id"]]["gunclass"] - - if GunClass == "SA" then - Data["MaxFlechettes"] = math.Clamp(math.floor(Data["Caliber"] * 3 - 4.5), 1, 32) - elseif GunClass == "MO" then - Data["MaxFlechettes"] = math.Clamp(math.floor(Data["Caliber"] * 4) - 12, 1, 32) - elseif GunClass == "HW" then - Data["MaxFlechettes"] = math.Clamp(math.floor(Data["Caliber"] * 4) - 10, 1, 32) - else - Data["MaxFlechettes"] = math.Clamp(math.floor(Data["Caliber"] * 4) - 8, 1, 32) - end - - Data["MinFlechettes"] = math.min(6, Data["MaxFlechettes"]) --force bigger guns to have higher min count - Data["Flechettes"] = math.Clamp(math.floor(PlayerData["Data5"]), Data["MinFlechettes"], Data["MaxFlechettes"]) --number of flechettes - Data["MinSpread"] = 0.25 - Data["MaxSpread"] = 30 - Data["FlechetteSpread"] = math.Clamp(tonumber(PlayerData["Data6"]), Data["MinSpread"], Data["MaxSpread"]) - local PenAdj = 0.8 --higher means lower pen, but more structure (hp) damage (old: 2.35, 2.85) - local RadiusAdj = 1.0 -- lower means less structure (hp) damage, but higher pen (old: 1.0, 0.8) - local PackRatio = 0.0025 * Data["Flechettes"] + 0.69 --how efficiently flechettes are packed into shell - Data["FlechetteRadius"] = math.sqrt(((PackRatio * RadiusAdj * Data["Caliber"] / 2) ^ 2) / Data["Flechettes"]) -- max radius flechette can be, to fit number of flechettes in a shell - Data["FlechetteArea"] = 3.1416 * Data["FlechetteRadius"] ^ 2 -- area of a single flechette - Data["FlechetteMass"] = Data["FlechetteArea"] * (Data["ProjLength"] * 7.9 / 1000) -- volume of single flechette * density of steel - Data["FlechettePenArea"] = (PenAdj * Data["FlechetteArea"]) ^ ACF.PenAreaMod - Data["FlechetteDragCoef"] = (Data["FlechetteArea"] / 10000) / Data["FlechetteMass"] - Data["ProjMass"] = Data["Flechettes"] * Data["FlechetteMass"] -- total mass of all flechettes - Data["PropMass"] = Data["PropMass"] - Data["ShovePower"] = 0.2 - Data["PenArea"] = Data["FrArea"] ^ ACF.PenAreaMod - Data["DragCoef"] = ((Data["FrArea"] / 10000) / Data["ProjMass"]) - Data["LimitVel"] = 500 --Most efficient penetration speed in m/s - Data["KETransfert"] = 0.1 --Kinetic energy transfert to the target for movement purposes - Data["Ricochet"] = 75 --Base ricochet angle - Data["MuzzleVel"] = ACF_MuzzleVelocity(Data["PropMass"], Data["ProjMass"], Data["Caliber"]) - Data.CartMass = Data.PropMass + Data.ProjMass - - --Only the crates need this part - if SERVER then - ServerData["Id"] = PlayerData["Id"] - ServerData["Type"] = PlayerData["Type"] - - return table.Merge(Data, ServerData) - end - - --Only the GUI needs this part - if CLIENT then - GUIData = table.Merge(GUIData, Round.getDisplayData(Data, PlayerData)) - - return table.Merge(Data, GUIData) - end -end - -function Round.getDisplayData(Data) - local GUIData = {} - local Energy = ACF_Kinetic(Data["MuzzleVel"] * 39.37, Data["FlechetteMass"], Data["LimitVel"]) - GUIData["MaxPen"] = (Energy.Penetration / Data["FlechettePenArea"]) * ACF.KEtoRHA - - return GUIData -end - -function Round.network(Crate, BulletData) - Crate:SetNWString("AmmoType", "FL") - Crate:SetNWString("AmmoID", BulletData["Id"]) - Crate:SetNWFloat("PropMass", BulletData["PropMass"]) - Crate:SetNWFloat("MuzzleVel", BulletData["MuzzleVel"]) - Crate:SetNWFloat("Tracer", BulletData["Tracer"]) - -- bullet effects use networked data, so set these to the flechette stats - Crate:SetNWFloat("Caliber", math.Round(BulletData["FlechetteRadius"] * 0.2, 2)) - Crate:SetNWFloat("ProjMass", BulletData["FlechetteMass"]) - Crate:SetNWFloat("DragCoef", BulletData["FlechetteDragCoef"]) - Crate:SetNWFloat("FillerMass", 0) - --Crate:SetNWFloat("Caliber",BulletData["Caliber"]) - --Crate:SetNWFloat("ProjMass",BulletData["ProjMass"]) - --Crate:SetNWFloat("DragCoef",BulletData["DragCoef"]) -end - -function Round.cratetxt(BulletData) - local DData = Round.getDisplayData(BulletData) - local inaccuracy = 0 - local Gun = ACF.Weapons.Guns[BulletData.Id] - - if Gun then - local Classes = ACF.Classes - - inaccuracy = (Classes.GunClass[Gun.gunclass] or { - spread = 0 - }).spread - end - - local coneAng = inaccuracy * ACF.GunInaccuracyScale - local str = {"Muzzle Velocity: ", math.Round(BulletData.MuzzleVel, 1), " m/s\n", "Max Penetration: ", math.floor(DData.MaxPen), " mm\n", "Max Spread: ", math.ceil((BulletData.FlechetteSpread + coneAng) * 10) / 10, " deg"} - - return table.concat(str) -end - -function Round.propimpact(_, Bullet, Target, HitNormal, HitPos, Bone) - if ACF_Check(Target) then - local Speed = Bullet["Flight"]:Length() / ACF.Scale - local Energy = ACF_Kinetic(Speed, Bullet["ProjMass"], Bullet["LimitVel"]) - local HitRes = ACF_RoundImpact(Bullet, Speed, Energy, Target, HitPos, HitNormal, Bone) - - if HitRes.Overkill > 0 then - table.insert(Bullet["Filter"], Target) --"Penetrate" (Ingoring the prop for the retry trace) - Bullet["Flight"] = Bullet["Flight"]:GetNormalized() * (Energy.Kinetic * (1 - HitRes.Loss) * 2000 / Bullet["ProjMass"]) ^ 0.5 * 39.37 - - return "Penetrated" - elseif HitRes.Ricochet then - return "Ricochet" - else - return false - end - else - table.insert(Bullet["Filter"], Target) - - return "Penetrated" - end -end - -function Round.worldimpact(_, Bullet, HitPos, HitNormal) - local Energy = ACF_Kinetic(Bullet.Flight:Length() / ACF.Scale, Bullet.ProjMass, Bullet.LimitVel) - local HitRes = ACF_PenetrateGround(Bullet, Energy, HitPos, HitNormal) - - if HitRes.Penetrated then - return "Penetrated" - elseif HitRes.Ricochet then - return "Ricochet" - else - return false - end -end - -function Round.endflight(Index) - ACF_RemoveBullet(Index) -end - -local DecalIndex = ACF.GetAmmoDecalIndex - -function Round.endeffect(_, Bullet) - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetRadius(Bullet.Caliber) - Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) - - util.Effect("ACF_Impact", Effect) -end - -function Round.pierceeffect(_, Bullet) - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetScale(Bullet.SimFlight:Length()) - Effect:SetMagnitude(Bullet.RoundMass) - Effect:SetRadius(Bullet.Caliber) - Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) - - util.Effect("ACF_Penetration", Effect) -end - -function Round.ricocheteffect(_, Bullet) - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetScale(Bullet.SimFlight:Length()) - Effect:SetMagnitude(Bullet.RoundMass) - Effect:SetRadius(Bullet.Caliber) - Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) - - util.Effect("ACF_Ricochet", Effect) -end - -function Round.guicreate(Panel, Table) - acfmenupanel:AmmoSelect(ACF.AmmoBlacklist["FL"]) - acfmenupanel:CPanelText("BonusDisplay", "") - acfmenupanel:CPanelText("Desc", "") --Description (Name, Desc) - acfmenupanel:CPanelText("LengthDisplay", "") --Total round length (Name, Desc) - acfmenupanel:AmmoSlider("PropLength", 0, 0, 1000, 3, "Propellant Length", "") --Propellant Length Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("ProjLength", 0, 0, 1000, 3, "Projectile Length", "") --Projectile Length Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("Flechettes", 3, 3, 32, 0, "Flechettes", "") --flechette count Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("FlechetteSpread", 10, 5, 60, 1, "Flechette Spread", "") --flechette spread Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoCheckbox("Tracer", "Tracer", "") --Tracer checkbox (Name, Title, Desc) - acfmenupanel:CPanelText("VelocityDisplay", "") --Proj muzzle velocity (Name, Desc) - --acfmenupanel:CPanelText("RicoDisplay", "") --estimated rico chance - acfmenupanel:CPanelText("PenetrationDisplay", "") --Proj muzzle penetration (Name, Desc) - Round.guiupdate(Panel, Table) -end - -function Round.guiupdate(Panel) - local PlayerData = {} - PlayerData["Id"] = acfmenupanel.AmmoData["Data"]["id"] --AmmoSelect GUI - PlayerData["Type"] = "FL" --Hardcoded, match ACFRoundTypes table index - PlayerData["PropLength"] = acfmenupanel.AmmoData["PropLength"] --PropLength slider - PlayerData["ProjLength"] = acfmenupanel.AmmoData["ProjLength"] --ProjLength slider - PlayerData["Data5"] = acfmenupanel.AmmoData["Flechettes"] --Flechette count slider - PlayerData["Data6"] = acfmenupanel.AmmoData["FlechetteSpread"] --flechette spread slider - --PlayerData["Data7"] = acfmenupanel.AmmoData[Name] --Not used - --PlayerData["Data8"] = acfmenupanel.AmmoData[Name] --Not used - --PlayerData["Data9"] = acfmenupanel.AmmoData[Name] --Not used - local Tracer = 0 - - if acfmenupanel.AmmoData["Tracer"] then - Tracer = 1 - end - - PlayerData["Data10"] = Tracer --Tracer - local Data = Round.convert(Panel, PlayerData) - RunConsoleCommand("acfmenu_data1", acfmenupanel.AmmoData["Data"]["id"]) - RunConsoleCommand("acfmenu_data2", PlayerData["Type"]) - RunConsoleCommand("acfmenu_data3", Data.PropLength) --For Gun ammo, Data3 should always be Propellant - RunConsoleCommand("acfmenu_data4", Data.ProjLength) --And Data4 total round mass - RunConsoleCommand("acfmenu_data5", Data.Flechettes) - RunConsoleCommand("acfmenu_data6", Data.FlechetteSpread) - RunConsoleCommand("acfmenu_data10", Data.Tracer) - - acfmenupanel:AmmoUpdate() - acfmenupanel:AmmoSlider("PropLength", Data.PropLength, Data.MinPropLength, Data["MaxTotalLength"], 3, "Propellant Length", "Propellant Mass : " .. (math.floor(Data.PropMass * 1000)) .. " g") --Propellant Length Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("ProjLength", Data.ProjLength, Data.MinProjLength, Data["MaxTotalLength"], 3, "Projectile Length", "Projectile Mass : " .. (math.floor(Data.ProjMass * 1000)) .. " g") --Projectile Length Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("Flechettes", Data.Flechettes, Data.MinFlechettes, Data.MaxFlechettes, 0, "Flechettes", "Flechette Radius: " .. math.Round(Data["FlechetteRadius"] * 10, 2) .. " mm") - acfmenupanel:AmmoSlider("FlechetteSpread", Data.FlechetteSpread, Data.MinSpread, Data.MaxSpread, 1, "Flechette Spread", "") - acfmenupanel:AmmoCheckbox("Tracer", "Tracer : " .. (math.floor(Data.Tracer * 10) / 10) .. "cm\n", "") --Tracer checkbox (Name, Title, Desc) - acfmenupanel:CPanelText("Desc", ACF.RoundTypes[PlayerData["Type"]]["desc"]) --Description (Name, Desc) - acfmenupanel:CPanelText("LengthDisplay", "Cartridge Length : " .. (math.floor((Data.PropLength + Data.ProjLength + Data.Tracer) * 100) / 100) .. "/" .. Data.MaxTotalLength .. " cm") --Total round length (Name, Desc) - acfmenupanel:CPanelText("VelocityDisplay", "Muzzle Velocity : " .. math.floor(Data.MuzzleVel * ACF.Scale) .. " m\\s") --Proj muzzle velocity (Name, Desc) - --local RicoAngs = ACF_RicoProbability( Data.Ricochet, Data.MuzzleVel*ACF.Scale ) - --acfmenupanel:CPanelText("RicoDisplay", "Ricochet probability vs impact angle:\n".." 0% @ "..RicoAngs.Min.." degrees\n 50% @ "..RicoAngs.Mean.." degrees\n100% @ "..RicoAngs.Max.." degrees") - local R1V, R1P = ACF_PenRanging(Data.MuzzleVel, Data.FlechetteDragCoef, Data.FlechetteMass, Data.FlechettePenArea, Data.LimitVel, 300) - local R2V, R2P = ACF_PenRanging(Data.MuzzleVel, Data.FlechetteDragCoef, Data.FlechetteMass, Data.FlechettePenArea, Data.LimitVel, 800) - acfmenupanel:CPanelText("PenetrationDisplay", "Maximum Penetration : " .. math.floor(Data.MaxPen) .. " mm RHA\n\n300m pen: " .. math.Round(R1P, 0) .. "mm @ " .. math.Round(R1V, 0) .. " m\\s\n800m pen: " .. math.Round(R2P, 0) .. "mm @ " .. math.Round(R2V, 0) .. " m\\s\n\nThe range data is an approximation and may not be entirely accurate.") --Proj muzzle penetration (Name, Desc) -end - -ACF.RoundTypes.FL = Round --Set the round properties - -ACF.RegisterAmmoDecal("FL", "damage/ap_pen", "damage/ap_rico") \ No newline at end of file diff --git a/lua/acf/shared/rounds/he.lua b/lua/acf/shared/rounds/he.lua deleted file mode 100644 index f723c6d8a..000000000 --- a/lua/acf/shared/rounds/he.lua +++ /dev/null @@ -1,218 +0,0 @@ -ACF.AmmoBlacklist.HE = {"MG", "RAC"} -local Round = {} -Round.type = "Ammo" --Tells the spawn menu what entity to spawn -Round.name = "High Explosive (HE)" --Human readable name -Round.model = "models/munitions/round_100mm_shot.mdl" --Shell flight model -Round.desc = "A shell filled with explosives, detonating on impact" - -function Round.create(_, BulletData) - ACF_CreateBullet(BulletData) -end - --- Function to convert the player's slider data into the complete round data -function Round.convert(_, PlayerData) - local Data = {} - local ServerData = {} - local GUIData = {} - - if not PlayerData.PropLength then - PlayerData.PropLength = 0 - end - - if not PlayerData.ProjLength then - PlayerData.ProjLength = 0 - end - - PlayerData.Data5 = math.max(PlayerData.Data5 or 0, 0) - - if not PlayerData.Data10 then - PlayerData.Data10 = 0 - end - - PlayerData, Data, ServerData, GUIData = ACF_RoundBaseGunpowder(PlayerData, Data, ServerData, GUIData) - --Shell sturdiness calcs - Data.ProjMass = math.max(GUIData.ProjVolume - PlayerData.Data5, 0) * 7.9 / 1000 + math.min(PlayerData.Data5, GUIData.ProjVolume) * ACF.HEDensity / 1000 --Volume of the projectile as a cylinder - Volume of the filler * density of steel + Volume of the filler * density of TNT - Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, Data.ProjMass, Data.Caliber) - local Energy = ACF_Kinetic(Data.MuzzleVel * 39.37, Data.ProjMass, Data.LimitVel) - local MaxVol = ACF_RoundShellCapacity(Energy.Momentum, Data.FrArea, Data.Caliber, Data.ProjLength) - GUIData.MinFillerVol = 0 - GUIData.MaxFillerVol = math.min(GUIData.ProjVolume, MaxVol) - GUIData.FillerVol = math.min(PlayerData.Data5, GUIData.MaxFillerVol) - Data.FillerMass = GUIData.FillerVol * ACF.HEDensity / 1000 - Data.ProjMass = math.max(GUIData.ProjVolume - GUIData.FillerVol, 0) * 7.9 / 1000 + Data.FillerMass - Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, Data.ProjMass, Data.Caliber) - --Random bullshit left - Data.ShovePower = 0.1 - Data.PenArea = Data.FrArea ^ ACF.PenAreaMod - Data.DragCoef = ((Data.FrArea / 10000) / Data.ProjMass) - Data.LimitVel = 100 --Most efficient penetration speed in m/s - Data.KETransfert = 0.1 --Kinetic energy transfert to the target for movement purposes - Data.Ricochet = 60 --Base ricochet angle - Data.DetonatorAngle = 80 - Data.CanFuze = Data.Caliber > ACF.MinFuzeCaliber -- Can fuze on calibers > 20mm - Data.CartMass = Data.PropMass + Data.ProjMass - - --Only the crates need this part - if SERVER then - ServerData.Id = PlayerData.Id - ServerData.Type = PlayerData.Type - - return table.Merge(Data, ServerData) - end - - --Only the GUI needs this part - if CLIENT then - GUIData = table.Merge(GUIData, Round.getDisplayData(Data)) - - return table.Merge(Data, GUIData) - end -end - -function Round.getDisplayData(Data) - local GUIData = {} - GUIData.BlastRadius = Data.FillerMass ^ 0.33 * 8 - local FragMass = Data.ProjMass - Data.FillerMass - GUIData.Fragments = math.max(math.floor((Data.FillerMass / FragMass) * ACF.HEFrag), 2) - GUIData.FragMass = FragMass / GUIData.Fragments - GUIData.FragVel = (Data.FillerMass * ACF.HEPower * 1000 / GUIData.FragMass / GUIData.Fragments) ^ 0.5 - - return GUIData -end - -function Round.network(Crate, BulletData) - Crate:SetNWString("AmmoType", "HE") - Crate:SetNWString("AmmoID", BulletData.Id) - Crate:SetNWFloat("Caliber", BulletData.Caliber) - Crate:SetNWFloat("ProjMass", BulletData.ProjMass) - Crate:SetNWFloat("FillerMass", BulletData.FillerMass) - Crate:SetNWFloat("PropMass", BulletData.PropMass) - Crate:SetNWFloat("DragCoef", BulletData.DragCoef) - Crate:SetNWFloat("MuzzleVel", BulletData.MuzzleVel) - Crate:SetNWFloat("Tracer", BulletData.Tracer) -end - -function Round.cratetxt(BulletData) - local DData = Round.getDisplayData(BulletData) - local str = { - "Muzzle Velocity: ", math.Round(BulletData.MuzzleVel, 1), " m/s\n", - "Blast Radius: ",math.Round(DData.BlastRadius, 1), "m\n", - "Blast Energy: ", math.floor(BulletData.FillerMass * ACF.HEPower), "kJ\n", - "Filler Mass: ", math.Round(BulletData.FillerMass * 1000, 2), "g\n", - "Avg. Frag Mass: ", math.Round(DData.FragMass * 1000, 2), "g\n", - "Case Mass: ", math.Round((BulletData.ProjMass - BulletData.FillerMass) * 1000, 2), "g", - } - - return table.concat(str) -end - -function Round.propimpact(_, Bullet, Target, HitNormal, HitPos, Bone) - if ACF_Check(Target) then - local Speed = Bullet.Flight:Length() / ACF.Scale - local Energy = ACF_Kinetic(Speed, Bullet.ProjMass - Bullet.FillerMass, Bullet.LimitVel) - local HitRes = ACF_RoundImpact(Bullet, Speed, Energy, Target, HitPos, HitNormal, Bone) - if HitRes.Ricochet then return "Ricochet" end - end - - return false -end - -function Round.worldimpact() - return false -end - -function Round.endflight(Index, Bullet, HitPos) - ACF_HE(HitPos, Bullet.FillerMass, Bullet.ProjMass - Bullet.FillerMass, Bullet.Owner, nil, Bullet.Gun) - ACF_RemoveBullet(Index) -end - -local DecalIndex = ACF.GetAmmoDecalIndex - -function Round.endeffect(_, Bullet) - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetScale(math.max(Bullet.FillerMass ^ 0.33 * 8 * 39.37, 1)) - Effect:SetRadius(Bullet.Caliber) - - util.Effect("ACF_Explosion", Effect) -end - -function Round.pierceeffect(_, Bullet) - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetScale(Bullet.SimFlight:Length()) - Effect:SetMagnitude(Bullet.RoundMass) - Effect:SetRadius(Bullet.Caliber) - Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) - - util.Effect("ACF_Penetration", Effect) -end - -function Round.ricocheteffect(_, Bullet) - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetScale(Bullet.SimFlight:Length()) - Effect:SetMagnitude(Bullet.RoundMass) - Effect:SetRadius(Bullet.Caliber) - Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) - - util.Effect("ACF_Ricochet", Effect) -end - -function Round.guicreate(Panel, Table) - acfmenupanel:AmmoSelect(ACF.AmmoBlacklist.HE) - - acfmenupanel:CPanelText("Desc", "") --Description (Name, Desc) - acfmenupanel:CPanelText("LengthDisplay", "") --Total round length (Name, Desc) - acfmenupanel:AmmoSlider("PropLength", 0, 0, 1000, 3, "Propellant Length", "") --Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("ProjLength", 0, 0, 1000, 3, "Projectile Length", "") --Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("FillerVol", 0, 0, 1000, 3, "HE Filler", "") --Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoCheckbox("Tracer", "Tracer", "") --Tracer checkbox (Name, Title, Desc) - acfmenupanel:CPanelText("VelocityDisplay", "") --Proj muzzle velocity (Name, Desc) - acfmenupanel:CPanelText("BlastDisplay", "") --HE Blast data (Name, Desc) - acfmenupanel:CPanelText("FragDisplay", "") --HE Fragmentation data (Name, Desc) - --acfmenupanel:CPanelText("RicoDisplay", "") --estimated rico chance - Round.guiupdate(Panel, Table) -end - -function Round.guiupdate(Panel) - local PlayerData = {} - PlayerData.Id = acfmenupanel.AmmoData.Data.id --AmmoSelect GUI - PlayerData.Type = "HE" --Hardcoded, match ACFRoundTypes table index - PlayerData.PropLength = acfmenupanel.AmmoData.PropLength --PropLength slider - PlayerData.ProjLength = acfmenupanel.AmmoData.ProjLength --ProjLength slider - PlayerData.Data5 = acfmenupanel.AmmoData.FillerVol - local Tracer = 0 - - if acfmenupanel.AmmoData.Tracer then - Tracer = 1 - end - - PlayerData.Data10 = Tracer --Tracer - local Data = Round.convert(Panel, PlayerData) - RunConsoleCommand("acfmenu_data1", acfmenupanel.AmmoData.Data.id) - RunConsoleCommand("acfmenu_data2", PlayerData.Type) - RunConsoleCommand("acfmenu_data3", Data.PropLength) --For Gun ammo, Data3 should always be Propellant - RunConsoleCommand("acfmenu_data4", Data.ProjLength) --And Data4 total round mass - RunConsoleCommand("acfmenu_data5", Data.FillerVol) - RunConsoleCommand("acfmenu_data10", Data.Tracer) - - acfmenupanel:AmmoUpdate() - acfmenupanel:AmmoSlider("PropLength", Data.PropLength, Data.MinPropLength, Data.MaxTotalLength, 3, "Propellant Length", "Propellant Mass : " .. (math.floor(Data.PropMass * 1000)) .. " g") --Propellant Length Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("ProjLength", Data.ProjLength, Data.MinProjLength, Data.MaxTotalLength, 3, "Projectile Length", "Projectile Mass : " .. (math.floor(Data.ProjMass * 1000)) .. " g") --Projectile Length Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("FillerVol", Data.FillerVol, Data.MinFillerVol, Data.MaxFillerVol, 3, "HE Filler Volume", "HE Filler Mass : " .. (math.floor(Data.FillerMass * 1000)) .. " g") --HE Filler Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoCheckbox("Tracer", "Tracer : " .. (math.floor(Data.Tracer * 10) / 10) .. "cm\n", "") --Tracer checkbox (Name, Title, Desc) - acfmenupanel:CPanelText("Desc", ACF.RoundTypes[PlayerData.Type].desc) --Description (Name, Desc) - acfmenupanel:CPanelText("LengthDisplay", "Cartridge Length : " .. (math.floor((Data.PropLength + Data.ProjLength + Data.Tracer) * 100) / 100) .. "/" .. Data.MaxTotalLength .. " cm") --Total round length (Name, Desc) - acfmenupanel:CPanelText("VelocityDisplay", "Muzzle Velocity : " .. math.floor(Data.MuzzleVel * ACF.Scale) .. " m/s") --Proj muzzle velocity (Name, Desc) - acfmenupanel:CPanelText("BlastDisplay", "Blast Radius : " .. (math.floor(Data.BlastRadius * 100) / 100) .. " m") --Proj muzzle velocity (Name, Desc) - acfmenupanel:CPanelText("FragDisplay", "Fragments : " .. Data.Fragments .. "\n Average Fragment Weight : " .. (math.floor(Data.FragMass * 10000) / 10) .. " g \n Average Fragment Velocity : " .. math.floor(Data.FragVel) .. " m/s") --Proj muzzle penetration (Name, Desc) - --local RicoAngs = ACF_RicoProbability( Data.Ricochet, Data.MuzzleVel*ACF.Scale ) - --acfmenupanel:CPanelText("RicoDisplay", "Ricochet probability vs impact angle:\n".." 0% @ "..RicoAngs.Min.." degrees\n 50% @ "..RicoAngs.Mean.." degrees\n100% @ "..RicoAngs.Max.." degrees") -end - -ACF.RoundTypes.HE = Round --Set the round properties - -ACF.RegisterAmmoDecal("HE", "damage/he_pen", "damage/he_rico") \ No newline at end of file diff --git a/lua/acf/shared/rounds/heat.lua b/lua/acf/shared/rounds/heat.lua deleted file mode 100644 index d795cf16c..000000000 --- a/lua/acf/shared/rounds/heat.lua +++ /dev/null @@ -1,380 +0,0 @@ -ACF.AmmoBlacklist.HEAT = {"MG", "HMG", "RAC", "AC", "SL", "SB"} -local Round = {} -Round.type = "Ammo" --Tells the spawn menu what entity to spawn -Round.name = "High Explosive Anti-Tank (HEAT)" --Human readable name -Round.model = "models/munitions/round_100mm_shot.mdl" --Shell flight model -Round.desc = "A shell with a shaped charge. When the round detonates, the explosive energy is focused into driving a small molten metal penetrator into the victim with extreme force, though this results in reduced damage from the explosion itself. Multiple layers of armor will dissipate the penetrator quickly." - -function Round.create(_, BulletData) - ACF_CreateBullet(BulletData) -end - -function Round.ConeCalc(ConeAngle, Radius) - local ConeLength = math.tan(math.rad(ConeAngle)) * Radius - local ConeArea = 3.1416 * Radius * (Radius ^ 2 + ConeLength ^ 2) ^ 0.5 - local ConeVol = (3.1416 * Radius ^ 2 * ConeLength) / 3 - - return ConeLength, ConeArea, ConeVol -end - --- calculates conversion of filler from powering HEAT jet to raw HE based on crush vel --- above a threshold vel, HEAT jet doesn't have time to form properly, converting to raw HE proportionally --- Vel needs to be in m/s (gmu*0.0254) -function Round.CrushCalc(Vel, FillerMass) - local Crushed = math.Clamp((Vel - ACF.HEATMinCrush) / (ACF.HEATMaxCrush - ACF.HEATMinCrush), 0, 1) - local HE_Filler = Lerp(Crushed, FillerMass * ACF.HEATBoomConvert, FillerMass) - local HEAT_Filler = Lerp(Crushed, FillerMass, 0) - --local HE_Filler = FillerMass * ACF.HEATBoomConvert + Crushed * FillerMass * (1-ACF.HEATBoomConvert) - --local HEAT_Filler = (1-Crushed) * FillerMass - - return Crushed, HEAT_Filler, HE_Filler -end - --- coneang now required for slug recalculation at detonation, defaults to 55 if not present -function Round.CalcSlugMV(Data, HEATFillerMass) - --keep fillermass/2 so that penetrator stays the same. - return (HEATFillerMass / 2 * ACF.HEPower * math.sin(math.rad(10 + (Data.ConeAng or 55)) / 2) / Data.SlugMass) ^ ACF.HEATMVScale -end - --- Function to convert the player's slider data into the complete round data -function Round.convert(_, PlayerData) - local Data = {} - local ServerData = {} - local GUIData = {} - - if not PlayerData.PropLength then - PlayerData.PropLength = 0 - end - - if not PlayerData.ProjLength then - PlayerData.ProjLength = 0 - end - - PlayerData.Data5 = math.max(PlayerData.Data5 or 0, 0) - - if not PlayerData.Data6 then - PlayerData.Data6 = 0 - end - - if not PlayerData.Data7 then - PlayerData.Data7 = 0 - end - - if not PlayerData.Data10 then - PlayerData.Data10 = 0 - end - - PlayerData, Data, ServerData, GUIData = ACF_RoundBaseGunpowder(PlayerData, Data, ServerData, GUIData) - local ConeThick = Data.Caliber / 50 - local ConeArea = 0 - local AirVol = 0 - ConeLength, ConeArea, AirVol = Round.ConeCalc(PlayerData.Data6, Data.Caliber / 2, PlayerData.ProjLength) - Data.ProjMass = math.max(GUIData.ProjVolume - PlayerData.Data5, 0) * 7.9 / 1000 + math.min(PlayerData.Data5, GUIData.ProjVolume) * ACF.HEDensity / 1000 + ConeArea * ConeThick * 7.9 / 1000 --Volume of the projectile as a cylinder - Volume of the filler - Volume of the crush cone * density of steel + Volume of the filler * density of TNT + Area of the cone * thickness * density of steel - Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, Data.ProjMass, Data.Caliber) - local Energy = ACF_Kinetic(Data.MuzzleVel * 39.37, Data.ProjMass, Data.LimitVel) - local MaxVol = 0 - MaxVol, MaxLength, MaxRadius = ACF_RoundShellCapacity(Energy.Momentum, Data.FrArea, Data.Caliber, Data.ProjLength) - GUIData.MinConeAng = 0 - GUIData.MaxConeAng = math.deg(math.atan((Data.ProjLength - ConeThick) / (Data.Caliber / 2))) - Data.ConeAng = math.Clamp(PlayerData.Data6 * 1, GUIData.MinConeAng, GUIData.MaxConeAng) - ConeLength, ConeArea, AirVol = Round.ConeCalc(Data.ConeAng, Data.Caliber / 2, Data.ProjLength) - local ConeVol = ConeArea * ConeThick - GUIData.MinFillerVol = 0 - GUIData.MaxFillerVol = math.max(MaxVol - AirVol - ConeVol, GUIData.MinFillerVol) - GUIData.FillerVol = math.Clamp(PlayerData.Data5 * 1, GUIData.MinFillerVol, GUIData.MaxFillerVol) - -- fillermass used for shell mass calcs - -- heatfillermass is how much fillermass is used to power heat jet - -- boomfillermass is how much fillermass creates HE damage on detonation. technically get 1/3 extra fillermass free as HE with no crushing, but screw trying to rebalance heat pen to properly use 1/3 of filler for HE and 2/3 for jet - -- distribution of heat and boom fillermass is calculated at detonation, or for GUI stuff - Data.FillerMass = GUIData.FillerVol * ACF.HEDensity / 1450 - Data.ProjMass = math.max(GUIData.ProjVolume - GUIData.FillerVol - AirVol - ConeVol, 0) * 7.9 / 1000 + Data.FillerMass + ConeVol * 7.9 / 1000 - Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, Data.ProjMass, Data.Caliber) - - --Let's calculate the actual HEAT slug - Data.SlugMass = ConeVol * 7.9 / 1000 - local Rad = math.rad(Data.ConeAng / 2) - Data.SlugCaliber = Data.Caliber - Data.Caliber * (math.sin(Rad) * 0.5 + math.cos(Rad) * 1.5) / 2 - local SlugFrArea = 3.1416 * (Data.SlugCaliber / 2) ^ 2 - Data.SlugPenArea = SlugFrArea ^ ACF.PenAreaMod - Data.SlugDragCoef = ((SlugFrArea / 10000) / Data.SlugMass) - Data.SlugRicochet = 500 --Base ricochet angle (The HEAT slug shouldn't ricochet at all) - -- these are only for compatibility with other stuff. it's recalculated when the round is detonated - local _, heatfiller, boomfiller = Round.CrushCalc(Data.MuzzleVel, Data.FillerMass) - Data.BoomFillerMass = boomfiller - Data.SlugMV = Round.CalcSlugMV(Data, heatfiller) - --Random bullshit left - Data.CasingMass = Data.ProjMass - Data.FillerMass - ConeVol * 7.9 / 1000 - Data.ShovePower = 0.1 - Data.PenArea = Data.FrArea ^ ACF.PenAreaMod - Data.DragCoef = ((Data.FrArea / 10000) / Data.ProjMass) - Data.LimitVel = 100 --Most efficient penetration speed in m/s - Data.KETransfert = 0.1 --Kinetic energy transfert to the target for movement purposes - Data.Ricochet = 60 --Base ricochet angle - Data.DetonatorAngle = 75 - Data.Detonated = false - Data.NotFirstPen = false - Data.CanFuze = Data.Caliber > ACF.MinFuzeCaliber -- Can fuze on calibers > 20mm - Data.CartMass = Data.PropMass + Data.ProjMass - - --Only the crates need this part - if SERVER then - ServerData.Id = PlayerData.Id - ServerData.Type = PlayerData.Type - - return table.Merge(Data, ServerData) - end - - --Only the GUI needs this part - if CLIENT then - GUIData = table.Merge(GUIData, Round.getDisplayData(Data)) - - return table.Merge(Data, GUIData) - end -end - -function Round.getDisplayData(Data) - local GUIData = {} - -- these are only GUI info, it's recalculated when the round is detonated since it's vel dependent - GUIData.Crushed, GUIData.HEATFillerMass, GUIData.BoomFillerMass = Round.CrushCalc(Data.MuzzleVel, Data.FillerMass) - GUIData.SlugMV = Round.CalcSlugMV(Data, GUIData.HEATFillerMass) * (Data.SlugPenMul or 1) -- slugpenmul is a missiles thing - GUIData.SlugMassUsed = Data.SlugMass * (1 - GUIData.Crushed) - local SlugEnergy = ACF_Kinetic(Data.MuzzleVel * 39.37 + GUIData.SlugMV * 39.37, GUIData.SlugMassUsed, 999999) - GUIData.MaxPen = (SlugEnergy.Penetration / Data.SlugPenArea) * ACF.KEtoRHA - GUIData.TotalFragMass = Data.CasingMass + Data.SlugMass * GUIData.Crushed - GUIData.BlastRadius = GUIData.BoomFillerMass ^ 0.33 * 8 --*39.37 - GUIData.Fragments = math.max(math.floor((GUIData.BoomFillerMass / GUIData.TotalFragMass) * ACF.HEFrag), 2) - GUIData.FragMass = GUIData.TotalFragMass / GUIData.Fragments - GUIData.FragVel = (GUIData.BoomFillerMass * ACF.HEPower * 1000 / GUIData.TotalFragMass) ^ 0.5 - - return GUIData -end - -function Round.network(Crate, BulletData) - Crate:SetNWString("AmmoType", "HEAT") - Crate:SetNWString("AmmoID", BulletData.Id) - Crate:SetNWFloat("Caliber", BulletData.Caliber) - Crate:SetNWFloat("ProjMass", BulletData.ProjMass) - Crate:SetNWFloat("FillerMass", BulletData.FillerMass) - Crate:SetNWFloat("PropMass", BulletData.PropMass) - Crate:SetNWFloat("DragCoef", BulletData.DragCoef) - Crate:SetNWFloat("SlugMass", BulletData.SlugMass) - Crate:SetNWFloat("SlugCaliber", BulletData.SlugCaliber) - Crate:SetNWFloat("SlugDragCoef", BulletData.SlugDragCoef) - Crate:SetNWFloat("MuzzleVel", BulletData.MuzzleVel) - Crate:SetNWFloat("Tracer", BulletData.Tracer) -end - ---local fakeent = {ACF = {Armour = 0}} ---local fakepen = {Penetration = 999999999} -function Round.cratetxt(BulletData) - local DData = Round.getDisplayData(BulletData) - local str = {"Muzzle Velocity: ", math.Round(BulletData.MuzzleVel, 1), " m/s\n", "Max Penetration: ", math.floor(DData.MaxPen), " mm\n", "Blast Radius: ", math.Round(DData.BlastRadius, 1), " m\n", "Blast Energy: ", math.floor(DData.BoomFillerMass * ACF.HEPower), " KJ"} - - return table.concat(str) -end - -function Round.detonate(_, Bullet, HitPos) - local Crushed, HEATFillerMass, BoomFillerMass = Round.CrushCalc(Bullet.Flight:Length() * 0.0254, Bullet.FillerMass) - ACF_HE(HitPos, BoomFillerMass, Bullet.CasingMass + Bullet.SlugMass * Crushed, Bullet.Owner, nil, Bullet.Gun) - if Crushed == 1 then return false end -- no HEAT jet to fire off, it was all converted to HE - - Bullet.Detonated = true - Bullet.InitTime = ACF.CurTime - Bullet.Flight = Bullet.Flight + Bullet.Flight:GetNormalized() * Round.CalcSlugMV(Bullet, HEATFillerMass) * 39.37 - Bullet.FuseLength = 0.005 + 40 / (Bullet.Flight:Length() * 0.0254) - Bullet.NextPos = HitPos - Bullet.DragCoef = Bullet.SlugDragCoef - Bullet.ProjMass = Bullet.SlugMass * (1 - Crushed) - Bullet.Caliber = Bullet.SlugCaliber - Bullet.PenArea = Bullet.SlugPenArea - Bullet.Ricochet = Bullet.SlugRicochet - - return true -end - -function Round.propimpact(Index, Bullet, Target, HitNormal, HitPos, Bone) - if ACF_Check(Target) then - if Bullet.Detonated then - Bullet.NotFirstPen = true - local Speed = Bullet.Flight:Length() / ACF.Scale - local Energy = ACF_Kinetic(Speed, Bullet.ProjMass, 999999) - local HitRes = ACF_RoundImpact(Bullet, Speed, Energy, Target, HitPos, HitNormal, Bone) - - if HitRes.Overkill > 0 then - table.insert(Bullet.Filter, Target) --"Penetrate" (Ingoring the prop for the retry trace) - Bullet.Flight = Bullet.Flight:GetNormalized() * math.sqrt(Energy.Kinetic * (1 - HitRes.Loss) * ((Bullet.NotFirstPen and ACF.HEATPenLayerMul) or 1) * 2000 / Bullet.ProjMass) * 39.37 - - return "Penetrated" - else - return false - end - else - local Speed = Bullet.Flight:Length() / ACF.Scale - local Energy = ACF_Kinetic(Speed, Bullet.ProjMass - Bullet.FillerMass, Bullet.LimitVel) - local HitRes = ACF_RoundImpact(Bullet, Speed, Energy, Target, HitPos, HitNormal, Bone) - - if HitRes.Ricochet then - return "Ricochet" - else - local jet = Round.detonate(Index, Bullet, HitPos, HitNormal) - - if jet then - return "Penetrated" - else - return false - end - end - end - else - table.insert(Bullet.Filter, Target) - - return "Penetrated" - end - - return false -end - -function Round.worldimpact(Index, Bullet, HitPos, HitNormal) - if not Bullet.Detonated then - local jet = Round.detonate(Index, Bullet, HitPos, HitNormal) - - if jet then - return "Penetrated" - else - return false - end - end - - local Energy = ACF_Kinetic(Bullet.Flight:Length() / ACF.Scale, Bullet.ProjMass, 999999) - local HitRes = ACF_PenetrateGround(Bullet, Energy, HitPos, HitNormal) - - if HitRes.Penetrated then - --elseif HitRes.Ricochet then --penetrator won't ricochet - -- return "Ricochet" - return "Penetrated" - else - return false - end -end - -function Round.endflight(Index) - ACF_RemoveBullet(Index) -end - -local DecalIndex = ACF.GetAmmoDecalIndex - -function Round.endeffect(_, Bullet) - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetRadius(Bullet.Caliber) - Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) - - util.Effect("ACF_Impact", Effect) -end - -function Round.pierceeffect(Effect, Bullet) - if Bullet.Detonated then - local Data = EffectData() - Data:SetOrigin(Bullet.SimPos) - Data:SetNormal(Bullet.SimFlight:GetNormalized()) - Data:SetScale(Bullet.SimFlight:Length()) - Data:SetMagnitude(Bullet.RoundMass) - Data:SetRadius(Bullet.Caliber) - Data:SetDamageType(DecalIndex(Bullet.AmmoType)) - - util.Effect("ACF_Penetration", Data) - else - local _, _, BoomFillerMass = Round.CrushCalc(Bullet.SimFlight:Length() * 0.0254, Bullet.FillerMass) - local Data = EffectData() - Data:SetOrigin(Bullet.SimPos) - Data:SetNormal(Bullet.SimFlight:GetNormalized()) - Data:SetRadius(math.max(BoomFillerMass ^ 0.33 * 8 * 39.37, 1)) - - util.Effect("ACF_HEAT_Explosion", Data) - - Bullet.Detonated = true - - Effect:SetModel("models/Gibs/wood_gib01e.mdl") - end -end - -function Round.ricocheteffect(_, Bullet) - local Detonated = Bullet.Detonated - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetScale(Bullet.SimFlight:Length()) - Effect:SetMagnitude(Bullet.RoundMass) - Effect:SetRadius(Bullet.Caliber) - Effect:SetDamageType(DecalIndex(Detonated and Bullet.AmmoType or "AP")) - - util.Effect("ACF_Ricochet", Effect) -end - -function Round.guicreate(Panel, Table) - acfmenupanel:AmmoSelect(ACF.AmmoBlacklist.HEAT) - - acfmenupanel:CPanelText("Desc", "") --Description (Name, Desc) - acfmenupanel:CPanelText("LengthDisplay", "") --Total round length (Name, Desc) - --Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("PropLength", 0, 0, 1000, 3, "Propellant Length", "") - acfmenupanel:AmmoSlider("ProjLength", 0, 0, 1000, 3, "Projectile Length", "") - acfmenupanel:AmmoSlider("ConeAng", 0, 0, 1000, 3, "HEAT Cone Angle", "") - acfmenupanel:AmmoSlider("FillerVol", 0, 0, 1000, 3, "Total HEAT Warhead volume", "") - acfmenupanel:AmmoCheckbox("Tracer", "Tracer", "") --Tracer checkbox (Name, Title, Desc) - acfmenupanel:CPanelText("VelocityDisplay", "") --Proj muzzle velocity (Name, Desc) - acfmenupanel:CPanelText("BlastDisplay", "") --HE Blast data (Name, Desc) - acfmenupanel:CPanelText("FragDisplay", "") --HE Fragmentation data (Name, Desc) - --acfmenupanel:CPanelText("RicoDisplay", "") --estimated rico chance - acfmenupanel:CPanelText("SlugDisplay", "") --HEAT Slug data (Name, Desc) - Round.guiupdate(Panel, Table) -end - -function Round.guiupdate(Panel) - local PlayerData = {} - PlayerData.Id = acfmenupanel.AmmoData.Data.id --AmmoSelect GUI - PlayerData.Type = "HEAT" --Hardcoded, match ACFRoundTypes table index - PlayerData.PropLength = acfmenupanel.AmmoData.PropLength --PropLength slider - PlayerData.ProjLength = acfmenupanel.AmmoData.ProjLength --ProjLength slider - PlayerData.Data5 = acfmenupanel.AmmoData.FillerVol - PlayerData.Data6 = acfmenupanel.AmmoData.ConeAng - local Tracer = 0 - - if acfmenupanel.AmmoData.Tracer then - Tracer = 1 - end - - PlayerData.Data10 = Tracer --Tracer - local Data = Round.convert(Panel, PlayerData) - RunConsoleCommand("acfmenu_data1", acfmenupanel.AmmoData.Data.id) - RunConsoleCommand("acfmenu_data2", PlayerData.Type) - RunConsoleCommand("acfmenu_data3", Data.PropLength) --For Gun ammo, Data3 should always be Propellant - RunConsoleCommand("acfmenu_data4", Data.ProjLength) - RunConsoleCommand("acfmenu_data5", Data.FillerVol) - RunConsoleCommand("acfmenu_data6", Data.ConeAng) - RunConsoleCommand("acfmenu_data10", Data.Tracer) - - acfmenupanel:AmmoUpdate() - acfmenupanel:AmmoSlider("PropLength", Data.PropLength, Data.MinPropLength, Data.MaxTotalLength, 3, "Propellant Length", "Propellant Mass : " .. (math.floor(Data.PropMass * 1000)) .. " g") --Propellant Length Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("ProjLength", Data.ProjLength, Data.MinProjLength, Data.MaxTotalLength, 3, "Projectile Length", "Projectile Mass : " .. (math.floor(Data.ProjMass * 1000)) .. " g") --Projectile Length Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("ConeAng", Data.ConeAng, Data.MinConeAng, Data.MaxConeAng, 0, "Crush Cone Angle", "") --HE Filler Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("FillerVol", Data.FillerVol, Data.MinFillerVol, Data.MaxFillerVol, 3, "HE Filler Volume", "HE Filler Mass : " .. (math.floor(Data.FillerMass * 1000)) .. " g") --HE Filler Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoCheckbox("Tracer", "Tracer : " .. (math.floor(Data.Tracer * 10) / 10) .. "cm\n", "") --Tracer checkbox (Name, Title, Desc) - acfmenupanel:CPanelText("Desc", ACF.RoundTypes[PlayerData.Type].desc) --Description (Name, Desc) - acfmenupanel:CPanelText("LengthDisplay", "Cartridge Length : " .. (math.floor((Data.PropLength + Data.ProjLength + Data.Tracer) * 100) / 100) .. "/" .. Data.MaxTotalLength .. " cm") --Total round length (Name, Desc) - acfmenupanel:CPanelText("VelocityDisplay", "Muzzle Velocity : " .. math.floor(Data.MuzzleVel * ACF.Scale) .. " m/s") --Proj muzzle velocity (Name, Desc) - acfmenupanel:CPanelText("BlastDisplay", "Blast Radius : " .. (math.floor(Data.BlastRadius * 100) / 100) .. " m") --Proj muzzle velocity (Name, Desc) - acfmenupanel:CPanelText("FragDisplay", "Fragments : " .. Data.Fragments .. "\n Average Fragment Weight : " .. (math.floor(Data.FragMass * 10000) / 10) .. " g \n Average Fragment Velocity : " .. math.floor(Data.FragVel) .. " m/s") --Proj muzzle penetration (Name, Desc) - --local RicoAngs = ACF_RicoProbability( Data.Ricochet, Data.MuzzleVel*ACF.Scale ) - --acfmenupanel:CPanelText("RicoDisplay", "Ricochet probability vs impact angle:\n".." 0% @ "..RicoAngs.Min.." degrees\n 50% @ "..RicoAngs.Mean.." degrees\n100% @ "..RicoAngs.Max.." degrees") - local R1V, R1P = ACF_PenRanging(Data.MuzzleVel, Data.DragCoef, Data.ProjMass, Data.PenArea, Data.LimitVel, 300) - R1P = (ACF_Kinetic((R1V + Data.SlugMV) * 39.37, Data.SlugMassUsed, 999999).Penetration / Data.SlugPenArea) * ACF.KEtoRHA - local R2V, R2P = ACF_PenRanging(Data.MuzzleVel, Data.DragCoef, Data.ProjMass, Data.PenArea, Data.LimitVel, 800) - R2P = (ACF_Kinetic((R2V + Data.SlugMV) * 39.37, Data.SlugMassUsed, 999999).Penetration / Data.SlugPenArea) * ACF.KEtoRHA - acfmenupanel:CPanelText("SlugDisplay", "Penetrator Mass : " .. (math.floor(Data.SlugMassUsed * 10000) / 10) .. " g \n Penetrator Caliber : " .. (math.floor(Data.SlugCaliber * 100) / 10) .. " mm \n Penetrator Velocity : " .. math.floor(Data.MuzzleVel + Data.SlugMV) .. " m/s \n Penetrator Maximum Penetration : " .. math.floor(Data.MaxPen) .. " mm RHA\n\n300m pen: " .. math.Round(R1P, 0) .. "mm @ " .. math.Round(R1V, 0) .. " m\\s\n800m pen: " .. math.Round(R2P, 0) .. "mm @ " .. math.Round(R2V, 0) .. " m\\s\n\nThe range data is an approximation and may not be entirely accurate.") --Proj muzzle penetration (Name, Desc) -end - -ACF.RoundTypes.HEAT = Round --Set the round properties - -ACF.RegisterAmmoDecal("HEAT", "damage/heat_pen", "damage/heat_rico", function(Caliber) return Caliber * 0.1667 end) \ No newline at end of file diff --git a/lua/acf/shared/rounds/heatfs.lua b/lua/acf/shared/rounds/heatfs.lua deleted file mode 100644 index ba2c5c2ab..000000000 --- a/lua/acf/shared/rounds/heatfs.lua +++ /dev/null @@ -1,406 +0,0 @@ -ACF.AmmoBlacklist.HEATFS = { "MO", "SL", "C", "HW", "AC", "SC", "SA", "MG", "AL", "RAC", "GL", "HMG", "AAM", "ARTY", "ASM", "BOMB", "GBU", "POD", "SAM", "UAR", "FFAR", "FGL" } - -local Round = {} - -Round.type = "Ammo" --Tells the spawn menu what entity to spawn -Round.name = "High Explosive Anti-Tank Fin Stabilized (HEAT-FS)" --Human readable name -Round.model = "models/munitions/round_100mm_shot.mdl" --Shell flight model -Round.desc = "HEAT, but fin stabilized with a fixed minimum propellant charge. Smoothbores only." - -function Round.create(_, BulletData) - ACF_CreateBullet(BulletData) -end - -function Round.ConeCalc(ConeAngle, Radius) - local ConeLength = math.tan(math.rad(ConeAngle)) * Radius - local ConeArea = 3.1416 * Radius * (Radius ^ 2 + ConeLength ^ 2) ^ 0.5 - local ConeVol = (3.1416 * Radius ^ 2 * ConeLength) / 3 - - return ConeLength, ConeArea, ConeVol -end - --- calculates conversion of filler from powering HEAT jet to raw HE based on crush vel --- above a threshold vel, HEAT jet doesn't have time to form properly, converting to raw HE proportionally --- Vel needs to be in m/s (gmu*0.0254) -function Round.CrushCalc(Vel, FillerMass) - local Crushed = math.Clamp((Vel - ACF.HEATMinCrush) / (ACF.HEATMaxCrush - ACF.HEATMinCrush), 0,1) - local HE_Filler = Lerp(Crushed, FillerMass * ACF.HEATBoomConvert, FillerMass) - local HEAT_Filler = Lerp(Crushed, FillerMass, 0) - - return Crushed, HEAT_Filler, HE_Filler -end - --- coneang now required for slug recalculation at detonation, defaults to 55 if not present -function Round.CalcSlugMV(Data, HEATFillerMass) - --keep fillermass/2 so that penetrator stays the same. - return (HEATFillerMass / 2 * ACF.HEPower * math.sin(math.rad(10 + (Data.ConeAng or 55)) / 2) / Data.SlugMass) ^ ACF.HEATMVScale -end - --- Function to convert the player's slider data into the complete round data -function Round.convert(_, PlayerData) - local Data = {} - local ServerData = {} - local GUIData = {} - - if not PlayerData.PropLength then - PlayerData.PropLength = 0 - end - - if not PlayerData.ProjLength then - PlayerData.ProjLength = 0 - end - - PlayerData.Data5 = math.max(PlayerData.Data5 or 0, 0) - - if not PlayerData.Data6 then - PlayerData.Data6 = 0 - end - - if not PlayerData.Data7 then - PlayerData.Data7 = 0 - end - - if not PlayerData.Data10 then - PlayerData.Data10 = 0 - end - - PlayerData, Data, ServerData, GUIData = ACF_RoundBaseGunpowder(PlayerData, Data, ServerData, GUIData) - - local ConeThick = Data.Caliber / 50 - local ConeArea = 0 - local AirVol = 0 - ConeLength, ConeArea, AirVol = Round.ConeCalc(PlayerData.Data6, Data.Caliber / 2, PlayerData.ProjLength) - Data.ProjMass = math.max(GUIData.ProjVolume - PlayerData.Data5, 0) * 7.9 / 1000 + math.min(PlayerData.Data5, GUIData.ProjVolume) * ACF.HEDensity / 1000 + ConeArea * ConeThick * 7.9 / 1000 --Volume of the projectile as a cylinder - Volume of the filler - Volume of the crush cone * density of steel + Volume of the filler * density of TNT + Area of the cone * thickness * density of steel - Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, Data.ProjMass, Data.Caliber) - local Energy = ACF_Kinetic(Data.MuzzleVel * 39.37, Data.ProjMass, Data.LimitVel) - - local MaxVol = 0 - MaxVol, MaxLength, MaxRadius = ACF_RoundShellCapacity(Energy.Momentum, Data.FrArea, Data.Caliber, Data.ProjLength) - - GUIData.MinConeAng = 0 - GUIData.MaxConeAng = math.deg(math.atan((Data.ProjLength - ConeThick) / (Data.Caliber / 2))) - Data.ConeAng = math.Clamp(PlayerData.Data6 * 1, GUIData.MinConeAng, GUIData.MaxConeAng) - ConeLength, ConeArea, AirVol = Round.ConeCalc(Data.ConeAng, Data.Caliber / 2, Data.ProjLength) - local ConeVol = ConeArea * ConeThick - - GUIData.MinFillerVol = 0 - GUIData.MaxFillerVol = math.max(MaxVol - AirVol - ConeVol, GUIData.MinFillerVol) - GUIData.FillerVol = math.Clamp(PlayerData.Data5 * 1, GUIData.MinFillerVol, GUIData.MaxFillerVol) - - -- fillermass used for shell mass calcs - -- heatfillermass is how much fillermass is used to power heat jet - -- boomfillermass is how much fillermass creates HE damage on detonation. technically get 1/3 extra fillermass free as HE with no crushing, but screw trying to rebalance heat pen to properly use 1/3 of filler for HE and 2/3 for jet - -- distribution of heat and boom fillermass is calculated at detonation, or for GUI stuff - - Data.FillerMass = GUIData.FillerVol * ACF.HEDensity / 1450 - Data.ProjMass = math.max(GUIData.ProjVolume - GUIData.FillerVol - AirVol - ConeVol, 0) * 7.9 / 1000 + Data.FillerMass + ConeVol * 7.9 / 1000 - Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, Data.ProjMass, Data.Caliber) - - --Let's calculate the actual HEAT slug - Data.SlugMass = ConeVol * 7.9 / 1000 - local Rad = math.rad(Data.ConeAng / 2) - Data.SlugCaliber = Data.Caliber - Data.Caliber * (math.sin(Rad) * 0.5 + math.cos(Rad) * 1.5) / 2 - - local SlugFrArea = 3.1416 * (Data.SlugCaliber / 2) ^ 2 - Data.SlugPenArea = (SlugFrArea ^ ACF.PenAreaMod) / 1.6 - Data.SlugDragCoef = (SlugFrArea / 10000) / Data.SlugMass - Data.SlugRicochet = 500 --Base ricochet angle (The HEAT slug shouldn't ricochet at all) - - -- these are only for compatibility with other stuff. it's recalculated when the round is detonated - local _, heatfiller, boomfiller = Round.CrushCalc(Data.MuzzleVel, Data.FillerMass) - Data.BoomFillerMass = boomfiller - Data.SlugMV = Round.CalcSlugMV(Data, heatfiller) - - --Random bullshit left - Data.CasingMass = Data.ProjMass - Data.FillerMass - ConeVol * 7.9 / 1000 - Data.ShovePower = 0.1 - Data.PenArea = Data.FrArea ^ ACF.PenAreaMod - Data.DragCoef = (Data.FrArea / 10000) / Data.ProjMass - Data.LimitVel = 100 --Most efficient penetration speed in m/s - Data.KETransfert = 0.1 --Kinetic energy transfert to the target for movement purposes - Data.Ricochet = 60 --Base ricochet angle - Data.DetonatorAngle = 75 - - Data.Detonated = false - Data.NotFirstPen = false - Data.CanFuze = Data.Caliber > ACF.MinFuzeCaliber -- Can fuze on calibers > 20mm - Data.CartMass = Data.PropMass + Data.ProjMass - - --Only the crates need this part - if SERVER then - ServerData.Id = PlayerData.Id - ServerData.Type = PlayerData.Type - return table.Merge(Data,ServerData) - end - - --Only the GUI needs this part - if CLIENT then - GUIData = table.Merge(GUIData, Round.getDisplayData(Data)) - return table.Merge(Data, GUIData) - end -end - - -function Round.getDisplayData(Data) - local GUIData = {} - - -- these are only GUI info, it's recalculated when the round is detonated since it's vel dependent - GUIData.Crushed, GUIData.HEATFillerMass, GUIData.BoomFillerMass = Round.CrushCalc(Data.MuzzleVel, Data.FillerMass) - GUIData.SlugMV = Round.CalcSlugMV(Data, GUIData.HEATFillerMass) * (Data.SlugPenMul or 1) -- slugpenmul is a missiles thing - GUIData.SlugMassUsed = Data.SlugMass * (1 - GUIData.Crushed) - - local SlugEnergy = ACF_Kinetic(Data.MuzzleVel * 39.37 + GUIData.SlugMV * 39.37, GUIData.SlugMassUsed, 999999) - GUIData.MaxPen = (SlugEnergy.Penetration / Data.SlugPenArea) * ACF.KEtoRHA - - GUIData.TotalFragMass = Data.CasingMass + Data.SlugMass * GUIData.Crushed - GUIData.BlastRadius = GUIData.BoomFillerMass ^ 0.33 * 8 --*39.37 - GUIData.Fragments = math.max(math.floor((GUIData.BoomFillerMass / GUIData.TotalFragMass) * ACF.HEFrag), 2) - GUIData.FragMass = GUIData.TotalFragMass / GUIData.Fragments - GUIData.FragVel = (GUIData.BoomFillerMass * ACF.HEPower * 1000 / GUIData.TotalFragMass) ^ 0.5 - - return GUIData -end - - -function Round.network(Crate, BulletData) - Crate:SetNWString("AmmoType", "HEATFS") - Crate:SetNWString("AmmoID", BulletData.Id) - Crate:SetNWFloat("Caliber", BulletData.Caliber) - Crate:SetNWFloat("ProjMass", BulletData.ProjMass) - Crate:SetNWFloat("FillerMass", BulletData.FillerMass) - Crate:SetNWFloat("PropMass", BulletData.PropMass) - Crate:SetNWFloat("DragCoef", BulletData.DragCoef) - Crate:SetNWFloat("SlugMass", BulletData.SlugMass) - Crate:SetNWFloat("SlugCaliber", BulletData.SlugCaliber) - Crate:SetNWFloat("SlugDragCoef", BulletData.SlugDragCoef) - Crate:SetNWFloat("MuzzleVel", BulletData.MuzzleVel) - Crate:SetNWFloat("Tracer", BulletData.Tracer) -end - -function Round.cratetxt(BulletData) - local DData = Round.getDisplayData(BulletData) - - local str = - { - "Muzzle Velocity: ", math.Round(BulletData.MuzzleVel, 1), " m/s\n", - "Max Penetration: ", math.floor(DData.MaxPen), " mm\n", - "Blast Radius: ", math.Round(DData.BlastRadius, 1), " m\n", - "Blast Energy: ", math.floor(DData.BoomFillerMass * ACF.HEPower), " KJ" - } - - return table.concat(str) -end - -function Round.detonate(_, Bullet, HitPos) - local Crushed, HEATFillerMass, BoomFillerMass = Round.CrushCalc(Bullet.Flight:Length() * 0.0254, Bullet.FillerMass) - - ACF_HE(HitPos, BoomFillerMass, Bullet.CasingMass + Bullet.SlugMass * Crushed, Bullet.Owner, nil, Bullet.Gun) - - if Crushed == 1 then return false end -- no HEAT jet to fire off, it was all converted to HE - - Bullet.Detonated = true - Bullet.InitTime = ACF.CurTime - Bullet.Flight = Bullet.Flight + Bullet.Flight:GetNormalized() * Round.CalcSlugMV(Bullet, HEATFillerMass) * 39.37 - Bullet.FuseLength = 0.005 + 40 / (Bullet.Flight:Length() * 0.0254) - Bullet.NextPos = HitPos - Bullet.DragCoef = Bullet.SlugDragCoef - Bullet.ProjMass = Bullet.SlugMass * (1 - Crushed) - Bullet.Caliber = Bullet.SlugCaliber - Bullet.PenArea = Bullet.SlugPenArea - Bullet.Ricochet = Bullet.SlugRicochet - - return true -end - -function Round.propimpact(Index, Bullet, Target, HitNormal, HitPos, Bone) - if ACF_Check(Target) then - if Bullet.Detonated then - Bullet.NotFirstPen = true - - local Speed = Bullet.Flight:Length() / ACF.Scale - local Energy = ACF_Kinetic(Speed, Bullet.ProjMass, 999999) - local HitRes = ACF_RoundImpact(Bullet, Speed, Energy, Target, HitPos, HitNormal, Bone) - - if HitRes.Overkill > 0 then - table.insert(Bullet.Filter, Target) --"Penetrate" (Ingoring the prop for the retry trace) - Bullet.Flight = Bullet.Flight:GetNormalized() * math.sqrt(Energy.Kinetic * (1 - HitRes.Loss) * ((Bullet.NotFirstPen and ACF.HEATPenLayerMul) or 1) * 2000 / Bullet.ProjMass) * 39.37 - - return "Penetrated" - else - return false - end - else - local Speed = Bullet.Flight:Length() / ACF.Scale - local Energy = ACF_Kinetic(Speed, Bullet.ProjMass - Bullet.FillerMass, Bullet.LimitVel) - local HitRes = ACF_RoundImpact(Bullet, Speed, Energy, Target, HitPos, HitNormal, Bone) - - if HitRes.Ricochet then - return "Ricochet" - else - local jet = Round.detonate(Index, Bullet, HitPos, HitNormal) - - if jet then - return "Penetrated" - else - return false - end - end - end - else - table.insert(Bullet.Filter, Target) - return "Penetrated" - end - - return false -end - -function Round.worldimpact(Index, Bullet, HitPos, HitNormal) - if not Bullet.Detonated then - local jet = Round.detonate(Index, Bullet, HitPos, HitNormal) - - if jet then - return "Penetrated" - else - return false - end - end - - local Energy = ACF_Kinetic(Bullet.Flight:Length() / ACF.Scale, Bullet.ProjMass, 999999) - local HitRes = ACF_PenetrateGround(Bullet, Energy, HitPos, HitNormal) - - if HitRes.Penetrated then - return "Penetrated" - else - return false - end -end - -function Round.endflight(Index) - ACF_RemoveBullet(Index) -end - -local DecalIndex = ACF.GetAmmoDecalIndex - -function Round.endeffect(_, Bullet) - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetRadius(Bullet.Caliber) - Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) - - util.Effect("ACF_Impact", Effect) -end - -function Round.pierceeffect(Effect, Bullet) - if Bullet.Detonated then - local Data = EffectData() - Data:SetOrigin(Bullet.SimPos) - Data:SetNormal(Bullet.SimFlight:GetNormalized()) - Data:SetScale(Bullet.SimFlight:Length()) - Data:SetMagnitude(Bullet.RoundMass) - Data:SetRadius(Bullet.Caliber) - Data:SetDamageType(DecalIndex(Bullet.AmmoType)) - - util.Effect("ACF_Penetration", Data) - else - local _, _, BoomFillerMass = Round.CrushCalc(Bullet.SimFlight:Length() * 0.0254, Bullet.FillerMass) - local Data = EffectData() - Data:SetOrigin(Bullet.SimPos) - Data:SetNormal(Bullet.SimFlight:GetNormalized()) - Data:SetRadius(math.max(BoomFillerMass ^ 0.33 * 8 * 39.37, 1)) - - util.Effect("ACF_HEAT_Explosion", Data) - - Bullet.Detonated = true - - Effect:SetModel("models/Gibs/wood_gib01e.mdl") - end -end - -function Round.ricocheteffect(_, Bullet) - local Detonated = Bullet.Detonated - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetScale(Bullet.SimFlight:Length()) - Effect:SetMagnitude(Bullet.RoundMass) - Effect:SetRadius(Bullet.Caliber) - Effect:SetDamageType(DecalIndex(Detonated and Bullet.AmmoType or "AP")) - - util.Effect("ACF_Ricochet", Effect) -end - -function Round.guicreate(Panel, Table) - acfmenupanel:AmmoSelect(ACF.AmmoBlacklist.HEATFS) - - acfmenupanel:CPanelText("Desc", "") --Description (Name, Desc) - acfmenupanel:CPanelText("LengthDisplay", "") --Total round length (Name, Desc) - - --Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("PropLength", 0, 0, 1000, 3, "Propellant Length", "") - acfmenupanel:AmmoSlider("ProjLength", 0, 0, 1000, 3, "Projectile Length", "") - acfmenupanel:AmmoSlider("ConeAng", 0, 0, 1000, 3, "HEAT Cone Angle", "") - acfmenupanel:AmmoSlider("FillerVol", 0, 0, 1000, 3, "Total HEAT Warhead volume", "") - - acfmenupanel:AmmoCheckbox("Tracer", "Tracer", "") --Tracer checkbox (Name, Title, Desc) - - acfmenupanel:CPanelText("VelocityDisplay", "") --Proj muzzle velocity (Name, Desc) - acfmenupanel:CPanelText("BlastDisplay", "") --HE Blast data (Name, Desc) - acfmenupanel:CPanelText("FragDisplay", "") --HE Fragmentation data (Name, Desc) - - acfmenupanel:CPanelText("SlugDisplay", "") --HEAT Slug data (Name, Desc) - - Round.guiupdate(Panel, Table) -end - -function Round.guiupdate(Panel) - local PlayerData = {} - PlayerData.Id = acfmenupanel.AmmoData.Data.id --AmmoSelect GUI - PlayerData.Type = "HEATFS" --Hardcoded, match ACFRoundTypes table index - PlayerData.PropLength = acfmenupanel.AmmoData.PropLength --PropLength slider - PlayerData.ProjLength = acfmenupanel.AmmoData.ProjLength --ProjLength slider - PlayerData.Data5 = acfmenupanel.AmmoData.FillerVol - PlayerData.Data6 = acfmenupanel.AmmoData.ConeAng - local Tracer = 0 - - if acfmenupanel.AmmoData.Tracer then - Tracer = 1 - end - - PlayerData.Data10 = Tracer --Tracer - - local Data = Round.convert(Panel, PlayerData) - - RunConsoleCommand("acfmenu_data1", acfmenupanel.AmmoData.Data.id) - RunConsoleCommand("acfmenu_data2", PlayerData.Type) - RunConsoleCommand("acfmenu_data3", Data.PropLength) --For Gun ammo, Data3 should always be Propellant - RunConsoleCommand("acfmenu_data4", Data.ProjLength) - RunConsoleCommand("acfmenu_data5", Data.FillerVol) - RunConsoleCommand("acfmenu_data6", Data.ConeAng) - RunConsoleCommand("acfmenu_data10", Data.Tracer) - - acfmenupanel:AmmoUpdate() - acfmenupanel:AmmoSlider("PropLength", Data.PropLength, Data.MinPropLength + (Data.Caliber * 3.75), Data.MaxTotalLength, 3, "Propellant Length", "Propellant Mass : " .. (math.floor(Data.PropMass * 1000)) .. " g") --Propellant Length Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("ProjLength", Data.ProjLength, Data.MinProjLength, Data.MaxTotalLength, 3, "Projectile Length", "Projectile Mass : " .. (math.floor(Data.ProjMass * 1000)) .. " g") --Projectile Length Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("ConeAng", Data.ConeAng, Data.MinConeAng, Data.MaxConeAng, 0, "Crush Cone Angle", "") --HE Filler Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("FillerVol", Data.FillerVol, Data.MinFillerVol, Data.MaxFillerVol, 3, "HE Filler Volume", "HE Filler Mass : " .. (math.floor(Data.FillerMass * 1000)) .. " g") --HE Filler Slider (Name, Min, Max, Decimals, Title, Desc) - - acfmenupanel:AmmoCheckbox("Tracer", "Tracer : " .. (math.floor(Data.Tracer * 10) / 10) .. "cm\n", "") --Tracer checkbox (Name, Title, Desc) - - acfmenupanel:CPanelText("Desc", ACF.RoundTypes[PlayerData.Type].desc) --Description (Name, Desc) - acfmenupanel:CPanelText("LengthDisplay", "Cartridge Length : " .. (math.floor((Data.PropLength + Data.ProjLength + Data.Tracer) * 100) / 100) .. "/" .. Data.MaxTotalLength .. " cm") --Total round length (Name, Desc) - acfmenupanel:CPanelText("VelocityDisplay", "Muzzle Velocity : " .. math.floor(Data.MuzzleVel * ACF.Scale) .. " m/s") --Proj muzzle velocity (Name, Desc) - acfmenupanel:CPanelText("BlastDisplay", "Blast Radius : " .. (math.floor(Data.BlastRadius * 100) / 100) .. " m") --Proj muzzle velocity (Name, Desc) - acfmenupanel:CPanelText("FragDisplay", "Fragments : " .. Data.Fragments .. "\n Average Fragment Weight : " .. (math.floor(Data.FragMass * 10000) / 10) .. " g \n Average Fragment Velocity : " .. math.floor(Data.FragVel) .. " m/s") --Proj muzzle penetration (Name, Desc) - - local R1V, R1P = ACF_PenRanging(Data.MuzzleVel, Data.DragCoef, Data.ProjMass, Data.PenArea, Data.LimitVel, 300) - R1P = (ACF_Kinetic((R1V + Data.SlugMV) * 39.37, Data.SlugMassUsed, 999999).Penetration / Data.SlugPenArea) * ACF.KEtoRHA - local R2V, R2P = ACF_PenRanging(Data.MuzzleVel, Data.DragCoef, Data.ProjMass, Data.PenArea, Data.LimitVel, 800) - R2P = (ACF_Kinetic((R2V + Data.SlugMV) * 39.37, Data.SlugMassUsed, 999999).Penetration / Data.SlugPenArea) * ACF.KEtoRHA - - acfmenupanel:CPanelText("SlugDisplay", "Penetrator Mass : " .. (math.floor(Data.SlugMassUsed * 10000) / 10) .. " g \n Penetrator Caliber : " .. (math.floor(Data.SlugCaliber * 100) / 10) .. " mm \n Penetrator Velocity : " .. math.floor(Data.MuzzleVel + Data.SlugMV) .. " m/s \n Penetrator Maximum Penetration : " .. math.floor(Data.MaxPen) .. " mm RHA\n\n300m pen: " .. math.Round(R1P,0) .. "mm @ " .. math.Round(R1V,0) .. " m\\s\n800m pen: " .. math.Round(R2P,0) .. "mm @ " .. math.Round(R2V,0) .. " m\\s\n\nThe range data is an approximation and may not be entirely accurate.") --Proj muzzle penetration (Name, Desc) -end - -ACF.RoundTypes.HEATFS = Round --Set the round properties - -ACF.RegisterAmmoDecal("HEATFS", "damage/heat_pen", "damage/heat_rico", function(Caliber) return Caliber * 0.1667 end) diff --git a/lua/acf/shared/rounds/hp.lua b/lua/acf/shared/rounds/hp.lua deleted file mode 100644 index c383e02bf..000000000 --- a/lua/acf/shared/rounds/hp.lua +++ /dev/null @@ -1,151 +0,0 @@ -local Round = table.Copy(ACF.RoundTypes.AP) -- inherit from AP - -ACF.AmmoBlacklist.HP = ACF.AmmoBlacklist.AP -Round.type = "Ammo" --Tells the spawn menu what entity to spawn -Round.name = "Hollow Point (HP)" --Human readable name -Round.model = "models/munitions/round_100mm_shot.mdl" --Shell flight model -Round.desc = "A solid shell with a soft point, meant to flatten against armour" - --- Function to convert the player's slider data into the complete round data -function Round.convert(_, PlayerData) - local Data = {} - local ServerData = {} - local GUIData = {} - - if not PlayerData.PropLength then - PlayerData.PropLength = 0 - end - - if not PlayerData.ProjLength then - PlayerData.ProjLength = 0 - end - - PlayerData.Data5 = math.max(PlayerData.Data5 or 0, 0) - - if not PlayerData.Data10 then - PlayerData.Data10 = 0 - end - - PlayerData, Data, ServerData, GUIData = ACF_RoundBaseGunpowder(PlayerData, Data, ServerData, GUIData) - --if GUIData.MaxCavVol != nil then PlayerData.Data5 = math.min(PlayerData.Data5, GUIData.MaxCavVol) end - --Shell sturdiness calcs - Data.ProjMass = Data.FrArea * (Data.ProjLength * 7.9 / 1000) --Volume of the projectile as a cylinder * density of steel - Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, Data.ProjMass, Data.Caliber) - local Energy = ACF_Kinetic(Data.MuzzleVel * 39.37, Data.ProjMass, Data.LimitVel) - local MaxVol = ACF_RoundShellCapacity(Energy.Momentum, Data.FrArea, Data.Caliber, Data.ProjLength) - GUIData.MinCavVol = 0 - GUIData.MaxCavVol = math.min(GUIData.ProjVolume, MaxVol) - Data.CavVol = math.Clamp(PlayerData.Data5, GUIData.MinCavVol, GUIData.MaxCavVol) - Data.ProjMass = ((Data.FrArea * Data.ProjLength) - Data.CavVol) * 7.9 / 1000 --Volume of the projectile as a cylinder * fraction missing due to hollow point (Data5) * density of steel - Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, Data.ProjMass, Data.Caliber) - local ExpRatio = (Data.CavVol / GUIData.ProjVolume) - Data.ShovePower = 0.2 + ExpRatio / 2 - Data.ExpCaliber = Data.Caliber + ExpRatio * Data.ProjLength - Data.PenArea = 3.1416 * ((Data.ExpCaliber / 2) ^ 2 ) ^ ACF.PenAreaMod - Data.DragCoef = ((Data.FrArea / 10000) / Data.ProjMass) - Data.LimitVel = 800 --Most efficient penetration speed in m/s - Data.KETransfert = 0.1 --Kinetic energy transfert to the target for movement purposes - Data.Ricochet = 60 --Base ricochet angle - Data.CartMass = Data.PropMass + Data.ProjMass - - --Only the crates need this part - if SERVER then - ServerData.Id = PlayerData.Id - ServerData.Type = PlayerData.Type - - return table.Merge(Data, ServerData) - end - - --Only the GUI needs this part - if CLIENT then - GUIData = table.Merge(GUIData, Round.getDisplayData(Data)) - - return table.Merge(Data, GUIData) - end -end - -function Round.getDisplayData(Data) - local GUIData = {} - local Energy = ACF_Kinetic(Data.MuzzleVel * 39.37, Data.ProjMass, Data.LimitVel) - GUIData.MaxKETransfert = Energy.Kinetic * Data.ShovePower - GUIData.MaxPen = (Energy.Penetration / Data.PenArea) * ACF.KEtoRHA - - return GUIData -end - -function Round.network(Crate, BulletData) - Crate:SetNWString("AmmoType", "HP") - Crate:SetNWString("AmmoID", BulletData.Id) - Crate:SetNWFloat("Caliber", BulletData.Caliber) - Crate:SetNWFloat("ProjMass", BulletData.ProjMass) - Crate:SetNWFloat("PropMass", BulletData.PropMass) - Crate:SetNWFloat("ExpCaliber", BulletData.ExpCaliber) - Crate:SetNWFloat("DragCoef", BulletData.DragCoef) - Crate:SetNWFloat("MuzzleVel", BulletData.MuzzleVel) - Crate:SetNWFloat("Tracer", BulletData.Tracer) -end - -function Round.cratetxt(BulletData) - local DData = Round.getDisplayData(BulletData) - local str = {"Muzzle Velocity: ", math.Round(BulletData.MuzzleVel, 1), " m/s\n", "Max Penetration: ", math.floor(DData.MaxPen), " mm\n", "Expanded Caliber: ", math.floor(BulletData.ExpCaliber * 10), " mm\n", "Imparted Energy: ", math.floor(DData.MaxKETransfert), " KJ"} - - return table.concat(str) -end - -function Round.guicreate(Panel) - acfmenupanel:AmmoSelect(ACF.AmmoBlacklist.HP) - - acfmenupanel:CPanelText("Desc", "") --Description (Name, Desc) - acfmenupanel:CPanelText("LengthDisplay", "") --Total round length (Name, Desc) - acfmenupanel:AmmoSlider("PropLength", 0, 0, 1000, 3, "Propellant Length", "") --Propellant Length Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("ProjLength", 0, 0, 1000, 3, "Projectile Length", "") --Projectile Length Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("CavVol", 0, 0, 1000, 2, "Hollow Point Length", "") --Hollow Point Cavity Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoCheckbox("Tracer", "Tracer", "") --Tracer checkbox (Name, Title, Desc) - acfmenupanel:CPanelText("VelocityDisplay", "") --Proj muzzle velocity (Name, Desc) - acfmenupanel:CPanelText("KEDisplay", "") --Proj muzzle KE (Name, Desc) - --acfmenupanel:CPanelText("RicoDisplay", "") --estimated rico chance - acfmenupanel:CPanelText("PenetrationDisplay", "") --Proj muzzle penetration (Name, Desc) - Round.guiupdate(Panel, nil) -end - -function Round.guiupdate(Panel) - local PlayerData = {} - PlayerData.Id = acfmenupanel.AmmoData.Data.id --AmmoSelect GUI - PlayerData.Type = "HP" --Hardcoded, match ACFRoundTypes table index - PlayerData.PropLength = acfmenupanel.AmmoData.PropLength --PropLength slider - PlayerData.ProjLength = acfmenupanel.AmmoData.ProjLength --ProjLength slider - PlayerData.Data5 = acfmenupanel.AmmoData.CavVol - local Tracer = 0 - - if acfmenupanel.AmmoData.Tracer then - Tracer = 1 - end - - PlayerData.Data10 = Tracer --Tracer - local Data = Round.convert(Panel, PlayerData) - RunConsoleCommand("acfmenu_data1", acfmenupanel.AmmoData.Data.id) - RunConsoleCommand("acfmenu_data2", PlayerData.Type) - RunConsoleCommand("acfmenu_data3", Data.PropLength) --For Gun ammo, Data3 should always be Propellant - RunConsoleCommand("acfmenu_data4", Data.ProjLength) --And Data4 total round mass - RunConsoleCommand("acfmenu_data5", Data.CavVol) - RunConsoleCommand("acfmenu_data10", Data.Tracer) - - acfmenupanel:AmmoUpdate() - acfmenupanel:AmmoSlider("PropLength", Data.PropLength, Data.MinPropLength, Data.MaxTotalLength, 3, "Propellant Length", "Propellant Mass : " .. (math.floor(Data.PropMass * 1000)) .. " g") --Propellant Length Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("ProjLength", Data.ProjLength, Data.MinProjLength, Data.MaxTotalLength, 3, "Projectile Length", "Projectile Mass : " .. (math.floor(Data.ProjMass * 1000)) .. " g") --Projectile Length Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("CavVol", Data.CavVol, Data.MinCavVol, Data.MaxCavVol, 2, "Hollow Point cavity Volume", "Expanded caliber : " .. (math.floor(Data.ExpCaliber * 10)) .. " mm") --Hollow Point Cavity Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoCheckbox("Tracer", "Tracer : " .. (math.floor(Data.Tracer * 10) / 10) .. "cm\n", "") --Tracer checkbox (Name, Title, Desc) - acfmenupanel:CPanelText("Desc", ACF.RoundTypes[PlayerData.Type].desc) --Description (Name, Desc) - acfmenupanel:CPanelText("LengthDisplay", "Cartridge Length : " .. (math.floor((Data.PropLength + Data.ProjLength + Data.Tracer) * 100) / 100) .. "/" .. Data.MaxTotalLength .. " cm") --Total round length (Name, Desc) - acfmenupanel:CPanelText("VelocityDisplay", "Muzzle Velocity : " .. math.floor(Data.MuzzleVel * ACF.Scale) .. " m/s") --Proj muzzle velocity (Name, Desc) - acfmenupanel:CPanelText("KEDisplay", "Kinetic Energy Transfered : " .. math.floor(Data.MaxKETransfert) .. " KJ") --Proj muzzle KE (Name, Desc) - --local RicoAngs = ACF_RicoProbability( Data.Ricochet, Data.MuzzleVel*ACF.Scale ) - --acfmenupanel:CPanelText("RicoDisplay", "Ricochet probability vs impact angle:\n".." 0% @ "..RicoAngs.Min.." degrees\n 50% @ "..RicoAngs.Mean.." degrees\n100% @ "..RicoAngs.Max.." degrees") - local R1V, R1P = ACF_PenRanging(Data.MuzzleVel, Data.DragCoef, Data.ProjMass, Data.PenArea, Data.LimitVel, 300) - local R2V, R2P = ACF_PenRanging(Data.MuzzleVel, Data.DragCoef, Data.ProjMass, Data.PenArea, Data.LimitVel, 800) - acfmenupanel:CPanelText("PenetrationDisplay", "Maximum Penetration : " .. math.floor(Data.MaxPen) .. " mm RHA\n\n300m pen: " .. math.Round(R1P, 0) .. "mm @ " .. math.Round(R1V, 0) .. " m\\s\n800m pen: " .. math.Round(R2P, 0) .. "mm @ " .. math.Round(R2V, 0) .. " m\\s\n\nThe range data is an approximation and may not be entirely accurate.") --Proj muzzle penetration (Name, Desc) -end - -ACF.RoundTypes.HP = Round --Set the round properties - -ACF.RegisterAmmoDecal("HP", "damage/ap_pen", "damage/ap_rico") \ No newline at end of file diff --git a/lua/acf/shared/rounds/refill.lua b/lua/acf/shared/rounds/refill.lua deleted file mode 100644 index 293bf69fb..000000000 --- a/lua/acf/shared/rounds/refill.lua +++ /dev/null @@ -1,59 +0,0 @@ -local Round = {} -Round.type = "Ammo" --Tells the spawn menu what entity to spawn -Round.name = "Refill" --Human readable name -Round.model = "models/munitions/round_100mm_shot.mdl" --Shell flight model -Round.desc = "Ammo Refill" - --- Function to convert the player's slider data into the complete round data -function Round.convert(_, PlayerData) - local BulletData = { - Id = PlayerData.Id, - Type = PlayerData.Type, - Caliber = ACF.Weapons.Guns[PlayerData.Id].caliber, - ProjMass = 5.5 * 7.9 / 100, --Volume of the projectile as a cylinder * streamline factor (Data5) * density of steel - PropMass = 5.5 * ACF.PDensity / 1000, --Volume of the case as a cylinder * Powder density converted from g to kg - FillerMass = 0, - DragCoef = 0, - Tracer = 0, - MuzzleVel = 0, - RoundVolume = 35, - } - - return BulletData -end - -function Round.getDisplayData() - return {} -end - -function Round.network(Crate, BulletData) - Crate:SetNWString("AmmoType", "Refill") - Crate:SetNWString("AmmoID", BulletData.Id) - Crate:SetNWFloat("Caliber", BulletData.Caliber) - Crate:SetNWFloat("ProjMass", BulletData.ProjMass) - Crate:SetNWFloat("FillerMass", BulletData.FillerMass) - Crate:SetNWFloat("PropMass", BulletData.PropMass) - Crate:SetNWFloat("DragCoef", BulletData.DragCoef) - Crate:SetNWFloat("MuzzleVel", BulletData.MuzzleVel) - Crate:SetNWFloat("Tracer", BulletData.Tracer) -end - -function Round.cratetxt() - return "" -end - -function Round.guicreate(Panel, Table) - acfmenupanel:AmmoSelect() - acfmenupanel:CPanelText("Desc", "") --Description (Name, Desc) - Round.guiupdate(Panel, Table) -end - -function Round.guiupdate() - RunConsoleCommand("acfmenu_data1", acfmenupanel.CData.AmmoId or "12.7mmMG") - RunConsoleCommand("acfmenu_data2", "Refill") - - acfmenupanel:AmmoUpdate() - acfmenupanel.CustomDisplay:PerformLayout() -end - -ACF.RoundTypes.Refill = Round --Set the round properties \ No newline at end of file diff --git a/lua/acf/shared/rounds/smoke.lua b/lua/acf/shared/rounds/smoke.lua deleted file mode 100644 index b26a1e1c5..000000000 --- a/lua/acf/shared/rounds/smoke.lua +++ /dev/null @@ -1,243 +0,0 @@ -ACF.AmmoBlacklist.SM = {"MG", "C", "GL", "HMG", "AL", "AC", "RAC", "SA", "SC"} -local Round = {} -Round.type = "Ammo" --Tells the spawn menu what entity to spawn -Round.name = "Smoke (SM)" --Human readable name -Round.model = "models/munitions/round_100mm_shot.mdl" --Shell flight model -Round.desc = "A shell filled white phosporous, detonating on impact. Smoke filler produces a long lasting cloud but takes a while to be effective, whereas WP filler quickly creates a cloud that also dissipates quickly." - -function Round.create(_, BulletData) - ACF_CreateBullet(BulletData) -end - --- Function to convert the player's slider data into the complete round data -function Round.convert(_, PlayerData) - local Data = {} - local ServerData = {} - local GUIData = {} - - if not PlayerData.PropLength then - PlayerData.PropLength = 0 - end - - if not PlayerData.ProjLength then - PlayerData.ProjLength = 0 - end - - PlayerData.Data5 = math.max(PlayerData.Data5 or 0, 0) - PlayerData.Data6 = math.max(PlayerData.Data6 or 0, 0) - - if not PlayerData.Data10 then - PlayerData.Data10 = 0 - end - - PlayerData, Data, ServerData, GUIData = ACF_RoundBaseGunpowder(PlayerData, Data, ServerData, GUIData) - --Shell sturdiness calcs - Data.ProjMass = math.max(GUIData.ProjVolume - PlayerData.Data5, 0) * 7.9 / 1000 + math.min(PlayerData.Data5, GUIData.ProjVolume) * ACF.HEDensity / 2000 --Volume of the projectile as a cylinder - Volume of the filler * density of steel + Volume of the filler * density of TNT - Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, Data.ProjMass, Data.Caliber) - local Energy = ACF_Kinetic(Data.MuzzleVel * 39.37, Data.ProjMass, Data.LimitVel) - local MaxVol = ACF_RoundShellCapacity(Energy.Momentum, Data.FrArea, Data.Caliber, Data.ProjLength) - GUIData.MinFillerVol = 0 - GUIData.MaxFillerVol = math.min(GUIData.ProjVolume, MaxVol) - GUIData.MaxSmokeVol = math.max(GUIData.MaxFillerVol - PlayerData.Data6, GUIData.MinFillerVol) - GUIData.MaxWPVol = math.max(GUIData.MaxFillerVol - PlayerData.Data5, GUIData.MinFillerVol) - local Ratio = math.min(GUIData.MaxFillerVol / (PlayerData.Data5 + PlayerData.Data6), 1) - GUIData.FillerVol = math.min(PlayerData.Data5 * Ratio, GUIData.MaxSmokeVol) - GUIData.WPVol = math.min(PlayerData.Data6 * Ratio, GUIData.MaxWPVol) - Data.FillerMass = GUIData.FillerVol * ACF.HEDensity / 2000 - Data.WPMass = GUIData.WPVol * ACF.HEDensity / 2000 - Data.ProjMass = math.max(GUIData.ProjVolume - (GUIData.FillerVol + GUIData.WPVol), 0) * 7.9 / 1000 + Data.FillerMass + Data.WPMass - Data.MuzzleVel = ACF_MuzzleVelocity(Data.PropMass, Data.ProjMass, Data.Caliber) - --Random bullshit left - Data.ShovePower = 0.1 - Data.PenArea = Data.FrArea ^ ACF.PenAreaMod - Data.DragCoef = ((Data.FrArea / 10000) / Data.ProjMass) - Data.LimitVel = 100 --Most efficient penetration speed in m/s - Data.KETransfert = 0.1 --Kinetic energy transfert to the target for movement purposes - Data.Ricochet = 60 --Base ricochet angle - Data.DetonatorAngle = 80 - Data.CanFuze = Data.Caliber > ACF.MinFuzeCaliber -- Can fuze on calibers > 20mm - Data.CartMass = Data.PropMass + Data.ProjMass - - --Only the crates need this part - if SERVER then - ServerData.Id = PlayerData.Id - ServerData.Type = PlayerData.Type - - return table.Merge(Data, ServerData) - end - - --Only the GUI needs this part - if CLIENT then - GUIData = table.Merge(GUIData, Round.getDisplayData(Data)) - - return table.Merge(Data, GUIData) - end -end - -function Round.network(Crate, BulletData) - Crate:SetNWString("AmmoType", "SM") - Crate:SetNWString("AmmoID", BulletData.Id) - Crate:SetNWFloat("Caliber", BulletData.Caliber) - Crate:SetNWFloat("ProjMass", BulletData.ProjMass) - Crate:SetNWFloat("FillerMass", BulletData.FillerMass) - Crate:SetNWFloat("WPMass", BulletData.WPMass) - Crate:SetNWFloat("PropMass", BulletData.PropMass) - Crate:SetNWFloat("DragCoef", BulletData.DragCoef) - Crate:SetNWFloat("MuzzleVel", BulletData.MuzzleVel) - Crate:SetNWFloat("Tracer", BulletData.Tracer) -end - -function Round.getDisplayData(Data) - local GUIData = {} - GUIData.SMFiller = math.min(math.log(1 + Data.FillerMass * 8 * 39.37) / 0.02303, 350) --smoke filler - GUIData.SMLife = math.Round(20 + GUIData.SMFiller / 4, 1) - GUIData.SMRadiusMin = math.Round(GUIData.SMFiller * 1.25 * 0.15 * 0.0254, 1) - GUIData.SMRadiusMax = math.Round(GUIData.SMFiller * 1.25 * 2 * 0.0254, 1) - GUIData.WPFiller = math.min(math.log(1 + Data.WPMass * 8 * 39.37) / 0.02303, 350) --wp filler - GUIData.WPLife = math.Round(6 + GUIData.WPFiller / 10, 1) - GUIData.WPRadiusMin = math.Round(GUIData.WPFiller * 1.25 * 0.0254, 1) - GUIData.WPRadiusMax = math.Round(GUIData.WPFiller * 1.25 * 2 * 0.0254, 1) - - return GUIData -end - -function Round.cratetxt(BulletData) - local GUIData = Round.getDisplayData(BulletData) - local str = {"Muzzle Velocity: ", math.Round(BulletData.MuzzleVel, 1), " m/s"} - - if GUIData.WPFiller > 0 then - local temp = {"\nWP Radius: ", GUIData.WPRadiusMin, " m to ", GUIData.WPRadiusMax, " m\n", "WP Lifetime: ", GUIData.WPLife, " s"} - - for i = 1, #temp do - str[#str + 1] = temp[i] - end - end - - if GUIData.SMFiller > 0 then - local temp = {"\nSM Radius: ", GUIData.SMRadiusMin, " m to ", GUIData.SMRadiusMax, " m\n", "SM Lifetime: ", GUIData.SMLife, " s"} - - for i = 1, #temp do - str[#str + 1] = temp[i] - end - end - - return table.concat(str) -end - -function Round.propimpact(_, Bullet, Target, HitNormal, HitPos, Bone) - if ACF_Check(Target) then - local Speed = Bullet.Flight:Length() / ACF.Scale - local Energy = ACF_Kinetic(Speed, Bullet.ProjMass - (Bullet.FillerMass + Bullet.WPMass), Bullet.LimitVel) - local HitRes = ACF_RoundImpact(Bullet, Speed, Energy, Target, HitPos, HitNormal, Bone) - if HitRes.Ricochet then return "Ricochet" end - end - - return false -end - -function Round.worldimpact() - return false -end - -function Round.endflight(Index) - ACF_RemoveBullet(Index) -end - -local DecalIndex = ACF.GetAmmoDecalIndex - -function Round.endeffect(_, Bullet) - local Crate = Bullet.Crate - local Color = IsValid(Crate) and Crate:GetColor() or Color(255, 255, 255) - - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetScale(math.max(Bullet.FillerMass * 8 * 39.37, 0)) - Effect:SetMagnitude(math.max(Bullet.WPMass * 8 * 39.37, 0)) - Effect:SetStart(Vector(Color.r, Color.g, Color.b)) - Effect:SetRadius(Bullet.Caliber) - - util.Effect("ACF_Smoke", Effect) -end - -function Round.pierceeffect(_, Bullet) - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetScale(Bullet.SimFlight:Length()) - Effect:SetMagnitude(Bullet.RoundMass) - Effect:SetRadius(Bullet.Caliber) - Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) - - util.Effect("ACF_Penetration", Effect) -end - -function Round.ricocheteffect(_, Bullet) - local Effect = EffectData() - Effect:SetOrigin(Bullet.SimPos) - Effect:SetNormal(Bullet.SimFlight:GetNormalized()) - Effect:SetScale(Bullet.SimFlight:Length()) - Effect:SetMagnitude(Bullet.RoundMass) - Effect:SetRadius(Bullet.Caliber) - Effect:SetDamageType(DecalIndex(Bullet.AmmoType)) - - util.Effect("ACF_Ricochet", Effect) -end - -function Round.guicreate(Panel, Table) - acfmenupanel:AmmoSelect(ACF.AmmoBlacklist.SM) - - acfmenupanel:CPanelText("Desc", "") --Description (Name, Desc) - acfmenupanel:CPanelText("LengthDisplay", "") --Total round length (Name, Desc) - acfmenupanel:AmmoSlider("PropLength", 0, 0, 1000, 3, "Propellant Length", "") --Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("ProjLength", 0, 0, 1000, 3, "Projectile Length", "") --Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("FillerVol", 0, 0, 1000, 3, "Smoke Filler", "") --Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("WPVol", 0, 0, 1000, 3, "WP Filler", "") --Slider (Name, Value, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoCheckbox("Tracer", "Tracer", "") --Tracer checkbox (Name, Title, Desc) - acfmenupanel:CPanelText("VelocityDisplay", "") --Proj muzzle velocity (Name, Desc) - acfmenupanel:CPanelText("BlastDisplay", "") --HE Blast data (Name, Desc) - acfmenupanel:CPanelText("FragDisplay", "") --HE Fragmentation data (Name, Desc) - Round.guiupdate(Panel, Table) -end - -function Round.guiupdate(Panel) - local PlayerData = {} - PlayerData.Id = acfmenupanel.AmmoData.Data.id --AmmoSelect GUI - PlayerData.Type = "SM" --Hardcoded, match ACFRoundTypes table index - PlayerData.PropLength = acfmenupanel.AmmoData.PropLength --PropLength slider - PlayerData.ProjLength = acfmenupanel.AmmoData.ProjLength --ProjLength slider - PlayerData.Data5 = acfmenupanel.AmmoData.FillerVol - PlayerData.Data6 = acfmenupanel.AmmoData.WPVol - - local Tracer = 0 - - if acfmenupanel.AmmoData.Tracer then - Tracer = 1 - end - - PlayerData.Data10 = Tracer --Tracer - local Data = Round.convert(Panel, PlayerData) - RunConsoleCommand("acfmenu_data1", acfmenupanel.AmmoData.Data.id) - RunConsoleCommand("acfmenu_data2", PlayerData.Type) - RunConsoleCommand("acfmenu_data3", Data.PropLength) --For Gun ammo, Data3 should always be Propellant - RunConsoleCommand("acfmenu_data4", Data.ProjLength) --And Data4 total round mass - RunConsoleCommand("acfmenu_data5", Data.FillerVol) - RunConsoleCommand("acfmenu_data6", Data.WPVol) - RunConsoleCommand("acfmenu_data10", Data.Tracer) - - acfmenupanel:AmmoUpdate() - acfmenupanel:AmmoSlider("PropLength", Data.PropLength, Data.MinPropLength, Data.MaxTotalLength, 3, "Propellant Length", "Propellant Mass : " .. (math.floor(Data.PropMass * 1000)) .. " g") --Propellant Length Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("ProjLength", Data.ProjLength, Data.MinProjLength, Data.MaxTotalLength, 3, "Projectile Length", "Projectile Mass : " .. (math.floor(Data.ProjMass * 1000)) .. " g") --Projectile Length Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("FillerVol", Data.FillerVol, Data.MinFillerVol, Data.MaxFillerVol, 3, "Smoke Filler Volume", "Smoke Filler Mass : " .. (math.floor(Data.FillerMass * 1000)) .. " g") --HE Filler Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoSlider("WPVol", Data.WPVol, Data.MinFillerVol, Data.MaxFillerVol, 3, "WP Filler Volume", "WP Filler Mass : " .. (math.floor(Data.WPMass * 1000)) .. " g") --HE Filler Slider (Name, Min, Max, Decimals, Title, Desc) - acfmenupanel:AmmoCheckbox("Tracer", "Tracer : " .. (math.floor(Data.Tracer * 10) / 10) .. "cm\n", "") --Tracer checkbox (Name, Title, Desc) - acfmenupanel:CPanelText("Desc", ACF.RoundTypes[PlayerData.Type].desc) --Description (Name, Desc) - acfmenupanel:CPanelText("LengthDisplay", "Cartridge Length : " .. (math.floor((Data.PropLength + Data.ProjLength + Data.Tracer) * 100) / 100) .. "/" .. Data.MaxTotalLength .. " cm") --Total round length (Name, Desc) - acfmenupanel:CPanelText("VelocityDisplay", "Muzzle Velocity : " .. math.floor(Data.MuzzleVel * ACF.Scale) .. " m/s") --Proj muzzle velocity (Name, Desc) - ---acfmenupanel:CPanelText("BlastDisplay", "Blast Radius : "..(math.floor(Data.BlastRadius*100)/1000).." m\n") --Proj muzzle velocity (Name, Desc) - ---acfmenupanel:CPanelText("FragDisplay", "Fragments : "..(Data.Fragments).."\n Average Fragment Weight : "..(math.floor(Data.FragMass*10000)/10).." ---g \n Average Fragment Velocity : "..math.floor(Data.FragVel).." m/s") --Proj muzzle penetration (Name, Desc) -end - -ACF.RoundTypes.SM = Round --Set the round properties - -ACF.RegisterAmmoDecal("SM", "damage/he_pen", "damage/he_rico") \ No newline at end of file diff --git a/lua/acf/shared/tool_operations/acf_copy.lua b/lua/acf/shared/tool_operations/acf_copy.lua new file mode 100644 index 000000000..90f60218a --- /dev/null +++ b/lua/acf/shared/tool_operations/acf_copy.lua @@ -0,0 +1,197 @@ +local CopiedData = {} +local Disabled = {} +local ACF = ACF + +local function GetDisabledData(Player, Class) + return Disabled[Player][Class] +end + +if SERVER then + util.AddNetworkString("ACF_SendCopyData") + util.AddNetworkString("ACF_SendDisabledData") + + net.Receive("ACF_SendDisabledData", function(_, Player) + local Class = net.ReadString() + local Data = net.ReadString() + local State = net.ReadBool() or nil + + if not IsValid(Player) then return end + + local DisabledData = GetDisabledData(Player, Class) + + DisabledData[Data] = State + end) + + hook.Add("PlayerInitialSpawn", "ACF Copy Data", function(Player) + CopiedData[Player] = {} + Disabled[Player] = {} + end) + + hook.Add("PlayerDisconnected", "ACF Copy Data", function(Player) + CopiedData[Player] = nil + Disabled[Player] = nil + end) +end + +local function GetCopyData(Player, Class) + return CopiedData[Player][Class] +end + +local function SaveCopyData(Player, Entity) + local Class = Entity:GetClass() + local Data = GetCopyData(Player, Class) + local List = {} + local Count = 0 + + if not Data then + Data = {} + + CopiedData[Player][Class] = Data + else + for K in pairs(Data) do + Data[K] = nil + end + end + + for _, V in ipairs(Entity.DataStore) do + local Value = Entity[V] + + if Value ~= nil then + Count = Count + 1 + + Data[V] = Value + List[Count] = { + Key = V, + Value = Value, + } + end + end + + if not GetDisabledData(Player, Class) then + Disabled[Player][Class] = {} + end + + return util.TableToJSON(List) +end + +local function GetSpawnData(Player, Entity, Class) + local Saved = GetCopyData(Player, Class) + + if not Saved then return end + + local Ignored = GetDisabledData(Player, Class) + local Data = {} + + for K, V in pairs(Saved) do + if Ignored[K] then + Data[K] = Entity and Entity[K] + else + Data[K] = V + end + end + + return Data +end + +local function CreateNewEntity(Player, Trace) + local Class = ACF.GetClientData(Player, "CopyClass") + + if not Class then return false end + + local Data = GetSpawnData(Player, nil, Class) + local Position = Trace.HitPos + Trace.HitNormal * 128 + local Angles = Trace.HitNormal:Angle():Up():Angle() + local Message = "" + + local Success, Result = ACF.CreateEntity(Class, Player, Position, Angles, Data) + + if not Success then + Message = "Couldn't create entity: " .. Result + else + local PhysObj = Result:GetPhysicsObject() + + Result:DropToFloor() + + if IsValid(PhysObj) then + PhysObj:EnableMotion(false) + end + + Message = "Entity created successfully." + end + + ACF.SendMessage(Player, Result and "Info" or "Error", Message) + + return true +end + +ACF.RegisterOperation("acfcopy", "Main", "CopyPaste", { + OnLeftClick = function(Tool, Trace) + if Trace.HitSky then return false end + + local Entity = Trace.Entity + local Player = Tool:GetOwner() + + if not IsValid(Entity) then return CreateNewEntity(Player, Trace) end + if not isfunction(Entity.Update) then + ACF.SendMessage(Player, "Error", "This entity doesn't support updating!") + return false + end + + local Class = Entity:GetClass() + local Data = GetSpawnData(Player, Entity, Class) + + if not Data then + ACF.SendMessage(Player, "Error", "No information has been copied for '", Class, "' entities!") + return false + end + + local Result, Message = ACF.UpdateEntity(Entity, Data) + + if not Result then + Message = "Couldn't update entity: " .. Message + end + + ACF.SendMessage(Player, Result and "Info" or "Error", Message) + + return true + end, + OnRightClick = function(Tool, Trace) + if Trace.HitSky then return false end + + local Entity = Trace.Entity + + if not IsValid(Entity) then return false end + if not Entity.DataStore then return false end + + local Player = Tool:GetOwner() + local List = SaveCopyData(Player, Entity) + + net.Start("ACF_SendCopyData") + net.WriteString(Entity:GetClass()) + net.WriteString(List) + net.Send(Player) + + return true + end, +}) + +ACF.RegisterToolInfo("acfcopy", "Main", "CopyPaste", { + name = "left", + text = "Update the ACF entity with the copied information for its class.", +}) + +ACF.RegisterToolInfo("acfcopy", "Main", "CopyPaste", { + name = "left_spawn", + text = "If no entity is hit, a new entity will be created with the copied information.", + icon2 = "gui/info", +}) + +ACF.RegisterToolInfo("acfcopy", "Main", "CopyPaste", { + name = "right", + text = "Copy the relevant information from an ACF entity.", +}) + +ACF.RegisterToolInfo("acfcopy", "Main", "CopyPaste", { + name = "info", + text = "You can toggle the copied information you want to apply/ignore when updating an ACF entity on the tool menu.", +}) diff --git a/lua/acf/shared/tool_operations/acf_menu.lua b/lua/acf/shared/tool_operations/acf_menu.lua new file mode 100644 index 000000000..b46dc7eee --- /dev/null +++ b/lua/acf/shared/tool_operations/acf_menu.lua @@ -0,0 +1,8 @@ +local ACF = ACF + +ACF.CreateMenuOperation("Weapon", "weapon", "ammo crate") +ACF.CreateMenuOperation("Missile", "rack", "ammo crate") +ACF.CreateMenuOperation("Engine", "engine", "fuel tank") +ACF.CreateMenuOperation("Component", "component") +ACF.CreateMenuOperation("Gearbox", "gearbox") +ACF.CreateMenuOperation("Sensor", "sensor") diff --git a/lua/acf/shared/weapons/autocannon.lua b/lua/acf/shared/weapons/autocannon.lua new file mode 100644 index 000000000..34d6c888f --- /dev/null +++ b/lua/acf/shared/weapons/autocannon.lua @@ -0,0 +1,80 @@ +ACF.RegisterWeaponClass("AC", { + Name = "Autocannon", + Description = "Autocannons have a rather high weight and bulk for the ammo they fire, but they can fire it extremely fast.", + MuzzleFlash = "auto_muzzleflash_noscale", + Spread = 0.2, + Sound = "acf_base/weapons/ac_fire4.mp3", + Caliber = { + Min = 20, + Max = 50, + }, +}) + +ACF.RegisterWeapon("20mmAC", "AC", { + Name = "20mm Autocannon", + Description = "The 20mm autocannon is the smallest of the family; having a good rate of fire but a tiny shell.", + Model = "models/autocannon/autocannon_20mm.mdl", + Caliber = 20, + Mass = 500, + Year = 1930, + MagSize = 100, + MagReload = 15, + Cyclic = 250, + Round = { + MaxLength = 32, + PropMass = 0.13, + } +}) + +ACF.RegisterWeapon("30mmAC", "AC", { + Name = "30mm Autocannon", + Description = "The 30mm autocannon can fire shells with sufficient space for a small payload, and has modest anti-armor capability", + Model = "models/autocannon/autocannon_30mm.mdl", + Caliber = 30, + Mass = 1000, + Year = 1935, + MagSize = 75, + MagReload = 20, + Cyclic = 225, + Round = { + MaxLength = 39, + PropMass = 0.350, + } +}) + +ACF.RegisterWeapon("40mmAC", "AC", { + Name = "40mm Autocannon", + Description = "The 40mm autocannon can fire shells with sufficient space for a useful payload, and can get decent penetration with proper rounds.", + Model = "models/autocannon/autocannon_40mm.mdl", + Caliber = 40, + Mass = 1500, + Year = 1940, + MagSize = 30, + MagReload = 25, + Cyclic = 200, + Round = { + MaxLength = 45, + PropMass = 0.9, + } +}) + +ACF.RegisterWeapon("50mmAC", "AC", { + Name = "50mm Autocannon", + Description = "The 50mm autocannon fires shells comparable with the 50mm Cannon, making it capable of destroying light armour quite quickly.", + Model = "models/autocannon/autocannon_50mm.mdl", + Caliber = 50, + Mass = 2000, + Year = 1965, + MagSize = 25, + MagReload = 30, + Cyclic = 175, + Round = { + MaxLength = 52, + PropMass = 1.2, + } +}) + +ACF.SetCustomAttachment("models/autocannon/autocannon_20mm.mdl", "muzzle", Vector(66), Angle(0, 0, 180)) +ACF.SetCustomAttachment("models/autocannon/autocannon_30mm.mdl", "muzzle", Vector(84), Angle(0, 0, 180)) +ACF.SetCustomAttachment("models/autocannon/autocannon_40mm.mdl", "muzzle", Vector(96), Angle(0, 0, 180)) +ACF.SetCustomAttachment("models/autocannon/autocannon_50mm.mdl", "muzzle", Vector(120), Angle(0, 0, 180)) diff --git a/lua/acf/shared/weapons/autoloader.lua b/lua/acf/shared/weapons/autoloader.lua new file mode 100644 index 000000000..e799e4f2d --- /dev/null +++ b/lua/acf/shared/weapons/autoloader.lua @@ -0,0 +1,80 @@ +ACF.RegisterWeaponClass("AL", { + Name = "Autoloader", + Description = "A cannon with attached autoloading mechanism. While it allows for several quick shots, the mechanism adds considerable bulk, weight, and magazine reload time.", + MuzzleFlash = "cannon_muzzleflash_noscale", + Spread = 0.08, + Sound = "acf_base/weapons/autoloader.mp3", + Caliber = { + Min = 75, + Max = 140, + }, +}) + +ACF.RegisterWeapon("75mmAL", "AL", { + Name = "75mm Autoloading Cannon", + Description = "A quick-firing 75mm gun, pops off a number of rounds in relatively short order.", + Model = "models/tankgun/tankgun_al_75mm.mdl", + Caliber = 75, + Mass = 1892, + Year = 1946, + MagSize = 8, + MagReload = 15, + Cyclic = 30, + Round = { + MaxLength = 78, + PropMass = 3.8, + } +}) + +ACF.RegisterWeapon("100mmAL", "AL", { + Name = "100mm Autoloading Cannon", + Description = "The 100mm is good for rapidly hitting medium armor, then running like your ass is on fire to reload.", + Model = "models/tankgun/tankgun_al_100mm.mdl", + Caliber = 100, + Mass = 3325, + Year = 1956, + MagSize = 6, + MagReload = 21, + Cyclic = 18, + Round = { + MaxLength = 93, + PropMass = 9.5, + } +}) + +ACF.RegisterWeapon("120mmAL", "AL", { + Name = "120mm Autoloading Cannon", + Description = "The 120mm autoloader can do serious damage before reloading, but the reload time is killer.", + Model = "models/tankgun/tankgun_al_120mm.mdl", + Caliber = 120, + Mass = 6050, + Year = 1956, + MagSize = 5, + MagReload = 27, + Cyclic = 11, + Round = { + MaxLength = 110, + PropMass = 18, + } +}) + +ACF.RegisterWeapon("140mmAL", "AL", { + Name = "140mm Autoloading Cannon", + Description = "The 140mm can shred a medium tank's armor with one magazine, and even function as shoot & scoot artillery, with its useful HE payload.", + Model = "models/tankgun/tankgun_al_140mm.mdl", + Caliber = 140, + Mass = 8830, + Year = 1970, + MagSize = 5, + MagReload = 35, + Cyclic = 8, + Round = { + MaxLength = 127, + PropMass = 28, + } +}) + +ACF.SetCustomAttachment("models/tankgun/tankgun_al_75mm.mdl", "muzzle", Vector(109.65), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/tankgun/tankgun_al_100mm.mdl", "muzzle", Vector(146.2), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/tankgun/tankgun_al_120mm.mdl", "muzzle", Vector(175.44), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/tankgun/tankgun_al_140mm.mdl", "muzzle", Vector(204.68), Angle(0, 0, 90)) diff --git a/lua/acf/shared/weapons/cannon.lua b/lua/acf/shared/weapons/cannon.lua new file mode 100644 index 000000000..9de9887f5 --- /dev/null +++ b/lua/acf/shared/weapons/cannon.lua @@ -0,0 +1,98 @@ +ACF.RegisterWeaponClass("C", { + Name = "Cannon", + Description = "High velocity guns that can fire very powerful ammunition, but are rather slow to reload.", + MuzzleFlash = "cannon_muzzleflash_noscale", + Spread = 0.08, + Sound = "acf_base/weapons/cannon_new.mp3", + Caliber = { + Min = 20, + Max = 140, + }, +}) + +ACF.RegisterWeapon("37mmC", "C", { + Name = "37mm Cannon", + Description = "A light and fairly weak cannon with good accuracy.", + Model = "models/tankgun/tankgun_37mm.mdl", + Caliber = 37, + Mass = 350, + Year = 1919, + Sound = "acf_base/weapons/ac_fire4.mp3", + Round = { + MaxLength = 48, + PropMass = 1.125, + } +}) + +ACF.RegisterWeapon("50mmC", "C", { + Name = "50mm Cannon", + Description = "The 50mm is surprisingly fast-firing, with good effectiveness against light armor, but a pea-shooter compared to its bigger cousins", + Model = "models/tankgun/tankgun_50mm.mdl", + Caliber = 50, + Mass = 665, + Year = 1935, + Sound = "acf_base/weapons/ac_fire4.mp3", + Round = { + MaxLength = 63, + PropMass = 2.1, + } +}) + +ACF.RegisterWeapon("75mmC", "C", { + Name = "75mm Cannon", + Description = "The 75mm is still rather respectable in rate of fire, but has only modest payload. Often found on the Eastern Front, and on cold war light tanks.", + Model = "models/tankgun/tankgun_75mm.mdl", + Caliber = 75, + Mass = 1420, + Year = 1942, + Round = { + MaxLength = 78, + PropMass = 3.8, + } +}) + +ACF.RegisterWeapon("100mmC", "C", { + Name = "100mm Cannon", + Description = "The 100mm was a benchmark for the early cold war period, and has great muzzle velocity and hitting power, while still boasting a respectable, if small, payload.", + Model = "models/tankgun/tankgun_100mm.mdl", + Caliber = 100, + Mass = 2750, + Year = 1944, + Round = { + MaxLength = 93, + PropMass = 9.5, + } +}) + +ACF.RegisterWeapon("120mmC", "C", { + Name = "120mm Cannon", + Description = "Often found in MBTs, the 120mm shreds lighter armor with utter impunity, and is formidable against even the big boys.", + Model = "models/tankgun/tankgun_120mm.mdl", + Caliber = 120, + Mass = 5200, + Year = 1955, + Round = { + MaxLength = 110, + PropMass = 18, + } +}) + +ACF.RegisterWeapon("140mmC", "C", { + Name = "140mm Cannon", + Description = "The 140mm fires a massive shell with enormous penetrative capability, but has a glacial reload speed and a very hefty weight.", + Model = "models/tankgun/tankgun_140mm.mdl", + Caliber = 140, + Mass = 8180, + Year = 1990, + Round = { + MaxLength = 127, + PropMass = 28, + } +}) + +ACF.SetCustomAttachment("models/tankgun/tankgun_37mm.mdl", "muzzle", Vector(55.77), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/tankgun/tankgun_50mm.mdl", "muzzle", Vector(75.36, -0.01), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/tankgun/tankgun_75mm.mdl", "muzzle", Vector(113.04, -0.01), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/tankgun/tankgun_100mm.mdl", "muzzle", Vector(150.72, -0.01), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/tankgun/tankgun_120mm.mdl", "muzzle", Vector(180.85, -0.02), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/tankgun/tankgun_140mm.mdl", "muzzle", Vector(210.99, -0.02), Angle(0, 0, 90)) diff --git a/lua/acf/shared/weapons/grenadelauncher.lua b/lua/acf/shared/weapons/grenadelauncher.lua new file mode 100644 index 000000000..e31487ba1 --- /dev/null +++ b/lua/acf/shared/weapons/grenadelauncher.lua @@ -0,0 +1,30 @@ +ACF.RegisterWeaponClass("GL", { + Name = "Grenade Launcher", + Description = "Grenade Launchers can fire shells with relatively large payloads at a fast rate, but with very limited velocities and poor accuracy.", + MuzzleFlash = "gl_muzzleflash_noscale", + Spread = 0.28, + Sound = "acf_base/weapons/grenadelauncher.mp3", + IsBoxed = true, + Caliber = { + Min = 25, + Max = 40, + }, +}) + +ACF.RegisterWeapon("40mmGL", "GL", { + Name = "40mm Grenade Launcher", + Description = "The 40mm chews up infantry but is about as useful as tits on a nun for fighting armor. Often found on 4x4s rolling through the third world.", + Model = "models/launcher/40mmgl.mdl", + Caliber = 40, + Mass = 55, + Year = 1970, + MagSize = 30, + MagReload = 7.5, + Cyclic = 200, + Round = { + MaxLength = 7.5, + PropMass = 0.01, + } +}) + +ACF.SetCustomAttachment("models/launcher/40mmgl.mdl", "muzzle", Vector(19), Angle(0, 0, -180)) diff --git a/lua/acf/shared/weapons/heavymachinegun.lua b/lua/acf/shared/weapons/heavymachinegun.lua new file mode 100644 index 000000000..296ec43b1 --- /dev/null +++ b/lua/acf/shared/weapons/heavymachinegun.lua @@ -0,0 +1,95 @@ +ACF.RegisterWeaponClass("HMG", { + Name = "Heavy Machinegun", + Description = "Designed as autocannons for aircraft, HMGs are rapid firing, lightweight, and compact but sacrifice accuracy, magazine size, and reload times.", + MuzzleFlash = "mg_muzzleflash_noscale", + Spread = 1.2, + Sound = "acf_base/weapons/mg_fire3.mp3", + Caliber = { + Min = 13, + Max = 40, + }, + LongBarrel = { + Index = 2, + Submodel = 4, + NewPos = "muzzle2", + } +}) + +ACF.RegisterWeapon("13mmHMG", "HMG", { + Name = "13mm Heavy Machinegun", + Description = "The lightest of the HMGs, the 13mm has a rapid fire rate but suffers from poor payload size. Often used to strafe ground troops or annoy low-flying aircraft.", + Model = "models/machinegun/machinegun_20mm.mdl", + Caliber = 13, + Mass = 90, + Year = 1935, + MagSize = 35, + MagReload = 6, + Cyclic = 550, + Round = { + MaxLength = 22, + PropMass = 0.09, + } +}) + +ACF.RegisterWeapon("20mmHMG", "HMG", { + Name = "20mm Heavy Machinegun", + Description = "The 20mm has a rapid fire rate but suffers from poor payload size. Often used to strafe ground troops or annoy low-flying aircraft.", + Model = "models/machinegun/machinegun_20mm_compact.mdl", + Caliber = 20, + Mass = 160, + Year = 1935, + MagSize = 30, + MagReload = 6, + Cyclic = 525, + Round = { + MaxLength = 30, + PropMass = 0.12, + } +}) + +ACF.RegisterWeapon("30mmHMG", "HMG", { + Name = "30mm Heavy Machinegun", + Description = "30mm shell chucker, light and compact. Your average cold war dogfight go-to.", + Model = "models/machinegun/machinegun_30mm_compact.mdl", + Caliber = 30, + Mass = 480, + Year = 1941, + MagSize = 25, + MagReload = 6, + Cyclic = 500, + Round = { + MaxLength = 37, + PropMass = 0.35, + } +}) + +ACF.RegisterWeapon("40mmHMG", "HMG", { + Name = "40mm Heavy Machinegun", + Description = "The heaviest of the heavy machineguns. Massively powerful with a killer reload and hefty ammunition requirements, it can pop even relatively heavy targets with ease.", + Model = "models/machinegun/machinegun_40mm_compact.mdl", + Caliber = 40, + Mass = 780, + Year = 1955, + MagSize = 20, + MagReload = 8, + Cyclic = 475, + Round = { + MaxLength = 42, + PropMass = 0.9, + } +}) + +ACF.SetCustomAttachments("models/machinegun/machinegun_20mm_compact.mdl", { + { Name = "muzzle", Pos = Vector(30.93, -0.02), Ang = Angle(0, 0, 90) }, + { Name = "muzzle2", Pos = Vector(69.93, -0.15), Ang = Angle(0, 0, 90) }, +}) + +ACF.SetCustomAttachments("models/machinegun/machinegun_30mm_compact.mdl", { + { Name = "muzzle", Pos = Vector(41.76, -0.03), Ang = Angle(0, 0, 90) }, + { Name = "muzzle2", Pos = Vector(94.41, -0.2), Ang = Angle(0, 0, 90) }, +}) + +ACF.SetCustomAttachments("models/machinegun/machinegun_40mm_compact.mdl", { + { Name = "muzzle", Pos = Vector(51.04, -0.03), Ang = Angle(0, 0, 90) }, + { Name = "muzzle2", Pos = Vector(115.39, -0.25), Ang = Angle(0, 0, 90) }, +}) diff --git a/lua/acf/shared/weapons/howitzer.lua b/lua/acf/shared/weapons/howitzer.lua new file mode 100644 index 000000000..e32d3760c --- /dev/null +++ b/lua/acf/shared/weapons/howitzer.lua @@ -0,0 +1,82 @@ +ACF.RegisterWeaponClass("HW", { + Name = "Howitzer", + Description = "Howitzers are limited to rather mediocre muzzle velocities, but can fire extremely heavy projectiles with large useful payload capacities.", + MuzzleFlash = "howie_muzzleflash_noscale", + Spread = 0.1, + Sound = "acf_base/weapons/howitzer_new2.mp3", + Caliber = { + Min = 75, + Max = 203, + }, +}) + +ACF.RegisterWeapon("75mmHW", "HW", { + Name = "75mm Howitzer", + Description = "Often found being towed by large smelly animals, the 75mm has a high rate of fire, and is surprisingly lethal against light armor. Great for a sustained barrage against someone you really don't like.", + Model = "models/howitzer/howitzer_75mm.mdl", + Caliber = 75, + Mass = 530, + Year = 1900, + Round = { + MaxLength = 60, + PropMass = 1.8, + } +}) + +ACF.RegisterWeapon("105mmHW", "HW", { + Name = "105mm Howitzer", + Description = "The 105 lobs a big shell far, and its HEAT rounds can be extremely effective against even heavier armor.", + Model = "models/howitzer/howitzer_105mm.mdl", + Caliber = 105, + Mass = 1480, + Year = 1900, + Round = { + MaxLength = 86, + PropMass = 3.75, + } +}) + +ACF.RegisterWeapon("122mmHW", "HW", { + Name = "122mm Howitzer", + Description = "The 122mm bridges the gap between the 105 and the 155, providing a lethal round with a big splash radius.", + Model = "models/howitzer/howitzer_122mm.mdl", + Caliber = 122, + Mass = 3420, + Year = 1900, + Round = { + MaxLength = 106, + PropMass = 7, + } +}) + +ACF.RegisterWeapon("155mmHW", "HW", { + Name = "155mm Howitzer", + Description = "The 155 is a classic heavy artillery round, with good reason. A versatile weapon, it's found on most modern SPGs.", + Model = "models/howitzer/howitzer_155mm.mdl", + Caliber = 155, + Mass = 5340, + Year = 1900, + Round = { + MaxLength = 124, + PropMass = 13.5, + } +}) + +ACF.RegisterWeapon("203mmHW", "HW", { + Name = "203mm Howitzer", + Description = "An 8-inch deck gun, found on siege artillery and cruisers.", + Model = "models/howitzer/howitzer_203mm.mdl", + Caliber = 203, + Mass = 10280, + Year = 1900, + Round = { + MaxLength = 162.4, + PropMass = 28.5, + } +}) + +ACF.SetCustomAttachment("models/howitzer/howitzer_75mm.mdl", "muzzle", Vector(71.67, 0, -0.77)) +ACF.SetCustomAttachment("models/howitzer/howitzer_105mm.mdl", "muzzle", Vector(101.08, 0, -1.08)) +ACF.SetCustomAttachment("models/howitzer/howitzer_122mm.mdl", "muzzle", Vector(117.51, 0, -1.26)) +ACF.SetCustomAttachment("models/howitzer/howitzer_155mm.mdl", "muzzle", Vector(149.31, 0, -1.6)) +ACF.SetCustomAttachment("models/howitzer/howitzer_203mm.mdl", "muzzle", Vector(195.59, 0, -2.1)) diff --git a/lua/acf/shared/weapons/machinegun.lua b/lua/acf/shared/weapons/machinegun.lua new file mode 100644 index 000000000..3497c75f1 --- /dev/null +++ b/lua/acf/shared/weapons/machinegun.lua @@ -0,0 +1,81 @@ +ACF.RegisterWeaponClass("MG", { + Name = "Machinegun", + Description = "Machineguns are light guns that fire equally light bullets at a fast rate.", + MuzzleFlash = "mg_muzzleflash_noscale", + Spread = 0.16, + Sound = "acf_base/weapons/mg_fire4.mp3", + IsBoxed = true, + Caliber = { + Min = 5.56, + Max = 20, + }, +}) + +ACF.RegisterWeapon("7.62mmMG", "MG", { + Name = "7.62mm Machinegun", + Description = "The 7.62mm is effective against infantry, but its usefulness against armor is laughable at best.", + Model = "models/machinegun/machinegun_762mm.mdl", + Caliber = 7.62, + Mass = 15, + Year = 1930, + MagSize = 250, + MagReload = 5, + Cyclic = 700, -- Rounds per minute + Round = { + MaxLength = 13, + PropMass = 0.04, + } +}) + +ACF.RegisterWeapon("12.7mmMG", "MG", { + Name = "12.7mm Machinegun", + Description = "The 12.7mm MG is still light, finding its way into a lot of mountings, including on top of tanks.", + Model = "models/machinegun/machinegun_127mm.mdl", + Caliber = 12.7, + Mass = 30, + Year = 1910, + MagSize = 150, + MagReload = 6, + Cyclic = 600, + Round = { + MaxLength = 15.8, + PropMass = 0.03, + } +}) + +ACF.RegisterWeapon("14.5mmMG", "MG", { + Name = "14.5mm Machinegun", + Description = "The 14.5mm MG trades its smaller stablemates' rate of fire for more armor penetration and damage.", + Model = "models/machinegun/machinegun_145mm.mdl", + Caliber = 14.5, + Mass = 45, + Year = 1932, + MagSize = 90, + MagReload = 7, + Cyclic = 500, + Round = { + MaxLength = 19.5, + PropMass = 0.04, + } +}) + +ACF.RegisterWeapon("20mmMG", "MG", { + Name = "20mm Machinegun", + Description = "The 20mm MG is practically a cannon in its own right; the weight and recoil made it difficult to mount on light land vehicles, though it was adapted for use on both aircraft and ships.", + Model = "models/machinegun/machinegun_20mm.mdl", + Caliber = 20, + Mass = 95, + Year = 1935, + MagSize = 200, + MagReload = 8, + Cyclic = 400, + Round = { + MaxLength = 22, + PropMass = 0.09, + } +}) + +ACF.SetCustomAttachment("models/machinegun/machinegun_762mm.mdl", "muzzle", Vector(26.53, 0, -0.05), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/machinegun/machinegun_127mm.mdl", "muzzle", Vector(37.14, 0, -0.07), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/machinegun/machinegun_145mm.mdl", "muzzle", Vector(39.79, 0, -0.08), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/machinegun/machinegun_20mm.mdl", "muzzle", Vector(53.05, 0, -0.11), Angle(0, 0, 90)) diff --git a/lua/acf/shared/weapons/mortar.lua b/lua/acf/shared/weapons/mortar.lua new file mode 100644 index 000000000..31247f238 --- /dev/null +++ b/lua/acf/shared/weapons/mortar.lua @@ -0,0 +1,82 @@ +ACF.RegisterWeaponClass("MO", { + Name = "Mortar", + Description = "Mortars are able to fire shells with usefull payloads from a light weight gun, at the price of limited velocities.", + MuzzleFlash = "mortar_muzzleflash_noscale", + Spread = 0.72, + Sound = "acf_base/weapons/mortar_new.mp3", + Caliber = { + Min = 37, + Max = 280, + }, +}) + +ACF.RegisterWeapon("60mmM", "MO", { + Name = "60mm Mortar", + Description = "The 60mm is a common light infantry support weapon, with a high rate of fire but a puny payload.", + Model = "models/mortar/mortar_60mm.mdl", + Caliber = 60, + Mass = 60, + Year = 1930, + Round = { + MaxLength = 20, + PropMass = 0.037, + } +}) + +ACF.RegisterWeapon("80mmM", "MO", { + Name = "80mm Mortar", + Description = "The 80mm is a common infantry support weapon, with a good bit more boom than its little cousin.", + Model = "models/mortar/mortar_80mm.mdl", + Caliber = 80, + Mass = 120, + Year = 1930, + Round = { + MaxLength = 28, + PropMass = 0.055, + } +}) + +ACF.RegisterWeapon("120mmM", "MO", { + Name = "120mm Mortar", + Description = "The versatile 120 is sometimes vehicle-mounted to provide quick boomsplat to support the infantry. Carries more boom in its boomsplat, has good HEAT performance, and is more accurate in high-angle firing.", + Model = "models/mortar/mortar_120mm.mdl", + Caliber = 120, + Mass = 640, + Year = 1935, + Round = { + MaxLength = 45, + PropMass = 0.175, + } +}) + +ACF.RegisterWeapon("150mmM", "MO", { + Name = "150mm Mortar", + Description = "The perfect balance between the 120mm and the 200mm. Can prove a worthy main gun weapon, as well as a mighty good mortar emplacement", + Model = "models/mortar/mortar_150mm.mdl", + Caliber = 150, + Mass = 1255, + Year = 1945, + Round = { + MaxLength = 58, + PropMass = 0.235, + } +}) + +ACF.RegisterWeapon("200mmM", "MO", { + Name = "200mm Mortar", + Description = "The 200mm is a beast, often used against fortifications. Though enormously powerful, feel free to take a nap while it reloads", + Model = "models/mortar/mortar_200mm.mdl", + Caliber = 200, + Mass = 2850, + Year = 1940, + Round = { + MaxLength = 80, + PropMass = 0.330, + } +}) + +ACF.SetCustomAttachment("models/mortar/mortar_60mm.mdl", "muzzle", Vector(12.01), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/mortar/mortar_80mm.mdl", "muzzle", Vector(16.02), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/mortar/mortar_120mm.mdl", "muzzle", Vector(24.02), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/mortar/mortar_150mm.mdl", "muzzle", Vector(30.03), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/mortar/mortar_200mm.mdl", "muzzle", Vector(40.04), Angle(0, 0, 90)) diff --git a/lua/acf/shared/weapons/rotaryautocannon.lua b/lua/acf/shared/weapons/rotaryautocannon.lua new file mode 100644 index 000000000..209401a69 --- /dev/null +++ b/lua/acf/shared/weapons/rotaryautocannon.lua @@ -0,0 +1,63 @@ +ACF.RegisterWeaponClass("RAC", { + Name = "Rotary Autocannon", + Description = "Rotary Autocannons sacrifice weight, bulk and accuracy over classic autocannons to get the highest rate of fire possible.", + MuzzleFlash = "mg_muzzleflash_noscale", + Spread = 0.48, + Sound = "acf_base/weapons/mg_fire3.mp3", + Caliber = { + Min = 7.62, + Max = 37, + }, +}) + +ACF.RegisterWeapon("14.5mmRAC", "RAC", { + Name = "14.5mm Rotary Autocannon", + Description = "A lightweight rotary autocannon, used primarily against infantry and light vehicles.", + Model = "models/rotarycannon/kw/14_5mmrac.mdl", + Caliber = 14.5, + Mass = 400, + Year = 1962, + MagSize = 250, + MagReload = 10, + Cyclic = 2250, + Round = { + MaxLength = 25, + PropMass = 0.06, + } +}) + +ACF.RegisterWeapon("20mmRAC", "RAC", { + Name = "20mm Rotary Autocannon", + Description = "The 20mm is able to chew up light armor with decent penetration or put up a big flak screen. Typically mounted on ground attack aircraft, SPAAGs and the ocassional APC. Suffers from a moderate cooldown period between bursts to avoid overheating the barrels.", + Model = "models/rotarycannon/kw/20mmrac.mdl", + Caliber = 20, + Mass = 760, + Year = 1965, + MagSize = 200, + MagReload = 15, + Cyclic = 2000, + Round = { + MaxLength = 30, + PropMass = 0.12, + } +}) + +ACF.RegisterWeapon("30mmRAC", "RAC", { + Name = "30mm Rotary Autocannon", + Description = "The 30mm is the bane of ground-attack aircraft, able to tear up light armor without giving one single fuck. Also seen in the skies above dead T-72s. Has a moderate cooldown period between bursts to avoid overheating the barrels.", + Model = "models/rotarycannon/kw/30mmrac.mdl", + Caliber = 30, + Mass = 1500, + Year = 1975, + MagSize = 100, + MagReload = 20, + Cyclic = 1500, + Round = { + MaxLength = 40, + PropMass = 0.350, + } +}) + +ACF.SetCustomAttachment("models/rotarycannon/kw/14_5mmrac.mdl", "muzzle", Vector(43.21, 0, 1.26)) +ACF.SetCustomAttachment("models/rotarycannon/kw/20mmrac.mdl", "muzzle", Vector(59.6, 0, 1.74)) +ACF.SetCustomAttachment("models/rotarycannon/kw/30mmrac.mdl", "muzzle", Vector(89.4, 0, 2.61)) diff --git a/lua/acf/shared/weapons/semiauto.lua b/lua/acf/shared/weapons/semiauto.lua new file mode 100644 index 000000000..5211aa7e5 --- /dev/null +++ b/lua/acf/shared/weapons/semiauto.lua @@ -0,0 +1,98 @@ +ACF.RegisterWeaponClass("SA", { + Name = "Semiautomatic Cannon", + Description = "Semiautomatic cannons offer light weight, small size, and high rates of fire at the cost of often reloading and low accuracy.", + MuzzleFlash = "semi_muzzleflash_noscale", + Spread = 0.12, + Sound = "acf_base/weapons/sa_fire1.mp3", + IsBoxed = true, + Caliber = { + Min = 20, + Max = 76, + }, +}) + +ACF.RegisterWeapon("25mmSA", "SA", { + Name = "25mm Semiautomatic Cannon", + Description = "The 25mm semiauto can quickly put five rounds downrange, being lethal, yet light.", + Model = "models/autocannon/semiautocannon_25mm.mdl", + Caliber = 25, + Mass = 250, + Year = 1935, + MagSize = 5, + MagReload = 2.5, + Cyclic = 300, + Round = { + MaxLength = 39, + PropMass = 0.5, + } +}) + +ACF.RegisterWeapon("37mmSA", "SA", { + Name = "37mm Semiautomatic Cannon", + Description = "The 37mm is surprisingly powerful, its five-round clips boasting a respectable payload and a high muzzle velocity.", + Model = "models/autocannon/semiautocannon_37mm.mdl", + Caliber = 37, + Mass = 500, + Year = 1940, + MagSize = 5, + MagReload = 3.7, + Cyclic = 250, + Round = { + MaxLength = 42, + PropMass = 1.125, + } +}) + +ACF.RegisterWeapon("45mmSA", "SA", { + Name = "45mm Semiautomatic Cannon", + Description = "The 45mm can easily shred light armor, with a respectable rate of fire, but its armor penetration pales in comparison to regular cannons.", + Model = "models/autocannon/semiautocannon_45mm.mdl", + Caliber = 45, + Mass = 750, + Year = 1965, + MagSize = 5, + MagReload = 4.5, + Cyclic = 225, + Round = { + MaxLength = 52, + PropMass = 1.8, + } +}) + +ACF.RegisterWeapon("57mmSA", "SA", { + Name = "57mm Semiautomatic Cannon", + Description = "The 57mm is a respectable light armament, offering considerable penetration and moderate fire rate.", + Model = "models/autocannon/semiautocannon_57mm.mdl", + Caliber = 57, + Mass = 1000, + Year = 1965, + MagSize = 5, + MagReload = 5.7, + Cyclic = 200, + Round = { + MaxLength = 62, + PropMass = 2, + } +}) + +ACF.RegisterWeapon("76mmSA", "SA", { + Name = "76mm Semiautomatic Cannon", + Description = "The 76mm semiauto is a fearsome weapon, able to put five 76mm rounds downrange in 8 seconds.", + Model = "models/autocannon/semiautocannon_76mm.mdl", + Caliber = 76.2, + Mass = 2000, + Year = 1984, + MagSize = 5, + MagReload = 7.6, + Cyclic = 150, + Round = { + MaxLength = 70, + PropMass = 4.75, + } +}) + +ACF.SetCustomAttachment("models/autocannon/semiautocannon_25mm.mdl", "muzzle", Vector(44), Angle(0, 0, 180)) +ACF.SetCustomAttachment("models/autocannon/semiautocannon_37mm.mdl", "muzzle", Vector(65.12), Angle(0, 0, 180)) +ACF.SetCustomAttachment("models/autocannon/semiautocannon_45mm.mdl", "muzzle", Vector(79.2), Angle(0, 0, 180)) +ACF.SetCustomAttachment("models/autocannon/semiautocannon_57mm.mdl", "muzzle", Vector(109.12), Angle(0, 0, 180)) +ACF.SetCustomAttachment("models/autocannon/semiautocannon_76mm.mdl", "muzzle", Vector(167.2), Angle(0, 0, 180)) diff --git a/lua/acf/shared/weapons/shortcannon.lua b/lua/acf/shared/weapons/shortcannon.lua new file mode 100644 index 000000000..3f567e160 --- /dev/null +++ b/lua/acf/shared/weapons/shortcannon.lua @@ -0,0 +1,98 @@ +ACF.RegisterWeaponClass("SC", { + Name = "Short-Barrelled Cannon", + Description = "Short cannons trade muzzle velocity and accuracy for lighter weight and smaller size, with more penetration than howitzers and lighter than cannons.", + MuzzleFlash = "cannon_muzzleflash_noscale", + Spread = 0.16, + Sound = "acf_base/weapons/cannon_new.mp3", + Caliber = { + Min = 37, + Max = 140, + }, +}) + +ACF.RegisterWeapon("37mmSC", "SC", { + Name = "37mm Short Cannon", + Description = "Quick-firing and light, but penetration is laughable. You're better off throwing rocks.", + Model = "models/tankgun/tankgun_short_37mm.mdl", + Caliber = 37, + Mass = 200, + Year = 1915, + Sound = "acf_base/weapons/ac_fire4.mp3", + Round = { + MaxLength = 45, + PropMass = 0.29, + } +}) + +ACF.RegisterWeapon("50mmSC", "SC", { + Name = "50mm Short Cannon", + Description = "The 50mm is a quick-firing pea-shooter, good for scouts, and common on old interwar tanks.", + Model = "models/tankgun/tankgun_short_50mm.mdl", + Caliber = 50, + Mass = 330, + Year = 1915, + Sound = "acf_base/weapons/ac_fire4.mp3", + Round = { + MaxLength = 63, + PropMass = 0.6, + } +}) + +ACF.RegisterWeapon("75mmSC", "SC", { + Name = "75mm Short Cannon", + Description = "The 75mm is common WW2 medium tank armament, and still useful in many other applications.", + Model = "models/tankgun/tankgun_short_75mm.mdl", + Caliber = 75, + Mass = 750, + Year = 1936, + Round = { + MaxLength = 76, + PropMass = 2, + } +}) + +ACF.RegisterWeapon("100mmSC", "SC", { + Name = "100mm Short Cannon", + Description = "The 100mm is an effective infantry-support or antitank weapon, with a lot of uses and surprising lethality.", + Model = "models/tankgun/tankgun_short_100mm.mdl", + Caliber = 100, + Mass = 1750, + Year = 1940, + Round = { + MaxLength = 93, + PropMass = 4.5, + } +}) + +ACF.RegisterWeapon("120mmSC", "SC", { + Name = "120mm Short Cannon", + Description = "The 120mm is a formidable yet lightweight weapon, with excellent performance against larger vehicles.", + Model = "models/tankgun/tankgun_short_120mm.mdl", + Caliber = 120, + Mass = 3800, + Year = 1944, + Round = { + MaxLength = 110, + PropMass = 8.5, + } +}) + +ACF.RegisterWeapon("140mmSC", "SC", { + Name = "140mm Short Cannon", + Description = "A specialized weapon, developed from dark magic and anti-heavy tank hatred. Deal with it.", + Model = "models/tankgun/tankgun_short_140mm.mdl", + Caliber = 140, + Mass = 6040, + Year = 1999, + Round = { + MaxLength = 127, + PropMass = 12.8, + } +}) + +ACF.SetCustomAttachment("models/tankgun/tankgun_short_37mm.mdl", "muzzle", Vector(30.66), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/tankgun/tankgun_short_50mm.mdl", "muzzle", Vector(41.43, -0.01), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/tankgun/tankgun_short_75mm.mdl", "muzzle", Vector(62.15, -0.01), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/tankgun/tankgun_short_100mm.mdl", "muzzle", Vector(82.86, -0.01), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/tankgun/tankgun_short_120mm.mdl", "muzzle", Vector(99.42, -0.02), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/tankgun/tankgun_short_140mm.mdl", "muzzle", Vector(115.99, -0.02), Angle(0, 0, 90)) diff --git a/lua/acf/shared/weapons/smokelauncher.lua b/lua/acf/shared/weapons/smokelauncher.lua new file mode 100644 index 000000000..dac8a8f50 --- /dev/null +++ b/lua/acf/shared/weapons/smokelauncher.lua @@ -0,0 +1,36 @@ +ACF.RegisterWeaponClass("SL", { + Name = "Smoke Launcher", + Description = "Smoke launcher to block an attacker's line of sight.", + MuzzleFlash = "gl_muzzleflash_noscale", + Spread = 0.32, + Sound = "acf_base/weapons/smoke_launch.mp3", + IsBoxed = true, + Cleanup = "acf_smokelauncher", + LimitConVar = { + Name = "_acf_smokelauncher", + Amount = 10, + Text = "Maximum amount of ACF smoke launchers a player can create." + }, + Caliber = { + Min = 40, + Max = 81, + }, +}) + +ACF.RegisterWeapon("40mmSL", "SL", { + Name = "40mm Smoke Launcher", + Description = "", + Model = "models/launcher/40mmsl.mdl", + Caliber = 40, + Mass = 1, + Year = 1941, + MagSize = 1, + MagReload = 30, + Cyclic = 600, + Round = { + MaxLength = 17.5, + PropMass = 0.000075, + } +}) + +ACF.SetCustomAttachment("models/launcher/40mmsl.mdl", "muzzle", Vector(5), Angle(0, 0, 180)) diff --git a/lua/acf/shared/weapons/smoothbore.lua b/lua/acf/shared/weapons/smoothbore.lua new file mode 100644 index 000000000..d6dc380e8 --- /dev/null +++ b/lua/acf/shared/weapons/smoothbore.lua @@ -0,0 +1,54 @@ +ACF.RegisterWeaponClass("SB", { + Name = "Smoothbore Cannon", + Description = "More modern smoothbore cannons that can only fire munitions that do not rely on spinning for accuracy.", + MuzzleFlash = "cannon_muzzleflash_noscale", + Spread = 0.08, + Sound = "acf_base/weapons/cannon_new.mp3", + Caliber = { + Min = 20, + Max = 140, + }, +}) + +ACF.RegisterWeapon("105mmSB", "SB", { + Name = "105mm Smoothbore Cannon", + Description = "The 105mm was a benchmark for the early cold war period, and has great muzzle velocity and hitting power, while still boasting a respectable, if small, payload.", + Model = "models/tankgun_old/tankgun_100mm.mdl", + Caliber = 105, + Mass = 3550, + Year = 1970, + Round = { + MaxLength = 101, + PropMass = 9, + } +}) + +ACF.RegisterWeapon("120mmSB", "SB", { + Name = "120mm Smoothbore Cannon", + Description = "Often found in MBTs, the 120mm shreds lighter armor with utter impunity, and is formidable against even the big boys.", + Model = "models/tankgun_old/tankgun_120mm.mdl", + Caliber = 120, + Mass = 6000, + Year = 1975, + Round = { + MaxLength = 145, + PropMass = 18, + } +}) + +ACF.RegisterWeapon("140mmSB", "SB", { + Name = "140mm Smoothbore Cannon", + Description = "The 140mm fires a massive shell with enormous penetrative capability, but has a glacial reload speed and a very hefty weight.", + Model = "models/tankgun_old/tankgun_140mm.mdl", + Caliber = 140, + Mass = 8980, + Year = 1990, + Round = { + MaxLength = 145, + PropMass = 28, + } +}) + +ACF.SetCustomAttachment("models/tankgun_old/tankgun_100mm.mdl", "muzzle", Vector(135), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/tankgun_old/tankgun_120mm.mdl", "muzzle", Vector(162), Angle(0, 0, 90)) +ACF.SetCustomAttachment("models/tankgun_old/tankgun_140mm.mdl", "muzzle", Vector(189), Angle(0, 0, 90)) diff --git a/lua/autorun/acf_loader.lua b/lua/autorun/acf_loader.lua index b4c9c32e7..f5bb94298 100644 --- a/lua/autorun/acf_loader.lua +++ b/lua/autorun/acf_loader.lua @@ -15,87 +15,8 @@ ]]-- MsgN("\n===========[ Loading ACF ]============\n|") -local GunClasses = {} -local GunTable = {} -local MobilityTable = {} -local FuelTankTable = {} - if not ACF then ACF = {} end -ACF.RoundTypes = ACF.RoundTypes or {} - -ACF.Classes = ACF.Classes or { - GunClass = GunClasses -} - -ACF.Weapons = ACF.Weapons or { - Guns = GunTable, - Mobility = MobilityTable, - FuelTanks = FuelTankTable -} - -local gun_base = { - ent = "acf_gun", - type = "Guns" -} - -local engine_base = { - ent = "acf_engine", - type = "Mobility" -} - -local gearbox_base = { - ent = "acf_gearbox", - type = "Mobility", - sound = "vehicles/junker/jnk_fourth_cruise_loop2.wav" -} - -local fueltank_base = { - ent = "acf_fueltank", - type = "Mobility", - explosive = true -} - -do - function ACF_defineGunClass( id, data ) - data.id = id - GunClasses[ id ] = data - - PrecacheParticleSystem(data["muzzleflash"]) - end - - function ACF_defineGun( id, data ) - data.id = id - data.round.id = id - table.Inherit( data, gun_base ) - GunTable[ id ] = data - end - - function ACF_DefineEngine( id, data ) - data.id = id - table.Inherit( data, engine_base ) - MobilityTable[ id ] = data - end - - function ACF_DefineGearbox( id, data ) - data.id = id - table.Inherit( data, gearbox_base ) - MobilityTable[ id ] = data - end - - function ACF_DefineFuelTank( id, data ) - data.id = id - table.Inherit( data, fueltank_base ) - MobilityTable[ id ] = data - end - - function ACF_DefineFuelTankSize( id, data ) - data.id = id - table.Inherit( data, fueltank_base ) - FuelTankTable[ id ] = data - end -end - if SERVER then local Realms = {client = "client", server = "server", shared = "shared"} local Text = "| > Loaded %s serverside file(s).\n| > Loaded %s shared file(s).\n| > Loaded %s clientside file(s)." @@ -160,16 +81,6 @@ if SERVER then MsgN(Text:format(ServerCount, SharedCount, ClientCount)) elseif CLIENT then - - gun_base.guicreate = function( _, tbl ) ACFGunGUICreate( tbl ) end or nil - gun_base.guiupdate = function() return end - engine_base.guicreate = function( _, tbl ) ACFEngineGUICreate( tbl ) end or nil - engine_base.guiupdate = function() return end - gearbox_base.guicreate = function( _, tbl ) ACFGearboxGUICreate( tbl ) end or nil - gearbox_base.guiupdate = function() return end - fueltank_base.guicreate = function( _, tbl ) ACFFuelTankGUICreate( tbl ) end or nil - fueltank_base.guiupdate = function( _, tbl ) ACFFuelTankGUIUpdate( tbl ) end or nil - local Text = "| > Loaded %s clientside file(s).\n| > Skipped %s clientside file(s)." local FileCount, SkipCount = 0, 0 diff --git a/lua/effects/acf_bullet_effect.lua b/lua/effects/acf_bullet_effect.lua index 8d03c5047..17ceaa101 100644 --- a/lua/effects/acf_bullet_effect.lua +++ b/lua/effects/acf_bullet_effect.lua @@ -1,7 +1,10 @@ + +local ACF = ACF +local AmmoTypes = ACF.Classes.AmmoTypes local Bullets = ACF.BulletEffect function EFFECT:Init(Data) - self.Index = Data:GetHitBox() + self.Index = Data:GetDamageType() self:SetModel("models/munitions/round_100mm_shot.mdl") @@ -11,32 +14,34 @@ function EFFECT:Init(Data) return end - self.CreateTime = CurTime() + self.CreateTime = ACF.CurTime - local Bullet = Bullets[self.Index] - local Flight = Data:GetStart() * 10 - local Origin = Data:GetOrigin() - local Hit = Data:GetScale() + local CanDraw = Data:GetAttachment() > 0 + local Bullet = Bullets[self.Index] + local Flight = Data:GetStart() * 10 + local Origin = Data:GetOrigin() + local Hit = Data:GetScale() -- Scale encodes the hit type, so if it's 0 it's a new bullet, else it's an update so we need to remove the effect if Bullet and Hit > 0 then - local RoundData = ACF.RoundTypes[Bullet.AmmoType] + local RoundData = AmmoTypes[Bullet.AmmoType] -- Updating old effect with new values + Bullet.Effect.DrawEffect = CanDraw Bullet.SimFlight = Flight Bullet.SimPos = Origin if Hit == 1 then -- Bullet has reached end of flight, remove old effect - RoundData.endeffect(Bullet.Effect, Bullet) + RoundData:ImpactEffect(Bullet.Effect, Bullet) Bullet.Effect.Kill = true elseif Hit == 2 then -- Bullet penetrated, don't remove old effect - RoundData.pierceeffect(Bullet.Effect, Bullet) + RoundData:PenetrationEffect(Bullet.Effect, Bullet) elseif Hit == 3 then -- Bullet ricocheted, don't remove old effect - RoundData.ricocheteffect(Bullet.Effect, Bullet) + RoundData:RicochetEffect(Bullet.Effect, Bullet) end -- We don't need this new effect, so we just remove it @@ -51,23 +56,24 @@ function EFFECT:Init(Data) return end - local Tracer = Crate:GetNWFloat("Tracer") > 0 + -- TODO: Force crates to network and store this information on the client when they're created + local Tracer = Crate:GetNW2Float("Tracer") > 0 local BulletData = { - Crate = Crate, - SimFlight = Flight, - SimPos = Origin, + Crate = Crate, + SimFlight = Flight, + SimPos = Origin, SimPosLast = Origin, - Caliber = Crate:GetNWFloat("Caliber", 10), - RoundMass = Crate:GetNWFloat("ProjMass", 10), - FillerMass = Crate:GetNWFloat("FillerMass"), - WPMass = Crate:GetNWFloat("WPMass"), - DragCoef = Crate:GetNWFloat("DragCoef", 1), - AmmoType = Crate:GetNWString("AmmoType", "AP"), - Tracer = Tracer and ParticleEmitter(Origin) or nil, - TracerColour = Tracer and Crate:GetColor() or nil, - Accel = Crate:GetNWVector("Accel", Vector(0, 0, -600)), - LastThink = CurTime(), - Effect = self, + Caliber = Crate:GetNW2Float("Caliber", 10), + RoundMass = Crate:GetNW2Float("ProjMass", 10), + FillerMass = Crate:GetNW2Float("FillerMass"), + WPMass = Crate:GetNW2Float("WPMass"), + DragCoef = Crate:GetNW2Float("DragCoef", 1), + AmmoType = Crate:GetNW2String("AmmoType", "AP"), + Tracer = Tracer and ParticleEmitter(Origin) or nil, + Color = Tracer and Crate:GetColor() or nil, + Accel = Crate:GetNW2Vector("Accel", Vector(0, 0, -600)), + LastThink = ACF.CurTime, + Effect = self, } --Add all that data to the bullet table, overwriting if needed @@ -77,6 +83,8 @@ function EFFECT:Init(Data) self:SetAngles(Flight:Angle()) self:SetModelScale(BulletData.Caliber * 0.1, 0) + self.DrawEffect = CanDraw + local CustomEffect = hook.Run("ACF_BulletEffect", BulletData.AmmoType) if CustomEffect then @@ -88,7 +96,7 @@ end function EFFECT:Think() local Bullet = Bullets[self.Index] - if Bullet and not self.Kill and self.CreateTime > CurTime() - 30 then return true end + if Bullet and not self.Kill and self.CreateTime > ACF.CurTime - 30 then return true end if Bullet then if IsValid(Bullet.Tracer) then @@ -116,6 +124,8 @@ function EFFECT:ApplyMovement(Bullet) self:SetAngles(Bullet.SimFlight:Angle()) end + if not self.DrawEffect then return end + if Bullet.Tracer and IsValid(Bullet.Tracer) then local DeltaPos = Position - Bullet.SimPosLast local Length = math.max(DeltaPos:Length() * 2, 1) @@ -123,7 +133,7 @@ function EFFECT:ApplyMovement(Bullet) local Light = Bullet.Tracer:Add("sprites/acf_tracer.vmt", Position) if Light then - local Color = Bullet.TracerColour + local Color = Bullet.Color Light:SetAngles(Bullet.SimFlight:Angle()) Light:SetVelocity(Bullet.SimFlight:GetNormalized()) @@ -158,5 +168,7 @@ function EFFECT:ApplyMovement(Bullet) end function EFFECT:Render() + if not self.DrawEffect then return end + self:DrawModel() end \ No newline at end of file diff --git a/lua/effects/acf_explosion.lua b/lua/effects/acf_explosion.lua index 948db5397..22b4ce965 100644 --- a/lua/effects/acf_explosion.lua +++ b/lua/effects/acf_explosion.lua @@ -119,8 +119,10 @@ function EFFECT:Core(Direction) end end - sound.Play("ambient/explosions/explode_5.wav", self.Origin, math.Clamp(Radius * 10, 75, 165), math.Clamp(300 - Radius * 12, 15, 255), ACF.SoundVolume) - sound.Play("ambient/explosions/explode_4.wav", self.Origin, math.Clamp(Radius * 10, 75, 165), math.Clamp(300 - Radius * 25, 15, 255), ACF.SoundVolume) + local Level = math.Clamp(Radius * 10, 75, 165) + + sound.Play("ambient/explosions/explode_5.wav", self.Origin, Level, math.Clamp(300 - Radius * 12, 15, 255), ACF.Volume) + sound.Play("ambient/explosions/explode_4.wav", self.Origin, Level, math.Clamp(300 - Radius * 25, 15, 255), ACF.Volume) end function EFFECT:GroundImpact() diff --git a/lua/effects/acf_muzzle_flash.lua b/lua/effects/acf_muzzle_flash.lua index 3b6530e73..ec5318ee9 100644 --- a/lua/effects/acf_muzzle_flash.lua +++ b/lua/effects/acf_muzzle_flash.lua @@ -1,3 +1,5 @@ +local Weapons = ACF.Classes.Weapons + function EFFECT:Init(Data) local Gun = Data:GetEntity() @@ -7,16 +9,16 @@ function EFFECT:Init(Data) local ReloadTime = Data:GetMagnitude() local Sound = Gun:GetNWString("Sound") local Class = Gun:GetNWString("Class") - local ClassData = ACF.Classes.GunClass[Class] + local ClassData = Weapons[Class] local Attachment = "muzzle" - local LongBarrel = ClassData.longbarrel + local LongBarrel = ClassData.LongBarrel - if LongBarrel and Gun:GetBodygroup(LongBarrel.index) == LongBarrel.submodel then - Attachment = LongBarrel.newpos + if LongBarrel and Gun:GetBodygroup(LongBarrel.Index) == LongBarrel.Submodel then + Attachment = LongBarrel.NewPos end if not ACF.IsValidSound(Sound) then - Sound = ClassData.sound + Sound = ClassData.Sound end if Propellant > 0 then @@ -25,18 +27,19 @@ function EFFECT:Init(Data) if Sound ~= "" then local SoundPressure = (Propellant * 1000) ^ 0.5 - sound.Play(Sound, GunPos, math.Clamp(SoundPressure, 75, 127), 100, ACF.SoundVolume) --wiki documents level tops out at 180, but seems to fall off past 127 + -- NOTE: Wiki documents level tops out at 180, but seems to fall off past 127 + sound.Play(Sound, GunPos, math.Clamp(SoundPressure, 75, 127), 100, ACF.Volume) if not (Class == "MG" or Class == "RAC") then - sound.Play(Sound, GunPos, math.Clamp(SoundPressure, 75, 127), 100, ACF.SoundVolume) + sound.Play(Sound, GunPos, math.Clamp(SoundPressure, 75, 127), 100, ACF.Volume) if SoundPressure > 127 then - sound.Play(Sound, GunPos, math.Clamp(SoundPressure - 127, 1, 127), 100, ACF.SoundVolume) + sound.Play(Sound, GunPos, math.Clamp(SoundPressure - 127, 1, 127), 100, ACF.Volume) end end end - local Effect = ClassData.muzzleflash + local Effect = ClassData.MuzzleFlash local AttachID = Gun:LookupAttachment(Attachment) if AttachID > 0 then diff --git a/lua/effects/acf_penetration.lua b/lua/effects/acf_penetration.lua index 7636b415c..640524ed2 100644 --- a/lua/effects/acf_penetration.lua +++ b/lua/effects/acf_penetration.lua @@ -1,8 +1,9 @@ -local TraceData = { start = true, endpos = true } -local TraceLine = util.TraceLine +local TraceData = { start = true, endpos = true } +local TraceLine = util.TraceLine local ValidDecal = ACF.IsValidAmmoDecal -local GetDecal = ACF.GetPenetrationDecal -local GetScale = ACF.GetDecalScale +local GetDecal = ACF.GetPenetrationDecal +local GetScale = ACF.GetDecalScale +local Sound = "acf_base/fx/penetration%s.mp3" function EFFECT:Init(Data) self.Caliber = Data:GetRadius() @@ -37,7 +38,10 @@ function EFFECT:Init(Data) util.DecalEx(GetDecal(Type), Trace.Entity, Trace.HitPos, self.Normal, Color(255, 255, 255), Scale, Scale) end - sound.Play("acf_base/fx/penetration" .. math.random(1, 6) .. ".mp3", Trace.HitPos, math.Clamp(self.Mass * 200, 65, 500), math.Clamp(self.Velocity * 0.01, 25, 255), ACF.SoundVolume) + local Level = math.Clamp(self.Mass * 200, 65, 500) + local Pitch = math.Clamp(self.Velocity * 0.01, 25, 255) + + sound.Play(Sound:format(math.random(1, 6)), Trace.HitPos, Level, Pitch, ACF.Volume) end function EFFECT:Metal() diff --git a/lua/effects/acf_ricochet.lua b/lua/effects/acf_ricochet.lua index 8bc2015d3..86a9e730f 100644 --- a/lua/effects/acf_ricochet.lua +++ b/lua/effects/acf_ricochet.lua @@ -1,8 +1,9 @@ -local TraceData = { start = true, endpos = true } -local TraceLine = util.TraceLine +local TraceData = { start = true, endpos = true } +local TraceLine = util.TraceLine local ValidDecal = ACF.IsValidAmmoDecal -local GetDecal = ACF.GetRicochetDecal -local GetScale = ACF.GetDecalScale +local GetDecal = ACF.GetRicochetDecal +local GetScale = ACF.GetDecalScale +local Sound = "acf_base/fx/ricochet%s.mp3" function EFFECT:Init(Data) local Caliber = Data:GetRadius() @@ -24,7 +25,10 @@ function EFFECT:Init(Data) util.DecalEx(GetDecal(DecalType), Trace.Entity, Trace.HitPos, Trace.HitNormal, Color(255, 255, 255), Scale, Scale) end - sound.Play("acf_base/fx/penetration" .. math.random(1, 4) .. ".mp3", Origin, math.Clamp(Mass * 200, 65, 500), math.Clamp(Velocity * 0.01, 25, 255), ACF.SoundVolume) + local Level = math.Clamp(Mass * 200, 65, 500) + local Pitch = math.Clamp(Velocity * 0.01, 25, 255) + + sound.Play(Sound:format(math.random(1, 4)), Origin, Level, Pitch, ACF.Volume) end function EFFECT:Think() diff --git a/lua/entities/acf_ammo/cl_init.lua b/lua/entities/acf_ammo/cl_init.lua index 3ec59c4ad..02e40dfd6 100644 --- a/lua/entities/acf_ammo/cl_init.lua +++ b/lua/entities/acf_ammo/cl_init.lua @@ -1,12 +1,16 @@ +DEFINE_BASECLASS("acf_base_scalable") -- Required to get the local BaseClass + include("shared.lua") -local RoundsDisplayCVar = GetConVar("ACF_MaxRoundsDisplay") -local HideInfo = ACF.HideInfoBubble -local Distance = ACF.RefillDistance +language.Add("Cleanup_acf_ammo", "ACF Ammo Crates") +language.Add("Cleaned_acf_ammo", "Cleaned up all ACF Ammo Crates") +language.Add("SBoxLimit__acf_ammo", "You've reached the ACF Ammo Crates limit!") + +local MaxRounds = GetConVar("acf_maxroundsdisplay") local Refills = {} local Queued = {} -local function UpdateClAmmo(Entity) +local function UpdateAmmoCount(Entity, Ammo) if not IsValid(Entity) then return end if not Entity.HasData then if Entity.HasData == nil then @@ -16,13 +20,11 @@ local function UpdateClAmmo(Entity) return end - local MaxDisplayRounds = RoundsDisplayCVar:GetInt() - - Entity.Ammo = math.Clamp(Entity:GetNWInt("Ammo", 0), 0, Entity.Capacity) - - local FinalAmmo = Entity.HasBoxedAmmo and math.floor(Entity.Ammo / Entity.MagSize) or Entity.Ammo + local MaxDisplayRounds = MaxRounds:GetInt() - Entity.BulkDisplay = FinalAmmo > MaxDisplayRounds + Entity.Ammo = Ammo or Entity:GetNWInt("Ammo", 0) + Entity.FinalAmmo = Entity.HasBoxedAmmo and math.floor(Entity.Ammo / Entity.MagSize) or Entity.Ammo + Entity.BulkDisplay = Entity.FinalAmmo > MaxDisplayRounds end net.Receive("ACF_RequestAmmoData", function() @@ -40,28 +42,27 @@ net.Receive("ACF_RequestAmmoData", function() Entity.LocalAng = Data.LocalAng Entity.FitPerAxis = Data.FitPerAxis Entity.Spacing = Data.Spacing - Entity.MagSize = Data.MGS - Entity.HasBoxedAmmo = Data.MGS > 0 + Entity.MagSize = Data.MagSize + Entity.HasBoxedAmmo = Data.MagSize > 0 end if Queued[Entity] then Queued[Entity] = nil end - UpdateClAmmo(Entity) + UpdateAmmoCount(Entity) end) function ENT:Initialize() - self:SetNWVarProxy("Ammo", function() - UpdateClAmmo(self) + self:SetNWVarProxy("Ammo", function(_, _, _, Ammo) + UpdateAmmoCount(self, Ammo) end) - cvars.AddChangeCallback("ACF_MaxRoundsDisplay", function() - UpdateClAmmo(self) - end) + cvars.AddChangeCallback("acf_maxroundsdisplay", function() + UpdateAmmoCount(self) + end, "Ammo Crate " .. self:EntIndex()) - self.DrawAmmoHookIndex = "draw_ammo_" .. self:EntIndex() - self.BaseClass.Initialize(self) + BaseClass.Initialize(self) end function ENT:RequestAmmoData() @@ -74,12 +75,12 @@ function ENT:RequestAmmoData() net.SendToServer() end -function ENT:OnResized() +function ENT:OnResized(Size) self.HitBoxes = { Main = { Pos = self:OBBCenter(), - Scale = self:GetSize(), - Angle = Angle(0, 0, 0), + Scale = Size, + Angle = Angle(), Sensitive = false } } @@ -93,25 +94,17 @@ function ENT:OnFullUpdate() net.SendToServer() end -function ENT:Draw() - self:DoNormalDraw(false, HideInfo()) - - Wire_Render(self) - - if self.GetBeamLength and (not self.GetShowBeam or self:GetShowBeam()) then - -- Every SENT that has GetBeamLength should draw a tracer. Some of them have the GetShowBeam boolean - Wire_DrawTracerBeam(self, 1, self.GetBeamHighlight and self:GetBeamHighlight() or false) - end -end - function ENT:OnRemove() Refills[self] = nil - hook.Remove("PostDrawOpaqueRenderables",self.DrawAmmoHookIndex) + cvars.RemoveChangeCallback("acf_maxroundsdisplay", "Ammo Crate " .. self:EntIndex()) end -- TODO: Resupply effect library, should apply for both ammo and fuel do -- Resupply effect + local Yellow = Color(255, 255, 0, 10) + local Distance = ACF.RefillDistance + net.Receive("ACF_RefillEffect", function() local Refill = net.ReadEntity() @@ -132,8 +125,76 @@ do -- Resupply effect render.SetColorMaterial() for Refill in pairs(Refills) do - render.DrawSphere(Refill:GetPos(), Distance, 50, 50, Color(255, 255, 0, 10)) - render.DrawSphere(Refill:GetPos(), -Distance, 50, 50, Color(255, 255, 0, 10)) + local Pos = Refill:GetPos() + + render.DrawSphere(Pos, Distance, 50, 50, Yellow) + render.DrawSphere(Pos, -Distance, 50, 50, Yellow) + end + end) +end + +do -- Ammo overlay + -- Ammo overlay colors + local Blue = Color(0, 127, 255, 65) + local Orange = Color(255, 127, 0, 65) + local Green = Color(0, 255, 0, 65) + local Red = Color(255, 0, 0, 65) + + local function GetPosition(X, Y, Z, RoundSize, Spacing, RoundAngle, Direction) + local SizeX = (X - 1) * (RoundSize.x + Spacing) * RoundAngle:Forward() * Direction + local SizeY = (Y - 1) * (RoundSize.y + Spacing) * RoundAngle:Right() * Direction + local SizeZ = (Z - 1) * (RoundSize.z + Spacing) * RoundAngle:Up() * Direction + + return SizeX + SizeY + SizeZ + end + + local function DrawRounds(Entity, Center, Spacing, Fits, RoundSize, RoundAngle, Total) + local Count = 0 + + local StartPos = GetPosition(Fits.x, Fits.y, Fits.z, RoundSize, Spacing, RoundAngle, 1) * 0.5 + + for X = 1, Fits.x do + for Y = 1, Fits.y do + for Z = 1, Fits.z do + local LocalPos = GetPosition(X, Y, Z, RoundSize, Spacing, RoundAngle, -1) + local C = Entity.IsRound and Blue or Entity.HasBoxedAmmo and Green or Orange + + render.DrawWireframeBox(Center + StartPos + LocalPos, RoundAngle, -RoundSize * 0.5, RoundSize * 0.5, C) + + Count = Count + 1 + + if Count == Total then return end + end + end + end + end + + hook.Add("ACF_DrawBoxes", "ACF Draw Ammo", function(Entity) + if not Entity.IsScalable then return end + if not Entity.HasData then + if Entity.HasData == nil and Entity.RequestAmmoData then + Entity:RequestAmmoData() + end + + return + end + if Entity.FinalAmmo <= 0 then return end + + local RoundAngle = Entity:LocalToWorldAngles(Entity.LocalAng) + local Center = Entity:LocalToWorld(Entity:OBBCenter()) + local RoundSize = Entity.RoundSize + local Spacing = Entity.Spacing + local Fits = Entity.FitPerAxis + + if not Entity.BulkDisplay then + DrawRounds(Entity, Center, Spacing, Fits, RoundSize, RoundAngle, Entity.FinalAmmo) + else -- Basic bitch box that scales according to ammo, only for bulk display + local AmmoPerc = Entity.Ammo / Entity.Capacity + local SizeAdd = Vector(Spacing, Spacing, Spacing) * Fits + local BulkSize = ((Fits * RoundSize * Vector(1, AmmoPerc, 1)) + SizeAdd) * 0.5 + local Offset = RoundAngle:Right() * (Fits.y * RoundSize.y) * 0.5 * (1 - AmmoPerc) + + render.DrawWireframeBox(Center + Offset, RoundAngle, -BulkSize, BulkSize, Red) end end) end diff --git a/lua/entities/acf_ammo/init.lua b/lua/entities/acf_ammo/init.lua index d2793b106..10ebc3b8f 100644 --- a/lua/entities/acf_ammo/init.lua +++ b/lua/entities/acf_ammo/init.lua @@ -3,531 +3,231 @@ AddCSLuaFile("shared.lua") include("shared.lua") -util.AddNetworkString("ACF_RefillEffect") -util.AddNetworkString("ACF_StopRefillEffect") -util.AddNetworkString("ACF_RequestAmmoData") - -- Local Vars ----------------------------------- -local CheckLegal = ACF_CheckLegal -local ClassLink = ACF.GetClassLink -local ClassUnlink = ACF.GetClassUnlink -local RefillDist = ACF.RefillDistance * ACF.RefillDistance + local ActiveCrates = ACF.AmmoCrates local TimerCreate = timer.Create local TimerExists = timer.Exists +local HookRun = hook.Run + +do -- Spawning and Updating -------------------- + local CheckLegal = ACF_CheckLegal + local Classes = ACF.Classes + local Crates = Classes.Crates + local AmmoTypes = Classes.AmmoTypes + local GetClassGroup = ACF.GetClassGroup + + local Updated = { + ["20mmHRAC"] = "20mmRAC", + ["30mmHRAC"] = "30mmRAC", + ["40mmCL"] = "40mmGL", + } + + local function VerifyData(Data) + if Data.RoundId then -- Updating old crates + Data.Weapon = Updated[Data.RoundId] or Data.RoundId + Data.AmmoType = Data.RoundType or "AP" + + if Data.Id and Crates[Data.Id] then -- Pre scalable crate remnants + local Crate = Crates[Data.Id] + + Data.Offset = Crate.Offset + Data.Size = Crate.Size + else + local X = Data.RoundData11 or 24 + local Y = Data.RoundData12 or 24 + local Z = Data.RoundData13 or 24 -local function CanRefillCrate(Refill, Crate, Distance) - if Refill == Crate then return false end - if not Refill:CanConsume() then return false end - if Crate.Ammo >= Crate.Capacity then return false end - if Crate.RoundType == "Refill" then return false end - if Crate.Disabled then return false end - if Crate.Damaged then return false end - - return Distance <= RefillDist -end - -local function RefillEffect(Entity) - net.Start("ACF_RefillEffect") - net.WriteEntity(Entity) - net.Broadcast() -end - -local function StopRefillEffect(Entity) - net.Start("ACF_StopRefillEffect") - net.WriteEntity(Entity) - net.Broadcast() -end - --- Whenever a player requests ammo data, we'll send it to them -net.Receive("ACF_RequestAmmoData", function(_, Player) - local Entity = net.ReadEntity() - - if IsValid(Entity) and Entity.CrateData then - net.Start("ACF_RequestAmmoData") - net.WriteEntity(Entity) - net.WriteString(Entity.CrateData) - net.Send(Player) - end -end) - -local function RefillCrates(Refill) - local Position = Refill:GetPos() - - for Crate in pairs(ActiveCrates) do - local Distance = Position:DistToSqr(Crate:GetPos()) - - if CanRefillCrate(Refill, Crate, Distance) then - local Supply = math.ceil((50000 / ((Crate.BulletData.ProjMass + Crate.BulletData.PropMass) * 1000)) / Distance ^ 0.5) - local Transfer = math.min(Supply, Refill.Ammo, Crate.Capacity - Crate.Ammo) - - if hook.Run("ACF_CanRefill", Refill, Crate, Transfer) == false then continue end - - if not next(Refill.SupplyingTo) then - RefillEffect(Refill) - end - - if not Refill.SupplyingTo[Crate] then - Refill.SupplyingTo[Crate] = true - - Crate:CallOnRemove("ACF Refill " .. Refill:EntIndex(), function() - Refill.SupplyingTo[Crate] = nil - end) - end - - Crate:Consume(-Transfer) - Refill:Consume(Transfer) - - Crate:EmitSound("items/ammo_pickup.wav", 70, 100, 0.5 * ACF.SoundVolume) - Refill:EmitSound("items/ammo_pickup.wav", 70, 100, 0.5 * ACF.SoundVolume) - - elseif Refill.SupplyingTo[Crate] then - Refill.SupplyingTo[Crate] = nil - - Crate:RemoveCallOnRemove("ACF Refill " .. Refill:EntIndex()) - - if not next(Refill.SupplyingTo) then - StopRefillEffect(Refill) + Data.Size = Vector(X, Y, Z) end end - end -end - --- Split this off from the original function, --- All this does is compare a distance against a table of distances with string indexes for the shortest fitting size --- It returns the string index of the dimension, or nil if it fails to fit -local function ShortestSize(Size,Spacing,DimTable,ExtraData,IsIrregular) - local ReturnDimension = nil - local X = 0 - local Y = 0 - local TestRoundCount = 0 - local BestCount = 0 - - --local FullSize = {x = Size.x + Spacing, y = Size.y + Spacing, z = Size.z + Spacing} -- size of the round with the padding - local ConvLength = Size.x - - for K,_ in pairs(DimTable) do - if K == "x" then - X = DimTable["y"] - Y = DimTable["z"] - elseif K == "y" then - X = DimTable["x"] - Y = DimTable["z"] - else -- z - X = DimTable["x"] - Y = DimTable["y"] - end - if not IsIrregular then - local ModifiedRoundLength = ConvLength + Spacing - local ModifiedRoundSize = Size.y + Spacing - if (math.floor(DimTable[K] / ConvLength) == 1) then ModifiedRoundLength = ConvLength end + do -- Clamping size + local Size = Data.Size - local RoundsX = math.floor(DimTable[K] / ModifiedRoundLength) - local RoundsY = math.floor(X / ModifiedRoundSize) - local RoundsZ = math.floor(Y / ModifiedRoundSize) + if not isvector(Size) then + Size = Vector(Data.CrateSizeX or 24, Data.CrateSizeY or 24, Data.CrateSizeZ or 24) - if ExtraData.MagSize or 0 > 0 then - TestRoundCount = RoundsX * RoundsY * RoundsZ * ExtraData.MagSize - else - TestRoundCount = RoundsX * RoundsY * RoundsZ + Data.Size = Size end - else - local ModifiedRoundLength = ConvLength + Spacing - local RoundWidth = Size.y + Spacing - local RoundHeight = Size.z + Spacing - -- Doesn't use round spacing for length wise if its just 1, because edge cases are fun - if (math.floor(DimTable[K] / ConvLength) == 1) then ModifiedRoundLength = ConvLength end - local RoundsX = math.floor(DimTable[K] / ModifiedRoundLength) - local RoundsY = math.floor(X / (RoundWidth + Spacing)) - local RoundsZ = math.floor(Y / (RoundHeight + Spacing)) - - TestRoundCount = RoundsX * RoundsY * RoundsZ + Size.x = math.Clamp(Size.x, 6, 96) + Size.y = math.Clamp(Size.y, 6, 96) + Size.z = math.Clamp(Size.z, 6, 96) end - if ReturnDimension == nil then - if TestRoundCount > BestCount then ReturnDimension = K BestCount = TestRoundCount end -- It fits, it sits - else - if TestRoundCount > BestCount then ReturnDimension = K BestCount = TestRoundCount end -- It fits, it sits in an even small spot - end - end - return ReturnDimension, BestCount -end - --- BoxSize is just OBBMaxs-OBBMins --- Removed caliber and round length inputs, uses GunData and BulletData now --- AddSpacing is just extra spacing (directly reduces storage, but can later make it harder to detonate) --- AddArmor is literally just extra armor on the ammo crate, but inside (also directly reduces storage) --- For missiles/bombs, they MUST have ActualLength and ActualWidth (of the model in cm, and in the round table) to use this, otherwise it will fall back to the original calculations --- Made by LiddulBOFH :) -local function CalcAmmo(BoxSize,GunData,BulletData,AddSpacing,AddArmor) - local RoundCaliber = GunData.caliber * ACF.AmmoCaseScale or 0 - local TotalRoundLength = (BulletData.PropLength or 0) + (BulletData.ProjLength or 0) + (BulletData.Tracer or 0) - local ExtraData = {} - - -- gives a nice number of rounds per refill box - if BulletData.Type == "Refill" then return math.ceil((BoxSize.x / 2) * (BoxSize.y / 2) * (BoxSize.z / 2)) end - - -- Filters for missiles, and sets up data - if (GunData.round.ActualWidth or nil) ~= nil then - RoundCaliber = GunData.round.ActualWidth - TotalRoundLength = GunData.round.ActualLength - ExtraData.isRacked = true - elseif GunData.ent == "acf_rack" then return -1 end -- Fallback to old capacity - -- Instantly invalidate garbage rounds - if RoundCaliber == 0 then return 0 end - if TotalRoundLength == 0 then return 0 end - - local Rounds = 0 - -- Converting everything to source units - local ConvCaliber = RoundCaliber * 0.3937 -- cm to inches - local ConvLength = TotalRoundLength * 0.3937 - local Spacing = math.max(math.abs(AddSpacing) + 0.125,0.125) - ExtraData.Spacing = Spacing - - local MagSize = 0 - local MagBoxSize = 0 - local Class = GunData.gunclass - - -- This block alters the stored round size, making it more like a container of the rounds - -- This cuts a little bit of ammo storage out - -- Anything that may potentially be belt-fed (RACs, ACs despite having the weirdass revolver design) is exempt - -- Anything with a sensible magazine is forced to use this - -- Autoloading cannons are exempt because of rounds being inserted into the stored drums - - -- a much needed enmasse comparing function - -- through some light digging, I couldn't find one, so I made one - local Force = switch({ - ["SA"] = true, - ["SL"] = true, - ["HMG"] = true, - ["GL"] = true, - ["MG"] = true, - ["default"] = false - }, - Class) - - local ForceSkip = switch({ - ["RAC"] = true, - ["AC"] = true, - ["AL"] = true, - ["default"] = false - }, - Class) - - if ((GunData.magsize or 0) > 0) and ((GunData.caliber <= 2) or (Force and (not (ExtraData.isRacked or false)))) and (not ForceSkip) then - MagSize = GunData.magsize - MagBoxSize = ConvCaliber * math.sqrt(MagSize) - -- Makes certain automatic ammo stored by boxes - ConvCaliber = MagBoxSize - ExtraData.MagSize = MagSize - ExtraData.isBoxed = true - end - - if AddArmor then - local ConvArmor = AddArmor * 0.039 * 2 -- Converting millimeters to inches then multiplying by two since the armor is on both sides - BoxSize = { - x = math.max(BoxSize.x-ConvArmor, 0), - y = math.max(BoxSize.y-ConvArmor, 0), - z = math.max(BoxSize.z-ConvArmor, 0) - } - end - - local D = {["x"] = BoxSize.x, ["y"] = BoxSize.y, ["z"] = BoxSize.z} - local ShortestFit = ShortestSize({x = ConvLength,y = ConvCaliber,z = ConvCaliber},Spacing,D,ExtraData,false) - - if ShortestFit ~= nil then -- From here we know the round can sorta fit in the box - local X = 0 - local Y = 0 - -- Creating the 'plane' to do the basic bitch math with - if ShortestFit == "x" then - X = D["y"] - Y = D["z"] - ExtraData.LocalAng = Angle(0,0,0) - elseif ShortestFit == "y" then - X = D["x"] - Y = D["z"] - ExtraData.LocalAng = Angle(0,90,0) - else -- z - X = D["x"] - Y = D["y"] - ExtraData.LocalAng = Angle(90,90,0) + if not isstring(Data.Destiny) then + Data.Destiny = ACF.FindWeaponrySource(Data.Weapon) or "Weapons" end - local ModifiedRoundLength = ConvLength + Spacing - local ModifiedRoundSize = ConvCaliber + Spacing - -- Doesn't use round spacing for length wise if its just 1, because edge cases are fun - if (math.floor(D[ShortestFit] / ConvLength) == 1) then ModifiedRoundLength = ConvLength end - -- That basic bitch math - ExtraData.RoundSize = Vector(ConvLength,ConvCaliber,ConvCaliber) - local RoundsX = math.floor(D[ShortestFit] / ModifiedRoundLength) - local RoundsY = math.floor(X / ModifiedRoundSize) - local RoundsZ = math.floor(Y / ModifiedRoundSize) - ExtraData.FitPerAxis = Vector(RoundsX,RoundsY,RoundsZ) - if MagSize > 0 then - Rounds = RoundsX * RoundsY * RoundsZ * MagSize - else - Rounds = RoundsX * RoundsY * RoundsZ - end - elseif ShortestFit == nil and ((ConvCaliber >= ((10 / 0.75) / 2.54)) or ((ExtraData.isRacked or false) == true)) and not (ExtraData.isBoxed or false) then - -- If ShortestFit is nil, that means the round isn't able to fit at all in the box - -- If its a racked munition that doesn't fit, it will go ahead and try to fit 2-pice - -- Otherwise, checks if the caliber is over 100mm before trying 2-piece ammunition - -- It will flatout not do anything if its boxed and not fitting - - -- Not exactly accurate, but cuts the round in two - ConvLength = ConvLength / 2 - -- Then makes a shape made of the now two pieces of ammunition - local RoundWidth = ConvCaliber * 2 -- two pieces wide - local RoundHeight = ConvCaliber -- one piece tall - - local ShortestFit1, ShortestFit2 - local Count1, Count2 - ShortestFit1,Count1 = ShortestSize({x = ConvLength,y = RoundWidth,z = RoundHeight},Spacing,D,ExtraData,true) - ShortestFit2,Count2 = ShortestSize({x = ConvLength,y = RoundHeight,z = RoundWidth},Spacing,D,ExtraData,true) - if Count1 > Count2 then ShortestFit = ShortestFit1 ShortestWidth = "x" else ShortestFit = ShortestFit2 ShortestWidth = "y" end - - -- Retrying the length fit - if ShortestFit ~= nil then - local X = 0 - local Y = 0 - -- Creating the 'plane' to do the basic bitch math with - if ShortestFit == "x" then - X = D["y"] - Y = D["z"] - ExtraData.LocalAng = Angle(0,0,0) - elseif ShortestFit == "y" then - X = D["x"] - Y = D["z"] - ExtraData.LocalAng = Angle(0,90,0) - else -- z - X = D["x"] - Y = D["y"] - ExtraData.LocalAng = Angle(90,90,0) - end + local Source = Classes[Data.Destiny] + local Class = GetClassGroup(Source, Data.Weapon) - -- Now we have to check which side will fit the new width of the round, in the shortest space possible - local D2 = {["x"] = X, ["y"] = Y} - - local FreeSpace = 0 - if ShortestWidth ~= nil then - if ShortestWidth == "x" then - FreeSpace = D2["y"] - ExtraData.LocalAng = ExtraData.LocalAng + Angle(0,0,0) - else -- y - FreeSpace = D2["x"] - ExtraData.LocalAng = ExtraData.LocalAng + Angle(0,0,90) - end + if not Class then + Class = GetClassGroup(Classes.Weapons, "50mmC") - local ModifiedRoundLength = ConvLength + Spacing - -- Doesn't use round spacing for length wise if its just 1, because edge cases are fun - if (math.floor(D[ShortestFit] / ConvLength) == 1) then ModifiedRoundLength = ConvLength end - ExtraData.RoundSize = Vector(ConvLength,RoundWidth,RoundHeight) - local RoundsX = math.floor(D[ShortestFit] / ModifiedRoundLength) - local RoundsY = math.floor(D2[ShortestWidth] / (RoundWidth + Spacing)) - local RoundsZ = math.floor(FreeSpace / (RoundHeight + Spacing)) - ExtraData.FitPerAxis = Vector(RoundsX,RoundsY,RoundsZ) - Rounds = RoundsX * RoundsY * RoundsZ - end + Data.Destiny = "Weapons" + Data.Weapon = "50mmC" end - -- If it still doesn't fit the box, then the box is just too small - - ExtraData.isTwoPiece = true - end - return Rounds,ExtraData -end + local Ammo = AmmoTypes[Data.AmmoType] -local function UpdateAmmoData(Entity, Data1, Data2, Data3, Data4, Data5, Data6, Data7, Data8, Data9, Data10, Data11, Data12, Data13, Data) - local GunData = ACF.Weapons.Guns[Data1] - local Percentage = Entity.Capacity and Entity.Ammo / math.max(Entity.Capacity, 1) or 1 + -- Making sure our ammo type exists and it's not blacklisted by the weapon + if not Ammo or Ammo.Blacklist[Class.ID] then + Data.AmmoType = Class.DefaultAmmo or "AP" - do -- Sanity checks - if not GunData then - Entity:Remove() - return + Ammo = AmmoTypes[Data.AmmoType] end - if next(Entity.Weapons) then - local Unloaded - - for Weapon in pairs(Entity.Weapons) do - if Weapon.CurrentCrate == Entity then - Unloaded = true + do -- External verifications + Ammo:VerifyData(Data, Class) -- All ammo types should come with this function - Weapon:Unload() - end + if Class.VerifyData then + Class.VerifyData(Data, Class, Ammo) end - if Unloaded then - ACF_SendNotify(Entity.Owner, false, "Crate updated while weapons were loaded with it's ammo. Weapons unloaded.") - end + HookRun("ACF_VerifyData", "acf_ammo", Data, Class, Ammo) end end - do -- Mass entity mod removal - local EntMods = Data and Data.EntityMods - - if EntMods and EntMods.mass then - EntMods.mass = nil - end - end + local function UpdateCrate(Entity, Data, Class, Weapon, Ammo) + local Name, ShortName, WireName = Ammo:GetCrateName() - local RoundData = ACF.RoundTypes[Data2] + Entity.Name = Name or Weapon.Name .. " " .. Ammo.Name + Entity.ShortName = ShortName or Weapon.ID .. " " .. Ammo.ID + Entity.EntType = "Ammo Crate" + Entity.ClassData = Class + Entity.WeaponData = Weapon + Entity.Caliber = Weapon.Caliber + Entity.Class = Class.ID - do -- Backwards compatibility - local AmmoData = Data and Data.Id and ACF.Weapons.Ammo[Data.Id] + Entity:SetNWString("WireName", "ACF " .. (WireName or Weapon.Name .. " Ammo Crate")) + Entity:SetSize(Data.Size) - if AmmoData and not (Data11 or Data12 or Data13) then - local NewPos = Entity:LocalToWorld(AmmoData.Offset) + do -- Updating round data + local OldAmmo = Entity.RoundData - Entity:SetPos(NewPos) + if OldAmmo then + if OldAmmo.OnLast then + OldAmmo:OnLast(Entity) + end - -- Updating the dupe position - -- TODO: Update constraints on the entity if it gets moved - if Data.BuildDupeInfo then - Data.BuildDupeInfo.PosReset = NewPos + HookRun("ACF_OnAmmoLast", OldAmmo, Entity) end - Data11 = AmmoData.Size[1] - Data12 = AmmoData.Size[2] - Data13 = AmmoData.Size[3] - end - end + Entity.RoundData = Ammo + Entity.BulletData = Ammo:ServerConvert(Data) + Entity.BulletData.Crate = Entity:EntIndex() - do -- Update RoundData - --Data 1 to 4 are should always be Round ID, Round Type, Propellant lenght, Projectile lenght - Entity.RoundId = Data1 --Weapon this round loads into, ie 140mmC, 105mmH ... - Entity.RoundType = RoundData and Data2 or "AP" --Type of round, IE AP, HE, HEAT ... - Entity.RoundPropellant = Data3 --Length of propellant - Entity.RoundProjectile = Data4 --Length of the projectile - Entity.RoundData5 = Data5 or 0 - Entity.RoundData6 = Data6 or 0 - Entity.RoundData7 = Data7 or 0 - Entity.RoundData8 = Data8 or 0 - Entity.RoundData9 = Data9 or 0 - Entity.RoundData10 = Data10 or 0 - Entity.RoundData11 = Data11 or Entity.RoundData11 or 24 -- Scale X - Entity.RoundData12 = Data12 or Entity.RoundData12 or 24 -- Scale Y - Entity.RoundData13 = Data13 or Entity.RoundData13 or 24 -- Scale Z - - Entity.Name = (Entity.RoundType ~= "Refill" and (Data1 .. " ") or "") .. Entity.RoundType - Entity.ShortName = Data1 - Entity.EntType = Entity.RoundType - Entity.RoundData = RoundData or ACF.RoundTypes.AP - - local PlayerData = { - Id = Entity.RoundId, - Type = Entity.RoundType, - PropLength = Entity.RoundPropellant, - ProjLength = Entity.RoundProjectile, - Data5 = Entity.RoundData5, - Data6 = Entity.RoundData6, - Data7 = Entity.RoundData7, - Data8 = Entity.RoundData8, - Data9 = Entity.RoundData9, - Data10 = Entity.RoundData10 - } + if Ammo.OnFirst then + Ammo:OnFirst(Entity) + end - Entity.BulletData = Entity.RoundData.convert(Entity, PlayerData) - Entity.BulletData.Crate = Entity:EntIndex() + HookRun("ACF_OnAmmoFirst", Ammo, Entity, Data, Class, Weapon) - if Entity.RoundType == "Refill" then - Entity.SupplyingTo = Entity.SupplyingTo or {} + Ammo:Network(Entity, Entity.BulletData) + end - TimerCreate("ACF Refill " .. Entity:EntIndex(), 1, 0, function() - if not IsValid(Entity) then return end + -- Storing all the relevant information on the entity for duping + for _, V in ipairs(Entity.DataStore) do + Entity[V] = Data[V] + end - RefillCrates(Entity) - end) - else - if Entity.SupplyingTo then - StopRefillEffect(Entity) + do -- Ammo count calculation + local Size = Entity:GetSize() + local Spacing = Weapon.Caliber * 0.0039 + local Rounds, ExtraData = ACF.GetAmmoCrateCapacity(Size, Weapon, Entity.BulletData, Spacing, ACF.AmmoArmor) + local Percentage = Entity.Capacity and Entity.Ammo / math.max(Entity.Capacity, 1) or 1 - Entity.SupplyingTo = nil - end + Entity.Capacity = Rounds + Entity.AmmoMassMax = math.floor(Entity.BulletData.CartMass * Entity.Capacity) + Entity.Ammo = math.floor(Entity.Capacity * Percentage) - timer.Remove("ACF Refill " .. Entity:EntIndex()) - end - end + WireLib.TriggerOutput(Entity, "Ammo", Entity.Ammo) - local Size - do -- Resizing - local X = math.Clamp(Entity.RoundData11, 6, 96) - local Y = math.Clamp(Entity.RoundData12, 6, 96) - local Z = math.Clamp(Entity.RoundData13, 6, 96) + Entity:SetNWInt("Ammo", Entity.Ammo) - Size = Vector(X, Y, Z) + if ExtraData then + local MagSize = Weapon.MagSize - if Size ~= Entity:GetSize() then - Entity:SetSize(Size) - end - end + -- for future use in reloading + --Entity.IsBoxed = ExtraData.IsBoxed -- Ammunition is boxed + --Entity.IsTwoPiece = ExtraData.IsTwoPiece -- Ammunition is broken down to two pieces - -- CalcAmmo function is just above - local Rounds, ExtraData = CalcAmmo(Size, GunData, Entity.BulletData, GunData.caliber * 0.039, ACF.AmmoArmor) + ExtraData.MagSize = ExtraData.IsBoxed and MagSize or 0 + ExtraData.IsRound = not (ExtraData.IsBoxed or ExtraData.IsTwoPiece or ExtraData.IsRacked) + ExtraData.Capacity = Entity.Capacity + ExtraData.Enabled = true + else + ExtraData = { Enabled = false } + end - if Rounds ~= -1 then - Entity.Capacity = Rounds - else - --print("Fallback (Rackable munition missing ActualLength/ActualWidth)") + Entity.CrateData = util.TableToJSON(ExtraData) - local Efficiency = 0.1576 * ACF.AmmoMod - local Volume = math.floor(Entity:GetPhysicsObject():GetVolume()) * Efficiency - local CapMul = (Volume > 40250) and ((math.log(Volume * 0.00066) / math.log(2) - 4) * 0.15 + 1) or 1 + net.Start("ACF_RequestAmmoData") + net.WriteEntity(Entity) + net.WriteString(Entity.CrateData) + net.Broadcast() + end - Entity.Capacity = math.floor(CapMul * Volume * 16.38 / Entity.BulletData.RoundVolume) - end + -- Linked weapon unloading + if next(Entity.Weapons) then + local Unloaded - Entity.AmmoMassMax = math.floor((Entity.BulletData.ProjMass + Entity.BulletData.PropMass) * Entity.Capacity) - Entity.Caliber = GunData.caliber - Entity.Ammo = math.floor(Entity.Capacity * Percentage) + for K in pairs(Entity.Weapons) do + if K.CurrentCrate == Entity then + Unloaded = true - Entity:SetNWInt("Ammo", Entity.Ammo) + K:Unload() + end + end - WireLib.TriggerOutput(Entity, "Ammo", Entity.Ammo) + if Unloaded then + ACF.SendNotify(Entity.Owner, false, "Crate updated while weapons were loaded with it's ammo. Weapons unloaded.") + end + end - if ExtraData then - local MGS = 0 - if ((GunData.magsize or 0) > 0) and (ExtraData.isBoxed or false) then MGS = (GunData.magsize or 0) end - ExtraData.MGS = MGS - ExtraData.IsRound = not (ExtraData.isBoxed or ExtraData.isTwoPiece or ExtraData.isRacked) + ACF.Activate(Entity, true) -- Makes Crate.ACF table - -- for future use in reloading - --if (ExtraData.isBoxed or false) then Entity.isBoxed = true end -- Ammunition is boxed - --if (ExtraData.isTwoPiece or false) then Entity.isTwoPiece = true end -- Ammunition is broken down to two pieces + Entity.ACF.Model = Entity:GetModel() - ExtraData.Capacity = Entity.Capacity - ExtraData.Enabled = true - else - ExtraData = { Enabled = false } + Entity:UpdateMass(true) end - Entity.CrateData = util.TableToJSON(ExtraData) + util.AddNetworkString("ACF_RequestAmmoData") - -- TODO: Figure out a way to not rely on this delay. - timer.Simple(0.1, function() - net.Start("ACF_RequestAmmoData") - net.WriteEntity(Entity) - net.WriteString(Entity.CrateData) - net.Broadcast() + -- Whenever a player requests ammo data, we'll send it to them + net.Receive("ACF_RequestAmmoData", function(_, Player) + local Entity = net.ReadEntity() + + if IsValid(Entity) and Entity.CrateData then + net.Start("ACF_RequestAmmoData") + net.WriteEntity(Entity) + net.WriteString(Entity.CrateData) + net.Send(Player) + end end) - Entity:SetNWString("WireName", "ACF " .. (Entity.RoundType == "Refill" and "Ammo Refill Crate" or GunData.name .. " Ammo")) + hook.Add("ACF_CanUpdateEntity", "ACF Crate Size Update", function(Entity, Data) + if not Entity.IsAmmoCrate then return end + if Data.Size then return end -- The menu won't send it like this - Entity.RoundData.network(Entity, Entity.BulletData) + Data.Size = Entity:GetSize() + Data.CrateSizeX = nil + Data.CrateSizeY = nil + Data.CrateSizeZ = nil + end) - ACF_Activate(Entity, true) -- Makes Crate.ACF table + ------------------------------------------------------------------------------- - Entity.ACF.Model = Entity:GetModel() + function MakeACF_Ammo(Player, Pos, Ang, Data) + VerifyData(Data) - Entity:UpdateMass(true) - Entity:UpdateOverlay(true) -end + local Source = Classes[Data.Destiny] + local Class = GetClassGroup(Source, Data.Weapon) + local Weapon = Class.Lookup[Data.Weapon] + local Ammo = AmmoTypes[Data.AmmoType] -do -- Spawn Func -------------------------------- - function MakeACF_Ammo(Player, Pos, Ang, ...) if not Player:CheckLimit("_acf_ammo") then return end local Crate = ents.Create("acf_ammo") @@ -535,421 +235,419 @@ do -- Spawn Func -------------------------------- if not IsValid(Crate) then return end Player:AddCount("_acf_ammo", Crate) - Player:AddCleanup("acfmenu", Crate) + Player:AddCleanup("acf_ammo", Crate) - Crate:SetPos(Pos) - Crate:SetAngles(Ang) - Crate:SetPlayer(Player) Crate:SetModel("models/holograms/rcube_thin.mdl") Crate:SetMaterial("phoenix_storms/Future_vents") + Crate:SetPlayer(Player) + Crate:SetAngles(Ang) + Crate:SetPos(Pos) Crate:Spawn() - Crate.IsExplosive = true - Crate.Owner = Player - Crate.Weapons = {} - Crate.Inputs = WireLib.CreateInputs(Crate, { "Load" }) - Crate.Outputs = WireLib.CreateOutputs(Crate, { "Entity [ENTITY]", "Ammo", "Loading" }) - Crate.CanUpdate = true - - UpdateAmmoData(Crate, ...) + Crate.Owner = Player -- MUST be stored on ent for PP + Crate.IsExplosive = true + Crate.Weapons = {} + Crate.Inputs = WireLib.CreateInputs(Crate, { "Load" }) + Crate.Outputs = WireLib.CreateOutputs(Crate, { "Entity [ENTITY]", "Ammo", "Loading" }) + Crate.DataStore = ACF.GetEntityArguments("acf_ammo") WireLib.TriggerOutput(Crate, "Entity", Crate) + UpdateCrate(Crate, Data, Class, Weapon, Ammo) + + if Class.OnSpawn then + Class.OnSpawn(Crate, Data, Class, Weapon, Ammo) + end + + HookRun("ACF_OnEntitySpawn", "acf_ammo", Crate, Data, Class, Weapon, Ammo) + + Crate:UpdateOverlay(true) + + -- Backwards compatibility with old crates + -- TODO: Update constraints on the entity if it gets moved + if Data.Offset then + local Position = Crate:LocalToWorld(Data.Offset) + + Crate:SetPos(Position) + + -- Updating the dupe position + if Data.BuildDupeInfo then + Data.BuildDupeInfo.PosReset = Position + end + end + + do -- Mass entity mod removal + local EntMods = Data.EntityMods + + if EntMods and EntMods.mass then + EntMods.mass = nil + end + end + -- Crates should be ready to load by default Crate:TriggerInput("Load", 1) - ACF.AmmoCrates[Crate] = true + ActiveCrates[Crate] = true CheckLegal(Crate) return Crate end - list.Set("ACFCvars", "acf_ammo", { "data1", "data2", "data3", "data4", "data5", "data6", "data7", "data8", "data9", "data10", "data11", "data12", "data13" }) - duplicator.RegisterEntityClass("acf_ammo", MakeACF_Ammo, "Pos", "Angle", "RoundId", "RoundType", "RoundPropellant", "RoundProjectile", "RoundData5", "RoundData6", "RoundData7", "RoundData8", "RoundData9", "RoundData10", "RoundData11", "RoundData12", "RoundData13", "Data") + ACF.RegisterEntityClass("acf_ammo", MakeACF_Ammo, "Weapon", "AmmoType", "Size") ACF.RegisterLinkSource("acf_ammo", "Weapons") -end -do -- Metamethods ------------------------------- - do -- Inputs/Outputs/Linking ---------------- - WireLib.AddInputAlias("Active", "Load") - WireLib.AddOutputAlias("Munitions", "Ammo") + ------------------- Updating --------------------- - function ENT:TriggerInput(Name, Value) - if self.Disabled then return end -- Ignore input if disabled + function ENT:Update(Data) + VerifyData(Data) - if Name == "Load" then - self.Load = tobool(Value) + local Source = Classes[Data.Destiny] + local Class = GetClassGroup(Source, Data.Weapon) + local OldClass = self.ClassData + local Weapon = Class.Lookup[Data.Weapon] + local OldWeapon = self.Weapon + local Ammo = AmmoTypes[Data.AmmoType] + local Blacklist = Ammo.Blacklist + local Extra = "" - WireLib.TriggerOutput(self, "Loading", self:CanConsume() and 1 or 0) - end - - self:UpdateOverlay() + if OldClass.OnLast then + OldClass.OnLast(self, OldClass) end - function ENT:Link(Target) - if not IsValid(Target) then return false, "Attempted to link an invalid entity." end - if self == Target then return false, "Can't link a crate to itself." end - if table.HasValue(ACF.AmmoBlacklist[self.BulletData.Type], Target.Class) then return false, "The ammo type in this crate cannot be used for this weapon." end - - local Function = ClassLink(self:GetClass(), Target:GetClass()) - - if Function then - return Function(self, Target) - end - - return false, "Crates can't be linked to '" .. Target:GetClass() .. "'." - end + HookRun("ACF_OnEntityLast", "acf_ammo", self, OldClass) - function ENT:Unlink(Target) - if not IsValid(Target) then return false, "Attempted to unlink an invalid entity." end - if self == Target then return false, "Can't unlink a crate from itself." end + ACF.SaveEntity(self) - local Function = ClassUnlink(self:GetClass(), Target:GetClass()) + UpdateCrate(self, Data, Class, Weapon, Ammo) - if Function then - return Function(self, Target) - end + ACF.RestoreEntity(self) - return false, "Crates can't be unlinked from '" .. Target:GetClass() .. "'." + if Class.OnUpdate then + Class.OnUpdate(self, Data, Class, Weapon, Ammo) end - end - do -- Overlay ------------------------------- - local Text = "%s\n\nSize: %sx%sx%s\n\nContents: %s ( %s / %s )%s%s" - local BulletText = "\nCartridge Mass: %s kg\nProjectile Mass: %s kg\nPropellant Mass: %s kg" + HookRun("ACF_OnEntityUpdate", "acf_ammo", self, Data, Class, Weapon, Ammo) - local function Overlay(Ent) - if Ent.Disabled then - Ent:SetOverlayText("Disabled: " .. Ent.DisableReason .. "\n" .. Ent.DisableDescription) - else - local Tracer = Ent.BulletData.Tracer ~= 0 and "-T" or "" - local X, Y, Z = Ent:GetSize():Unpack() - local AmmoInfo = Ent.RoundData.cratetxt and Ent.RoundData.cratetxt(Ent.BulletData) - local BulletInfo = "" - local Status - - if next(Ent.Weapons) or Ent.BulletData.Type == "Refill" then - Status = Ent:CanConsume() and "Providing Ammo" or (Ent.Ammo ~= 0 and "Idle" or "Empty") - else - Status = "Not linked to a weapon!" - end + if Data.Weapon ~= OldWeapon or self.Unlinkable then + for Entity in pairs(self.Weapons) do + self:Unlink(Entity) + end - X = math.Round(X, 2) - Y = math.Round(Y, 2) - Z = math.Round(Z, 2) + Extra = " All weapons have been unlinked." + else + local Count = 0 - if Ent.BulletData.Type ~= "Refill" then - local ProjectileMass = math.Round(Ent.BulletData.ProjMass, 2) - local PropellantMass = math.Round(Ent.BulletData.PropMass, 2) - local CartridgeMass = math.Round(Ent.BulletData.CartMass, 2) + for Entity in pairs(self.Weapons) do + if Blacklist[Entity.Class] then + self:Unlink(Entity) - BulletInfo = BulletText:format(CartridgeMass, ProjectileMass, PropellantMass) - end + Entity:Unload() - if AmmoInfo and AmmoInfo ~= "" then - AmmoInfo = "\n\n" .. AmmoInfo + Count = Count + 1 end - - Ent:SetOverlayText(Text:format(Status, X, Y, Z, Ent.BulletData.Type .. Tracer, Ent.Ammo, Ent.Capacity, BulletInfo, AmmoInfo)) end - end - function ENT:UpdateOverlay(Instant) - if Instant then - return Overlay(self) + -- Note: Wouldn't this just unlink all weapons anyway? + if Count > 0 then + Extra = " Unlinked " .. Count .. " weapons from this crate." end + end - if TimerExists("ACF Overlay Buffer" .. self:EntIndex()) then -- This entity has been updated too recently - self.OverlayBuffer = true -- Mark it to update when buffer time has expired - else - TimerCreate("ACF Overlay Buffer" .. self:EntIndex(), 1, 1, function() - if IsValid(self) and self.OverlayBuffer then - self.OverlayBuffer = nil + self:UpdateOverlay(true) - Overlay(self) - end - end) + net.Start("ACF_UpdateEntity") + net.WriteEntity(self) + net.Broadcast() - Overlay(self) - end - end + return true, "Crate updated successfully." .. Extra end +end --------------------------------------------- - do -- Legal Checks -------------------------- - function ENT:Enable() - WireLib.TriggerOutput(self, "Loading", self:CanConsume() and 1 or 0) +do -- ACF Activation and Damage ----------------- + local function CookoffCrate(Entity) + if Entity.Ammo <= 1 or Entity.Damaged < ACF.CurTime then -- Detonate when time is up or crate is out of ammo + Entity:Detonate() + elseif Entity.BulletData.Type ~= "Refill" and Entity.RoundData then -- Spew bullets out everywhere + local VolumeRoll = math.Rand(0, 150) > Entity.BulletData.RoundVolume ^ 0.5 + local AmmoRoll = math.Rand(0, 1) < Entity.Ammo / math.max(Entity.Capacity, 1) - self:UpdateOverlay(true) - self:UpdateMass(true) - end + if VolumeRoll and AmmoRoll then + local Speed = ACF_MuzzleVelocity(Entity.BulletData.PropMass, Entity.BulletData.ProjMass / 2) + local Pitch = math.max(255 - Entity.BulletData.PropMass * 100,60) - function ENT:Disable() - WireLib.TriggerOutput(self, "Loading", 0) + Entity:EmitSound("ambient/explosions/explode_4.wav", 140, Pitch, ACF.Volume) - self:UpdateOverlay(true) - self:UpdateMass(true) - end - end + Entity.BulletData.Pos = Entity:LocalToWorld(Entity:OBBCenter() + VectorRand() * Entity:GetSize() * 0.5) + Entity.BulletData.Flight = VectorRand():GetNormalized() * Speed * 39.37 + ACF_GetAncestor(Entity):GetVelocity() + Entity.BulletData.Owner = Entity.Inflictor or Entity.Owner + Entity.BulletData.Gun = Entity + Entity.BulletData.Crate = Entity:EntIndex() - do -- Consuming ammo, updating mass -------- - function ENT:CanConsume() - if self.Disabled then return false end - if not self.Load then return false end - if self.Damaged then return false end + Entity.RoundData:Create(Entity, Entity.BulletData) - return self.Ammo > 0 + Entity:Consume() + end end + end - function ENT:Consume(Num) - self.Ammo = math.Clamp(self.Ammo - (Num or 1), 0, self.Capacity) + ------------------------------------------------------------------------------- - self:UpdateOverlay() - self:UpdateMass() + function ENT:ACF_Activate(Recalc) + local PhysObj = self.ACF.PhysObj - WireLib.TriggerOutput(self, "Ammo", self.Ammo) - WireLib.TriggerOutput(self, "Loading", self:CanConsume() and 1 or 0) + if not self.ACF.Area then + self.ACF.Area = PhysObj:GetSurfaceArea() * 6.45 + end - if TimerExists("ACF Network Ammo " .. self:EntIndex()) then return end + local Volume = PhysObj:GetVolume() - TimerCreate("ACF Network Ammo " .. self:EntIndex(), 1, 1, function() - if not IsValid(self) then return end + local Armour = ACF.AmmoArmor + local Health = Volume / ACF.Threshold --Setting the threshold of the prop Area gone + local Percent = 1 - self:SetNWInt("Ammo", self.Ammo) - end) + if Recalc and self.ACF.Health and self.ACF.MaxHealth then + Percent = self.ACF.Health / self.ACF.MaxHealth end - local function UpdateMass(Ent) - Ent.ACF.LegalMass = math.floor(Ent.EmptyMass + (Ent.AmmoMassMax * (Ent.Ammo / math.max(Ent.Capacity, 0)))) + self.ACF.Health = Health * Percent + self.ACF.MaxHealth = Health + self.ACF.Armour = Armour * (0.5 + Percent / 2) + self.ACF.MaxArmour = Armour + self.ACF.Type = "Prop" + end + + function ENT:ACF_OnDamage(Energy, FrArea, Ang, Inflictor, _, Type) + local Mul = (Type == "HEAT" and ACF.HEATMulAmmo) or 1 --Heat penetrators deal bonus damage to ammo + local HitRes = ACF.PropDamage(self, Energy, FrArea * Mul, Ang, Inflictor) --Calling the standard damage prop function - local Phys = Ent:GetPhysicsObject() + if self.Exploding or not self.IsExplosive then return HitRes end - if IsValid(Phys) then - Phys:SetMass(Ent.ACF.LegalMass) - end - end + if HitRes.Kill then + if HookRun("ACF_AmmoExplode", self, self.BulletData) == false then return HitRes end - function ENT:UpdateMass(Instant) - if Instant then - return UpdateMass(self) + if IsValid(Inflictor) and Inflictor:IsPlayer() then + self.Inflictor = Inflictor end - if TimerExists("ACF Mass Buffer" .. self:EntIndex()) then return end - - TimerCreate("ACF Mass Buffer" .. self:EntIndex(), 5, 1, function() - if not IsValid(self) then return end + if self.Ammo > 0 then + self:Detonate() + end - UpdateMass(self) - end) + return HitRes end - end - do -- Misc ---------------------------------- - local function CookoffCrate(Entity) - if Entity.Ammo <= 1 or Entity.Damaged < ACF.CurTime then -- Detonate when time is up or crate is out of ammo - Entity:Detonate() - elseif Entity.BulletData.Type ~= "Refill" and Entity.RoundData then -- Spew bullets out everywhere - local VolumeRoll = math.Rand(0, 150) > Entity.BulletData.RoundVolume ^ 0.5 - local AmmoRoll = math.Rand(0, 1) < Entity.Ammo / math.max(Entity.Capacity, 1) + -- Cookoff chance + if self.Damaged then return HitRes end -- Already cooking off - if VolumeRoll and AmmoRoll then - local Speed = ACF_MuzzleVelocity(Entity.BulletData.PropMass, Entity.BulletData.ProjMass / 2, Entity.Caliber) + local Ratio = (HitRes.Damage / self.BulletData.RoundVolume) ^ 0.2 - Entity:EmitSound("ambient/explosions/explode_4.wav", 140, math.max(255 - Entity.BulletData.PropMass * 100,60), ACF.SoundVolume) + if (Ratio * self.Capacity / self.Ammo) > math.Rand(0, 1) then + self.Inflictor = Inflictor + self.Damaged = ACF.CurTime + (5 - Ratio * 3) - Entity.BulletData.Pos = Entity:LocalToWorld(Entity:OBBCenter() + VectorRand() * (Entity:OBBMaxs() - Entity:OBBMins()) / 2) - Entity.BulletData.Flight = VectorRand():GetNormalized() * Speed * 39.37 + ACF_GetAncestor(Entity):GetVelocity() - Entity.BulletData.Owner = Entity.Inflictor or Entity.Owner - Entity.BulletData.Gun = Entity - Entity.BulletData.Crate = Entity:EntIndex() + local Interval = 0.01 + self.BulletData.RoundVolume ^ 0.5 / 100 - Entity.RoundData.create(Entity, Entity.BulletData) + TimerCreate("ACF Crate Cookoff " .. self:EntIndex(), Interval, 0, function() + if not IsValid(self) then return end - Entity:Consume() - end - end + CookoffCrate(self) + end) end - function ENT:ACF_Activate(Recalc) - local PhysObj = self.ACF.PhysObj + return HitRes + end - if not self.ACF.Area then - self.ACF.Area = PhysObj:GetSurfaceArea() * 6.45 - end + function ENT:Detonate() + if not self.Damaged then return end - local Volume = PhysObj:GetVolume() + self.Exploding = true + self.Damaged = nil -- Prevent multiple explosions - local Armour = ACF.AmmoArmor - local Health = Volume / ACF.Threshold --Setting the threshold of the prop Area gone - local Percent = 1 + timer.Remove("ACF Crate Cookoff " .. self:EntIndex()) -- Prevent multiple explosions - if Recalc and self.ACF.Health and self.ACF.MaxHealth then - Percent = self.ACF.Health / self.ACF.MaxHealth - end + local Pos = self:LocalToWorld(self:OBBCenter() + VectorRand() * self:GetSize() * 0.5) + local Filler = self.BulletData.FillerMass or 0 + local Propellant = self.BulletData.PropMass or 0 + local ExplosiveMass = (Filler + Propellant * (ACF.PBase / ACF.HEPower)) * self.Ammo + local FragMass = self.BulletData.ProjMass or ExplosiveMass * 0.5 - self.ACF.Health = Health * Percent - self.ACF.MaxHealth = Health - self.ACF.Armour = Armour * (0.5 + Percent / 2) - self.ACF.MaxArmour = Armour - self.ACF.Type = "Prop" - end + ACF_KillChildProps(self, Pos, ExplosiveMass) + ACF_HE(Pos, ExplosiveMass, FragMass, self.Inflictor, {self}, self) - function ENT:ACF_OnDamage(Entity, Energy, FrArea, Ang, Inflictor, _, Type) - local Mul = (Type == "HEAT" and ACF.HEATMulAmmo) or 1 --Heat penetrators deal bonus damage to ammo - local HitRes = ACF.PropDamage(Entity, Energy, FrArea * Mul, Ang, Inflictor) --Calling the standard damage prop function + local Effect = EffectData() + Effect:SetOrigin(Pos) + Effect:SetNormal(Vector(0, 0, -1)) + Effect:SetScale(math.max(ExplosiveMass ^ 0.33 * 8 * 39.37, 1)) + Effect:SetRadius(0) - if self.Exploding or not self.IsExplosive then return HitRes end + util.Effect("ACF_Explosion", Effect) - if HitRes.Kill then - if hook.Run("ACF_AmmoExplode", self, self.BulletData) == false then return HitRes end + constraint.RemoveAll(self) + self:Remove() + end +end --------------------------------------------- - self.Exploding = true +do -- Entity Inputs ----------------------------- + WireLib.AddInputAlias("Active", "Load") + WireLib.AddOutputAlias("Munitions", "Ammo") - if IsValid(Inflictor) and Inflictor:IsPlayer() then - self.Inflictor = Inflictor - end + ACF.AddInputAction("acf_ammo", "Load", function(Entity, Value) + Entity.Load = tobool(Value) - if self.Ammo > 0 then - self:Detonate() - end + WireLib.TriggerOutput(Entity, "Loading", Entity:CanConsume() and 1 or 0) + end) +end --------------------------------------------- + +do -- Entity Overlay ---------------------------- + local Text = "%s\n\nSize: %sx%sx%s\n\nContents: %s ( %s / %s )%s%s%s" + local BulletText = "\nCartridge Mass: %s kg\nProjectile Mass: %s kg\nPropellant Mass: %s kg" + + function ENT:UpdateOverlayText() + local Tracer = self.BulletData.Tracer ~= 0 and "-T" or "" + local AmmoType = self.BulletData.Type .. Tracer + local X, Y, Z = self:GetSize():Unpack() + local AmmoInfo = self.RoundData:GetCrateText(self.BulletData) + local ExtraInfo = ACF.GetOverlayText(self) + local BulletInfo = "" + local Status + + if next(self.Weapons) or self.IsRefill then + Status = self:CanConsume() and "Providing Ammo" or (self.Ammo ~= 0 and "Idle" or "Empty") + else + Status = "Not linked to a weapon!" + end - return HitRes - end + X = math.Round(X, 2) + Y = math.Round(Y, 2) + Z = math.Round(Z, 2) - -- Cookoff chance - if self.Damaged then return HitRes end -- Already cooking off + if self.BulletData.Type ~= "Refill" then + local Projectile = math.Round(self.BulletData.ProjMass, 2) + local Propellant = math.Round(self.BulletData.PropMass, 2) + local Cartridge = math.Round(self.BulletData.CartMass, 2) - local Ratio = (HitRes.Damage / self.BulletData.RoundVolume) ^ 0.2 + BulletInfo = BulletText:format(Cartridge, Projectile, Propellant) + end - if (Ratio * self.Capacity / self.Ammo) > math.Rand(0, 1) then - self.Inflictor = Inflictor - self.Damaged = ACF.CurTime + (5 - Ratio * 3) + if AmmoInfo and AmmoInfo ~= "" then + AmmoInfo = "\n\n" .. AmmoInfo + end - local Interval = 0.01 + self.BulletData.RoundVolume ^ 0.5 / 100 + return Text:format(Status, X, Y, Z, AmmoType, self.Ammo, self.Capacity, BulletInfo, AmmoInfo, ExtraInfo) + end +end --------------------------------------------- - TimerCreate("ACF Crate Cookoff " .. self:EntIndex(), Interval, 0, function() - if not IsValid(self) then return end +do -- Mass Update ------------------------------- + local function UpdateMass(Ent) + Ent.ACF.LegalMass = math.floor(Ent.EmptyMass + (Ent.AmmoMassMax * (Ent.Ammo / math.max(Ent.Capacity, 1)))) - CookoffCrate(self) - end) - end + local Phys = Ent:GetPhysicsObject() - return HitRes + if IsValid(Phys) then + Phys:SetMass(Ent.ACF.LegalMass) end + end - function ENT:Update(ArgsTable) - -- That table is the player data, as sorted in the ACFCvars above, with player who shot, - -- and pos and angle of the tool trace inserted at the start - local Message = "Ammo crate updated successfully!" + ------------------------------------------------------------------------------- - if ArgsTable[1] ~= self.Owner then return false, "You don't own that ammo crate!" end -- Argtable[1] is the player that shot the tool - if ArgsTable[5] == "Refill" then return false, "Refill ammo type is only avaliable for new crates!" end -- Argtable[5] is the round type. If it's refill it shouldn't be loaded into guns, so we refuse to change to it + function ENT:UpdateMass(Instant) + if Instant then + return UpdateMass(self) + end - -- Argtable[4] is the weapon ID the new ammo loads into - if ArgsTable[4] ~= self.RoundId then - for Gun in pairs(self.Weapons) do - self:Unlink(Gun) - end + if TimerExists("ACF Mass Buffer" .. self:EntIndex()) then return end - Message = "New ammo type loaded, crate unlinked." - else -- ammotype wasn't changed, but let's check if new roundtype is blacklisted - local Blacklist = ACF.AmmoBlacklist[ArgsTable[5]] + TimerCreate("ACF Mass Buffer" .. self:EntIndex(), 5, 1, function() + if not IsValid(self) then return end - for Gun in pairs(self.Weapons) do - if table.HasValue(Blacklist, Gun.Class) then - self:Unlink(Gun) - Gun:Unload() + UpdateMass(self) + end) + end +end --------------------------------------------- - Message = "New round type cannot be used with linked gun, crate unlinked and gun unloaded." - end - end - end +do -- Ammo Consumption ------------------------- + function ENT:CanConsume() + if self.Disabled then return false end + if not self.Load then return false end + if self.Damaged then return false end - UpdateAmmoData(self, unpack(ArgsTable, 4)) + return self.Ammo > 0 + end - return true, Message - end + function ENT:Consume(Num) + self.Ammo = math.Clamp(self.Ammo - (Num or 1), 0, self.Capacity) - function ENT:OnResized() - local Size = self:GetSize() + self:UpdateOverlay() + self:UpdateMass() - do -- Calculate new empty mass - local A = ACF.AmmoArmor * 0.039 -- Millimeters to inches - local ExteriorVolume = Size[1] * Size[2] * Size[3] - local InteriorVolume = (Size[1] - A) * (Size[2] - A) * (Size[3] - A) -- Math degree + WireLib.TriggerOutput(self, "Ammo", self.Ammo) + WireLib.TriggerOutput(self, "Loading", self:CanConsume() and 1 or 0) - local Volume = ExteriorVolume - InteriorVolume - local Mass = Volume * 0.13 -- Kg of steel per inch + if TimerExists("ACF Network Ammo " .. self:EntIndex()) then return end - self.EmptyMass = Mass - end + TimerCreate("ACF Network Ammo " .. self:EntIndex(), 0.5, 1, function() + if not IsValid(self) then return end - self.HitBoxes = { - Main = { - Pos = self:OBBCenter(), - Scale = Size, - } - } + self:SetNWInt("Ammo", self.Ammo) + end) + end +end --------------------------------------------- - -- TODO: Remove as soon as racks are improved, this is only being readded because of them - local PhysObj = self:GetPhysicsObject() +do -- Misc -------------------------------------- + function ENT:Enable() + WireLib.TriggerOutput(self, "Loading", self:CanConsume() and 1 or 0) - if IsValid(PhysObj) then - self.Volume = PhysObj:GetVolume() * 0.1576 * ACF.AmmoMod - end + self:UpdateMass(true) + end - self:UpdateOverlay() - end + function ENT:Disable() + WireLib.TriggerOutput(self, "Loading", 0) - function ENT:Detonate() - timer.Remove("ACF Crate Cookoff " .. self:EntIndex()) -- Prevent multiple explosions - self.Damaged = nil -- Prevent multiple explosions + self:UpdateMass(true) + end - local Pos = self:LocalToWorld(self:OBBCenter() + VectorRand() * (self:OBBMaxs() - self:OBBMins()) / 2) - local Filler = self.RoundType == "Refill" and 0.001 or self.BulletData.FillerMass or 0 - local Propellant = self.RoundType == "Refill" and 0.001 or self.BulletData.PropMass or 0 + function ENT:OnResized(Size) + do -- Calculate new empty mass + local A = ACF.AmmoArmor * 0.039 -- Millimeters to inches + local ExteriorVolume = Size.x * Size.y * Size.z + local InteriorVolume = (Size.x - A) * (Size.y - A) * (Size.z - A) -- Math degree - local ExplosiveMass = (Filler + Propellant * (ACF.PBase / ACF.HEPower)) * self.Ammo - local FragMass = self.BulletData.ProjMass or ExplosiveMass * 0.5 + local Volume = ExteriorVolume - InteriorVolume + local Mass = Volume * 0.13 -- Kg of steel per inch - ACF_KillChildProps(self, Pos, ExplosiveMass) - ACF_HE(Pos, ExplosiveMass, FragMass, self.Inflictor, {self}, self) + self.EmptyMass = Mass + end - local Effect = EffectData() - Effect:SetOrigin(Pos) - Effect:SetNormal(Vector(0, 0, -1)) - Effect:SetScale(math.max(ExplosiveMass ^ 0.33 * 8 * 39.37, 1)) - Effect:SetRadius(0) + self.HitBoxes = { + Main = { + Pos = self:OBBCenter(), + Scale = Size, + } + } + end - util.Effect("ACF_Explosion", Effect) + function ENT:OnRemove() + local Class = self.ClassData - constraint.RemoveAll(self) - self:Remove() + if Class.OnLast then + Class.OnLast(self, Class) end - function ENT:OnRemove() - ActiveCrates[self] = nil - - if self.SupplyingTo then - for Crate in pairs(self.SupplyingTo) do - Crate:RemoveCallOnRemove("ACF Refill " .. self:EntIndex()) - - self.SupplyingTo[Crate] = nil - end - end + HookRun("ACF_OnEntityLast", "acf_ammo", self, Class) - if self.Damaged then -- Detonate immediately if cooking off - self:Detonate() - end + ActiveCrates[self] = nil - for K in pairs(self.Weapons) do -- Unlink weapons - self:Unlink(K) - end + if self.RoundData.OnLast then + self.RoundData:OnLast(self) + end - timer.Remove("ACF Refill " .. self:EntIndex()) - timer.Remove("ACF Crate Cookoff " .. self:EntIndex()) + self:Detonate() -- Detonate immediately if cooking off - WireLib.Remove(self) + for K in pairs(self.Weapons) do -- Unlink weapons + self:Unlink(K) end + + WireLib.Remove(self) end -end +end --------------------------------------------- diff --git a/lua/entities/acf_ammo/shared.lua b/lua/entities/acf_ammo/shared.lua index 78844338f..c2f5a22ee 100644 --- a/lua/entities/acf_ammo/shared.lua +++ b/lua/entities/acf_ammo/shared.lua @@ -1,4 +1,8 @@ -DEFINE_BASECLASS("base_scalable_box") +DEFINE_BASECLASS("acf_base_scalable") ENT.PrintName = "ACF Ammo Crate" -ENT.WireDebugName = "ACF Ammo Crate" \ No newline at end of file +ENT.WireDebugName = "ACF Ammo Crate" +ENT.PluralName = "ACF Ammo Crates" +ENT.IsAmmoCrate = true + +cleanup.Register("acf_ammo") diff --git a/lua/entities/acf_base_scalable/cl_init.lua b/lua/entities/acf_base_scalable/cl_init.lua new file mode 100644 index 000000000..17077083a --- /dev/null +++ b/lua/entities/acf_base_scalable/cl_init.lua @@ -0,0 +1,26 @@ +DEFINE_BASECLASS("base_scalable_mconvex") -- Required to get the local BaseClass + +include("shared.lua") + +local HideInfo = ACF.HideInfoBubble + +function ENT:Initialize(...) + BaseClass.Initialize(self, ...) + + self:Update() +end + +function ENT:Update() +end + +-- Copied from base_wire_entity: DoNormalDraw's notip arg isn't accessible from ENT:Draw defined there. +function ENT:Draw() + self:DoNormalDraw(false, HideInfo()) + + Wire_Render(self) + + if self.GetBeamLength and (not self.GetShowBeam or self:GetShowBeam()) then + -- Every SENT that has GetBeamLength should draw a tracer. Some of them have the GetShowBeam boolean + Wire_DrawTracerBeam(self, 1, self.GetBeamHighlight and self:GetBeamHighlight() or false) + end +end \ No newline at end of file diff --git a/lua/entities/acf_base_scalable/init.lua b/lua/entities/acf_base_scalable/init.lua new file mode 100644 index 000000000..2c580785e --- /dev/null +++ b/lua/entities/acf_base_scalable/init.lua @@ -0,0 +1,183 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +local ACF = ACF + +ENT.OverlayDelay = 1 -- Time in seconds between each overlay update + +-- You should overwrite these +function ENT:Enable() end +function ENT:Disable() end +function ENT:UpdateOverlayText() end + +do -- Entity Overlay ---------------------------- + local Disable = "Disabled: %s\n%s" + local Name = "ACF Overlay Buffer %s" + local timer = timer + + local function GetText(Entity) + if Entity.Disabled then + return Entity:GetDisableText() + end + + return Entity:UpdateOverlayText() + end + + function ENT:GetDisableText() + return Disable:format(self.DisableReason, self.DisableDescription) + end + + function ENT:UpdateOverlay(Instant) + if Instant then + return self:SetOverlayText(GetText(self)) + end + + if self.OverlayCooldown then -- This entity has been updated too recently + self.QueueOverlay = true -- Mark it to update when buffer time has expired + else + self:SetOverlayText(GetText(self)) + + self.OverlayCooldown = true + + timer.Create(Name:format(self:EntIndex()), self.OverlayDelay, 1, function() + if not IsValid(self) then return end + + self.OverlayCooldown = nil + + if self.QueueOverlay then + self.QueueOverlay = nil + + self:UpdateOverlay() + end + end) + end + end +end --------------------------------------------- + +do -- Entity linking and unlinking -------------- + local LinkText = "%s can't be linked to %s." + local UnlinkText = "%s can't be unlinked from %s." + + function ENT:Link(Target) + if not IsValid(Target) then return false, "Attempted to link an invalid entity." end + if self == Target then return false, "Can't link an entity to itself." end + + local Class = Target:GetClass() + local Function = ACF.GetClassLink(self:GetClass(), Class) + + if Function then + return Function(self, Target) + elseif self.DefaultLink then + return self:DefaultLink(Target) + end + + return false, LinkText:format(self.PluralName, Target.PluralName or Class) + end + + function ENT:Unlink(Target) + if not IsValid(Target) then return false, "Attempted to unlink an invalid entity." end + if self == Target then return false, "Can't unlink an entity from itself." end + + local Class = Target:GetClass() + local Function = ACF.GetClassUnlink(self:GetClass(), Class) + + if Function then + return Function(self, Target) + elseif self.DefaultUnlink then + return self:DefaultUnlink(Target) + end + + return false, UnlinkText:format(self.PluralName, Target.PluralName or Class) + end +end --------------------------------------------- + +do -- Entity inputs ----------------------------- + local function SetupInputActions(Entity) + Entity.InputActions = ACF.GetInputActions(Entity:GetClass()) + end + + local function FindInputName(Entity, Name, Actions) + if not Entity.InputAliases then return Name end + + local Aliases = Entity.InputAliases + local Checked = { [Name] = true } + + repeat + if Actions[Name] then + return Name + end + + Checked[Name] = true + + Name = Aliases[Name] or Name + until + Checked[Name] + + + return Name + end + + function ENT:TriggerInput(Name, Value) + if self.Disabled then return end -- Ignore input if disabled + if not self.InputActions then SetupInputActions(self) end + + local Actions = self.InputActions + local RealName = FindInputName(self, Name, Actions) + local Action = Actions[RealName] + + if Action then + Action(self, Value) + + self:UpdateOverlay() + end + end +end --------------------------------------------- + +do -- Entity user ------------------------------- + -- TODO: Add a function to register more user sources + local WireTable = { + gmod_wire_adv_pod = true, + gmod_wire_joystick = true, + gmod_wire_expression2 = true, + gmod_wire_joystick_multi = true, + gmod_wire_pod = function(_, Input) + if IsValid(Input.Pod) then + return Input.Pod:GetDriver() + end + end, + gmod_wire_keyboard = function(_, Input) + if Input.ply then + return Input.ply + end + end, + } + + local function FindUser(Entity, Input, Checked) + local Function = WireTable[Input:GetClass()] + + return Function and Function(Entity, Input, Checked or {}) + end + + WireTable.gmod_wire_adv_pod = WireTable.gmod_wire_pod + WireTable.gmod_wire_joystick = WireTable.gmod_wire_pod + WireTable.gmod_wire_joystick_multi = WireTable.gmod_wire_pod + WireTable.gmod_wire_expression2 = function(This, Input, Checked) + for _, V in pairs(Input.Inputs) do + if IsValid(V.Src) and not Checked[V.Src] and WireTable[V.Src:GetClass()] then + Checked[V.Src] = true -- We don't want to start an infinite loop + + return FindUser(This, V.Src, Checked) + end + end + end + + function ENT:GetUser(Input) + if not IsValid(Input) then return self:GetPlayer() end + + local User = FindUser(self, Input) + + return IsValid(User) and User or self:GetPlayer() + end +end --------------------------------------------- diff --git a/lua/entities/acf_base_scalable/shared.lua b/lua/entities/acf_base_scalable/shared.lua new file mode 100644 index 000000000..dbcf1357d --- /dev/null +++ b/lua/entities/acf_base_scalable/shared.lua @@ -0,0 +1,6 @@ +DEFINE_BASECLASS("base_scalable_mconvex") + +ENT.PrintName = "Scalable ACF Base Entity" +ENT.WireDebugName = "Scalable ACF Base Entity" +ENT.PluralName = "Scalable ACF Base Entities" +ENT.IsACFEntity = true \ No newline at end of file diff --git a/lua/entities/acf_base_simple/cl_init.lua b/lua/entities/acf_base_simple/cl_init.lua new file mode 100644 index 000000000..e9f36b206 --- /dev/null +++ b/lua/entities/acf_base_simple/cl_init.lua @@ -0,0 +1,26 @@ +DEFINE_BASECLASS("base_wire_entity") -- Required to get the local BaseClass + +include("shared.lua") + +local HideInfo = ACF.HideInfoBubble + +function ENT:Initialize(...) + BaseClass.Initialize(self, ...) + + self:Update() +end + +function ENT:Update() +end + +-- Copied from base_wire_entity: DoNormalDraw's notip arg isn't accessible from ENT:Draw defined there. +function ENT:Draw() + self:DoNormalDraw(false, HideInfo()) + + Wire_Render(self) + + if self.GetBeamLength and (not self.GetShowBeam or self:GetShowBeam()) then + -- Every SENT that has GetBeamLength should draw a tracer. Some of them have the GetShowBeam boolean + Wire_DrawTracerBeam(self, 1, self.GetBeamHighlight and self:GetBeamHighlight() or false) + end +end diff --git a/lua/entities/acf_base_simple/init.lua b/lua/entities/acf_base_simple/init.lua new file mode 100644 index 000000000..2c580785e --- /dev/null +++ b/lua/entities/acf_base_simple/init.lua @@ -0,0 +1,183 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +local ACF = ACF + +ENT.OverlayDelay = 1 -- Time in seconds between each overlay update + +-- You should overwrite these +function ENT:Enable() end +function ENT:Disable() end +function ENT:UpdateOverlayText() end + +do -- Entity Overlay ---------------------------- + local Disable = "Disabled: %s\n%s" + local Name = "ACF Overlay Buffer %s" + local timer = timer + + local function GetText(Entity) + if Entity.Disabled then + return Entity:GetDisableText() + end + + return Entity:UpdateOverlayText() + end + + function ENT:GetDisableText() + return Disable:format(self.DisableReason, self.DisableDescription) + end + + function ENT:UpdateOverlay(Instant) + if Instant then + return self:SetOverlayText(GetText(self)) + end + + if self.OverlayCooldown then -- This entity has been updated too recently + self.QueueOverlay = true -- Mark it to update when buffer time has expired + else + self:SetOverlayText(GetText(self)) + + self.OverlayCooldown = true + + timer.Create(Name:format(self:EntIndex()), self.OverlayDelay, 1, function() + if not IsValid(self) then return end + + self.OverlayCooldown = nil + + if self.QueueOverlay then + self.QueueOverlay = nil + + self:UpdateOverlay() + end + end) + end + end +end --------------------------------------------- + +do -- Entity linking and unlinking -------------- + local LinkText = "%s can't be linked to %s." + local UnlinkText = "%s can't be unlinked from %s." + + function ENT:Link(Target) + if not IsValid(Target) then return false, "Attempted to link an invalid entity." end + if self == Target then return false, "Can't link an entity to itself." end + + local Class = Target:GetClass() + local Function = ACF.GetClassLink(self:GetClass(), Class) + + if Function then + return Function(self, Target) + elseif self.DefaultLink then + return self:DefaultLink(Target) + end + + return false, LinkText:format(self.PluralName, Target.PluralName or Class) + end + + function ENT:Unlink(Target) + if not IsValid(Target) then return false, "Attempted to unlink an invalid entity." end + if self == Target then return false, "Can't unlink an entity from itself." end + + local Class = Target:GetClass() + local Function = ACF.GetClassUnlink(self:GetClass(), Class) + + if Function then + return Function(self, Target) + elseif self.DefaultUnlink then + return self:DefaultUnlink(Target) + end + + return false, UnlinkText:format(self.PluralName, Target.PluralName or Class) + end +end --------------------------------------------- + +do -- Entity inputs ----------------------------- + local function SetupInputActions(Entity) + Entity.InputActions = ACF.GetInputActions(Entity:GetClass()) + end + + local function FindInputName(Entity, Name, Actions) + if not Entity.InputAliases then return Name end + + local Aliases = Entity.InputAliases + local Checked = { [Name] = true } + + repeat + if Actions[Name] then + return Name + end + + Checked[Name] = true + + Name = Aliases[Name] or Name + until + Checked[Name] + + + return Name + end + + function ENT:TriggerInput(Name, Value) + if self.Disabled then return end -- Ignore input if disabled + if not self.InputActions then SetupInputActions(self) end + + local Actions = self.InputActions + local RealName = FindInputName(self, Name, Actions) + local Action = Actions[RealName] + + if Action then + Action(self, Value) + + self:UpdateOverlay() + end + end +end --------------------------------------------- + +do -- Entity user ------------------------------- + -- TODO: Add a function to register more user sources + local WireTable = { + gmod_wire_adv_pod = true, + gmod_wire_joystick = true, + gmod_wire_expression2 = true, + gmod_wire_joystick_multi = true, + gmod_wire_pod = function(_, Input) + if IsValid(Input.Pod) then + return Input.Pod:GetDriver() + end + end, + gmod_wire_keyboard = function(_, Input) + if Input.ply then + return Input.ply + end + end, + } + + local function FindUser(Entity, Input, Checked) + local Function = WireTable[Input:GetClass()] + + return Function and Function(Entity, Input, Checked or {}) + end + + WireTable.gmod_wire_adv_pod = WireTable.gmod_wire_pod + WireTable.gmod_wire_joystick = WireTable.gmod_wire_pod + WireTable.gmod_wire_joystick_multi = WireTable.gmod_wire_pod + WireTable.gmod_wire_expression2 = function(This, Input, Checked) + for _, V in pairs(Input.Inputs) do + if IsValid(V.Src) and not Checked[V.Src] and WireTable[V.Src:GetClass()] then + Checked[V.Src] = true -- We don't want to start an infinite loop + + return FindUser(This, V.Src, Checked) + end + end + end + + function ENT:GetUser(Input) + if not IsValid(Input) then return self:GetPlayer() end + + local User = FindUser(self, Input) + + return IsValid(User) and User or self:GetPlayer() + end +end --------------------------------------------- diff --git a/lua/entities/acf_base_simple/shared.lua b/lua/entities/acf_base_simple/shared.lua new file mode 100644 index 000000000..7dbeb6874 --- /dev/null +++ b/lua/entities/acf_base_simple/shared.lua @@ -0,0 +1,6 @@ +DEFINE_BASECLASS("base_wire_entity") + +ENT.PrintName = "Simple ACF Base Entity" +ENT.WireDebugName = "Simple ACF Base Entity" +ENT.PluralName = "Simple ACF Base Entities" +ENT.IsACFEntity = true diff --git a/lua/entities/acf_debris/cl_init.lua b/lua/entities/acf_debris/cl_init.lua new file mode 100644 index 000000000..a131fd014 --- /dev/null +++ b/lua/entities/acf_debris/cl_init.lua @@ -0,0 +1,9 @@ +include("shared.lua") + +function ENT:Initialize() + self:SetNoDraw(true) +end + +function ENT:OnRemove() + self:StopAndDestroyParticles() +end \ No newline at end of file diff --git a/lua/entities/acf_debris/init.lua b/lua/entities/acf_debris/init.lua index 333c4a1c0..f8b3e22b1 100644 --- a/lua/entities/acf_debris/init.lua +++ b/lua/entities/acf_debris/init.lua @@ -3,19 +3,7 @@ AddCSLuaFile("shared.lua") include("shared.lua") function ENT:Initialize() + self:SetModel("models/hunter/plates/plate.mdl") self:PhysicsInit(SOLID_VPHYSICS) - self:SetMoveType(MOVETYPE_VPHYSICS) - self:SetCollisionGroup(COLLISION_GROUP_WORLD) - - local PhysObj = self:GetPhysicsObject() - - if IsValid(PhysObj) then - PhysObj:Wake() - end - - timer.Simple(30, function() - if IsValid(self) then - self:Remove() - end - end) + self:SetCollisionGroup(COLLISION_GROUP_DEBRIS) end diff --git a/lua/entities/acf_debris/shared.lua b/lua/entities/acf_debris/shared.lua index 41e8551c7..765929fdf 100644 --- a/lua/entities/acf_debris/shared.lua +++ b/lua/entities/acf_debris/shared.lua @@ -1,3 +1,4 @@ DEFINE_BASECLASS("base_anim") -ENT.PrintName = "Debris" \ No newline at end of file +ENT.PrintName = "Fireball" +ENT.RenderGroup = RENDERGROUP_OTHER \ No newline at end of file diff --git a/lua/entities/acf_engine/cl_init.lua b/lua/entities/acf_engine/cl_init.lua index c4fb15352..1b41f89ac 100644 --- a/lua/entities/acf_engine/cl_init.lua +++ b/lua/entities/acf_engine/cl_init.lua @@ -1,70 +1,9 @@ include("shared.lua") -local HideInfo = ACF.HideInfoBubble +language.Add("Cleanup_acf_engine", "ACF Engines") +language.Add("Cleaned_acf_engine", "Cleaned up all ACF Engines") +language.Add("SBoxLimit__acf_engine", "You've reached the ACF Engines limit!") -function ENT:Initialize() +function ENT:Update() self.HitBoxes = ACF.HitBoxes[self:GetModel()] end - --- copied from base_wire_entity: DoNormalDraw's notip arg isn't accessible from ENT:Draw defined there. -function ENT:Draw() - self:DoNormalDraw(false, HideInfo()) - - Wire_Render(self) - - if self.GetBeamLength and (not self.GetShowBeam or self:GetShowBeam()) then - -- Every SENT that has GetBeamLength should draw a tracer. Some of them have the GetShowBeam boolean - Wire_DrawTracerBeam(self, 1, self.GetBeamHighlight and self:GetBeamHighlight() or false) - end -end - -function ACFEngineGUICreate(Table) - acfmenupanel:CPanelText("Name", Table.name) - acfmenupanel.CData.DisplayModel = vgui.Create("DModelPanel", acfmenupanel.CustomDisplay) - acfmenupanel.CData.DisplayModel:SetModel(Table.model) - acfmenupanel.CData.DisplayModel:SetCamPos(Vector(250, 500, 250)) - acfmenupanel.CData.DisplayModel:SetLookAt(Vector(0, 0, 0)) - acfmenupanel.CData.DisplayModel:SetFOV(20) - acfmenupanel.CData.DisplayModel:SetSize(acfmenupanel:GetWide(), acfmenupanel:GetWide()) - acfmenupanel.CData.DisplayModel.LayoutEntity = function() end - acfmenupanel.CustomDisplay:AddItem(acfmenupanel.CData.DisplayModel) - acfmenupanel:CPanelText("Desc", Table.desc) - local peakkw - local peakkwrpm - local pbmin - local pbmax - - --elecs and turbs get peak power in middle of rpm range - if (Table.iselec == true) then - peakkw = (Table.torque * (1 + Table.peakmaxrpm / Table.limitrpm)) * Table.limitrpm / (4 * 9548.8) --adjust torque to 1 rpm maximum, assuming a linear decrease from a max @ 1 rpm to min @ limiter - peakkwrpm = math.floor(Table.limitrpm / 2) - pbmin = Table.idlerpm - pbmax = peakkwrpm - else - peakkw = Table.torque * Table.peakmaxrpm / 9548.8 - peakkwrpm = Table.peakmaxrpm - pbmin = Table.peakminrpm - pbmax = Table.peakmaxrpm - end - - acfmenupanel:CPanelText("Power", "\nPeak Power : " .. math.floor(peakkw) .. " kW / " .. math.Round(peakkw * 1.34) .. " HP @ " .. peakkwrpm .. " RPM") - acfmenupanel:CPanelText("Torque", "Peak Torque : " .. Table.torque .. " n/m / " .. math.Round(Table.torque * 0.73) .. " ft-lb") - acfmenupanel:CPanelText("RPM", "Idle : " .. Table.idlerpm .. " RPM\nPowerband : " .. pbmin .. "-" .. pbmax .. " RPM\nRedline : " .. Table.limitrpm .. " RPM") - acfmenupanel:CPanelText("Weight", "Weight : " .. Table.weight .. " kg") - acfmenupanel:CPanelText("FuelType", "\nFuel Type : " .. Table.fuel) - - if Table.fuel == "Electric" then - local cons = ACF.FuelRate * peakkw / ACF.Efficiency[Table.enginetype] - acfmenupanel:CPanelText("FuelCons", "Peak energy use : " .. math.Round(cons, 1) .. " kW / " .. math.Round(0.06 * cons, 1) .. " MJ/min") - elseif Table.fuel == "Multifuel" then - local petrolcons = ACF.FuelRate * ACF.Efficiency[Table.enginetype] * peakkw / (60 * ACF.FuelDensity.Petrol) - local dieselcons = ACF.FuelRate * ACF.Efficiency[Table.enginetype] * peakkw / (60 * ACF.FuelDensity.Diesel) - acfmenupanel:CPanelText("FuelConsP", "Petrol Use at " .. peakkwrpm .. " rpm : " .. math.Round(petrolcons, 2) .. " liters/min / " .. math.Round(0.264 * petrolcons, 2) .. " gallons/min") - acfmenupanel:CPanelText("FuelConsD", "Diesel Use at " .. peakkwrpm .. " rpm : " .. math.Round(dieselcons, 2) .. " liters/min / " .. math.Round(0.264 * dieselcons, 2) .. " gallons/min") - else - local fuelcons = ACF.FuelRate * ACF.Efficiency[Table.enginetype] * peakkw / (60 * ACF.FuelDensity[Table.fuel]) - acfmenupanel:CPanelText("FuelCons", Table.fuel .. " Use at " .. peakkwrpm .. " rpm : " .. math.Round(fuelcons, 2) .. " liters/min / " .. math.Round(0.264 * fuelcons, 2) .. " gallons/min") - end - - acfmenupanel.CustomDisplay:PerformLayout() -end diff --git a/lua/entities/acf_engine/init.lua b/lua/entities/acf_engine/init.lua index 467634028..b71ebae2d 100644 --- a/lua/entities/acf_engine/init.lua +++ b/lua/entities/acf_engine/init.lua @@ -10,16 +10,9 @@ do ACF.RegisterClassLink("acf_engine", "acf_fueltank", function(Engine, Target) if Engine.FuelTanks[Target] then return false, "This engine is already linked to this fuel tank!" end if Target.Engines[Engine] then return false, "This engine is already linked to this fuel tank!" end + if not Engine.FuelTypes[Target.FuelType] then return false, "Cannot link because fuel type is incompatible." end if Target.NoLinks then return false, "This fuel tank doesn't allow linking." end - if Engine.FuelType == "Multifuel" then - if Target.FuelType == "Electric" then - return false, "Cannot link because fuel type is incompatible." - end - elseif Engine.FuelType ~= Target.FuelType then - return false, "Cannot link because fuel type is incompatible." - end - Engine.FuelTanks[Target] = true Target.Engines[Engine] = true @@ -31,6 +24,10 @@ do ACF.RegisterClassUnlink("acf_engine", "acf_fueltank", function(Engine, Target) if Engine.FuelTanks[Target] or Target.Engines[Engine] then + if Engine.FuelTank == Target then + Engine.FuelTank = next(Engine.FuelTanks, Target) + end + Engine.FuelTanks[Target] = nil Target.Engines[Engine] = nil @@ -105,71 +102,19 @@ end --===============================================================================================-- local CheckLegal = ACF_CheckLegal -local ClassLink = ACF.GetClassLink -local ClassUnlink = ACF.GetClassUnlink +local Engines = ACF.Classes.Engines +local EngineTypes = ACF.Classes.EngineTypes local UnlinkSound = "physics/metal/metal_box_impact_bullet%s.wav" local Round = math.Round local max = math.max local TimerCreate = timer.Create -local TimerExists = timer.Exists local TimerSimple = timer.Simple local TimerRemove = timer.Remove -local Gamemode = GetConVar("acf_gamemode") - -local function GetEfficiency(Entity) - local CompetitiveMult = Gamemode:GetInt() == 2 and ACF.CompFuelRate or 1 - - return ACF.Efficiency[Entity.EngineType] * CompetitiveMult -end - -local function UpdateEngineData(Entity, Id, EngineData) - Entity.Id = Id - Entity.Name = EngineData.name - Entity.ShortName = Id - Entity.EntType = EngineData.category - Entity.SoundPath = EngineData.sound - Entity.SoundPitch = EngineData.pitch or 1 - Entity.Mass = EngineData.weight - Entity.PeakTorque = EngineData.torque - Entity.PeakTorqueHeld = EngineData.torque - Entity.IdleRPM = EngineData.idlerpm - Entity.PeakMinRPM = EngineData.peakminrpm - Entity.PeakMaxRPM = EngineData.peakmaxrpm - Entity.LimitRPM = EngineData.limitrpm - Entity.Inertia = EngineData.flywheelmass * 3.1416 ^ 2 - Entity.IsElectric = EngineData.iselec - Entity.FlywheelOverride = EngineData.flywheeloverride - Entity.IsTrans = EngineData.istrans -- driveshaft outputs to the side - Entity.FuelType = EngineData.fuel or "Petrol" - Entity.EngineType = EngineData.enginetype or "GenericPetrol" - Entity.TorqueScale = ACF.TorqueScale[Entity.EngineType] - Entity.HitBoxes = ACF.HitBoxes[EngineData.model] - - --calculate boosted peak kw - if Entity.EngineType == "Turbine" or Entity.EngineType == "Electric" then - Entity.peakkw = (Entity.PeakTorque * (1 + Entity.PeakMaxRPM / Entity.LimitRPM)) * Entity.LimitRPM / (4 * 9548.8) --adjust torque to 1 rpm maximum, assuming a linear decrease from a max @ 1 rpm to min @ limiter - Entity.PeakKwRPM = math.floor(Entity.LimitRPM / 2) - else - Entity.peakkw = Entity.PeakTorque * Entity.PeakMaxRPM / 9548.8 - Entity.PeakKwRPM = Entity.PeakMaxRPM - end - - --calculate base fuel usage - if Entity.EngineType == "Electric" then - Entity.FuelUse = ACF.FuelRate * (GetEfficiency(Entity) / 3600) --elecs use current power output, not max - else - Entity.FuelUse = ACF.FuelRate * GetEfficiency(Entity) * Entity.peakkw / 3600 - end - - local PhysObj = Entity:GetPhysicsObject() - - if IsValid(PhysObj) then - PhysObj:SetMass(Entity.Mass) - end - - Entity:SetNWString("WireName", "ACF " .. Entity.Name) +local HookRun = hook.Run - Entity:UpdateOverlay(true) +-- Fuel consumption is increased on competitive servers +local function GetEfficiencyMult() + return ACF.Gamemode == 3 and ACF.CompFuelRate or 1 end local function GetPitchVolume(Engine) @@ -177,15 +122,13 @@ local function GetPitchVolume(Engine) local Pitch = math.Clamp(20 + (RPM * Engine.SoundPitch) * 0.02, 1, 255) local Volume = 0.25 + (0.1 + 0.9 * ((RPM / Engine.LimitRPM) ^ 1.5)) * Engine.Throttle * 0.666 * ACF.SoundVolume - return Pitch, Volume + return Pitch, Volume * ACF.Volume end local function GetNextFuelTank(Engine) if not next(Engine.FuelTanks) then return end - local Current = Engine.FuelTank - local NextKey = (IsValid(Current) and Engine.FuelTanks[Current]) and Current or nil - local Select = next(Engine.FuelTanks, NextKey) or next(Engine.FuelTanks) + local Select = next(Engine.FuelTanks, Engine.FuelTank) or next(Engine.FuelTanks) local Start = Select repeat @@ -202,7 +145,7 @@ local function CheckDistantFuelTanks(Engine) for Tank in pairs(Engine.FuelTanks) do if EnginePos:DistToSqr(Tank:GetPos()) > 262144 then - Engine:EmitSound(UnlinkSound:format(math.random(1, 3)), 70, 100, ACF.SoundVolume) + Engine:EmitSound(UnlinkSound:format(math.random(1, 3)), 70, 100, ACF.Volume) Engine:Unlink(Tank) end @@ -279,82 +222,247 @@ local function SetActive(Entity, Value) Entity:UpdateOutputs() end -local Inputs = { - Throttle = function(Entity, Value) - Entity.Throttle = math.Clamp(Value, 0, 100) / 100 - end, - Active = function(Entity, Value) - SetActive(Entity, tobool(Value)) +--===============================================================================================-- + +do -- Spawn and Update functions + local function VerifyData(Data) + if not Data.Engine then + Data.Engine = Data.Id or "5.7-V8" + end + + local Class = ACF.GetClassGroup(Engines, Data.Engine) + + if not Class then + Data.Engine = "5.7-V8" + + Class = ACF.GetClassGroup(Engines, "5.7-V8") + end + + do -- External verifications + if Class.VerifyData then + Class.VerifyData(Data, Class) + end + + HookRun("ACF_VerifyData", "acf_engine", Data, Class) + end end -} ---===============================================================================================-- + local function UpdateEngine(Entity, Data, Class, EngineData) + local Type = EngineData.Type or "GenericPetrol" + local EngineType = EngineTypes[Type] or EngineTypes.GenericPetrol + + Entity:SetModel(EngineData.Model) + + Entity:PhysicsInit(SOLID_VPHYSICS) + Entity:SetMoveType(MOVETYPE_VPHYSICS) -function MakeACF_Engine(Owner, Pos, Angle, Id, Data) - if not Owner:CheckLimit("_acf_misc") then return end + -- Storing all the relevant information on the entity for duping + for _, V in ipairs(Entity.DataStore) do + Entity[V] = Data[V] + end + + Entity.Name = EngineData.Name + Entity.ShortName = EngineData.ID + Entity.EntType = Class.Name + Entity.ClassData = Class + Entity.DefaultSound = EngineData.Sound + Entity.SoundPitch = EngineData.Pitch or 1 + Entity.PeakTorque = EngineData.Torque + Entity.PeakTorqueHeld = EngineData.Torque + Entity.IdleRPM = EngineData.RPM.Idle + Entity.PeakMinRPM = EngineData.RPM.PeakMin + Entity.PeakMaxRPM = EngineData.RPM.PeakMax + Entity.LimitRPM = EngineData.RPM.Limit + Entity.FlywheelOverride = EngineData.RPM.Override + Entity.FlywheelMass = EngineData.FlywheelMass + Entity.Inertia = EngineData.FlywheelMass * 3.1416 ^ 2 + Entity.IsElectric = EngineData.IsElectric + Entity.IsTrans = EngineData.IsTrans -- driveshaft outputs to the side + Entity.FuelTypes = EngineData.Fuel or { Petrol = true } + Entity.FuelType = next(EngineData.Fuel) + Entity.EngineType = EngineType.ID + Entity.Efficiency = EngineType.Efficiency * GetEfficiencyMult() + Entity.TorqueScale = EngineType.TorqueScale + Entity.HealthMult = EngineType.HealthMult + Entity.HitBoxes = ACF.HitBoxes[EngineData.Model] + Entity.Out = Entity:WorldToLocal(Entity:GetAttachment(Entity:LookupAttachment("driveshaft")).Pos) + + Entity:SetNWString("WireName", "ACF " .. Entity.Name) + + --calculate boosted peak kw + if EngineType.CalculatePeakEnergy then + local peakkw, PeakKwRPM = EngineType.CalculatePeakEnergy(Entity) + + Entity.peakkw = peakkw + Entity.PeakKwRPM = PeakKwRPM + else + Entity.peakkw = Entity.PeakTorque * Entity.PeakMaxRPM / 9548.8 + Entity.PeakKwRPM = Entity.PeakMaxRPM + end + + --calculate base fuel usage + if EngineType.CalculateFuelUsage then + Entity.FuelUse = EngineType.CalculateFuelUsage(Entity) + else + Entity.FuelUse = ACF.FuelRate * Entity.Efficiency * Entity.peakkw / 3600 + end + + ACF.Activate(Entity, true) - local EngineData = ACF.Weapons.Mobility[Id] + Entity.ACF.LegalMass = EngineData.Mass + Entity.ACF.Model = EngineData.Model - if not EngineData then return end + local Phys = Entity:GetPhysicsObject() + if IsValid(Phys) then Phys:SetMass(EngineData.Mass) end + end + + function MakeACF_Engine(Player, Pos, Angle, Data) + VerifyData(Data) - local Engine = ents.Create("acf_engine") + local Class = ACF.GetClassGroup(Engines, Data.Engine) + local EngineData = Class.Lookup[Data.Engine] + local Limit = Class.LimitConVar.Name - if not IsValid(Engine) then return end + if not Player:CheckLimit(Limit) then return false end - Engine:SetModel(EngineData.model) - Engine:SetPlayer(Owner) - Engine:SetAngles(Angle) - Engine:SetPos(Pos) - Engine:Spawn() + local Engine = ents.Create("acf_engine") - Engine:PhysicsInit(SOLID_VPHYSICS) - Engine:SetMoveType(MOVETYPE_VPHYSICS) + if not IsValid(Engine) then return end - Owner:AddCount("_acf_misc", Engine) - Owner:AddCleanup("acfmenu", Engine) + Engine:SetPlayer(Player) + Engine:SetAngles(Angle) + Engine:SetPos(Pos) + Engine:Spawn() - UpdateEngineData(Engine, Id, EngineData) + Player:AddCleanup("acf_engine", Engine) + Player:AddCount(Limit, Engine) - Engine.Owner = Owner - Engine.Model = EngineData.model - Engine.CanUpdate = true - Engine.Active = false - Engine.Gearboxes = {} - Engine.FuelTanks = {} - Engine.LastThink = 0 - Engine.MassRatio = 1 - Engine.FuelUsage = 0 - Engine.Throttle = 0 - Engine.FlyRPM = 0 - Engine.Out = Engine:WorldToLocal(Engine:GetAttachment(Engine:LookupAttachment("driveshaft")).Pos) + Engine.Owner = Player -- MUST be stored on ent for PP + Engine.Active = false + Engine.Gearboxes = {} + Engine.FuelTanks = {} + Engine.LastThink = 0 + Engine.MassRatio = 1 + Engine.FuelUsage = 0 + Engine.Throttle = 0 + Engine.FlyRPM = 0 + Engine.SoundPath = EngineData.Sound + Engine.Inputs = WireLib.CreateInputs(Engine, { "Active", "Throttle" }) + Engine.Outputs = WireLib.CreateOutputs(Engine, { "RPM", "Torque", "Power", "Fuel Use", "Entity [ENTITY]", "Mass", "Physical Mass" }) + Engine.DataStore = ACF.GetEntityArguments("acf_engine") - Engine.Inputs = WireLib.CreateInputs(Engine, { "Active", "Throttle" }) - Engine.Outputs = WireLib.CreateOutputs(Engine, { "RPM", "Torque", "Power", "Fuel Use", "Entity [ENTITY]", "Mass", "Physical Mass" }) + WireLib.TriggerOutput(Engine, "Entity", Engine) - WireLib.TriggerOutput(Engine, "Entity", Engine) + UpdateEngine(Engine, Data, Class, EngineData) + + if Class.OnSpawn then + Class.OnSpawn(Engine, Data, Class, EngineData) + end - ACF_Activate(Engine) + HookRun("ACF_OnEntitySpawn", "acf_engine", Engine, Data, Class, EngineData) - Engine.ACF.LegalMass = Engine.Mass - Engine.ACF.Model = Engine.Model + Engine:UpdateOverlay(true) - do -- Mass entity mod removal - local EntMods = Data and Data.EntityMods + do -- Mass entity mod removal + local EntMods = Data and Data.EntityMods - if EntMods and EntMods.mass then - EntMods.mass = nil + if EntMods and EntMods.mass then + EntMods.mass = nil + end end + + CheckLegal(Engine) + + return Engine end - CheckLegal(Engine) + ACF.RegisterEntityClass("acf_engine", MakeACF_Engine, "Engine") + ACF.RegisterLinkSource("acf_engine", "FuelTanks") + ACF.RegisterLinkSource("acf_engine", "Gearboxes") - return Engine -end + ------------------- Updating --------------------- -list.Set("ACFCvars", "acf_engine", { "id" }) -duplicator.RegisterEntityClass("acf_engine", MakeACF_Engine, "Pos", "Angle", "Id", "Data") -ACF.RegisterLinkSource("acf_engine", "FuelTanks") -ACF.RegisterLinkSource("acf_engine", "Gearboxes") + function ENT:Update(Data) + if self.Active then return false, "Turn off the engine before updating it!" end + + VerifyData(Data) + + local Class = ACF.GetClassGroup(Engines, Data.Engine) + local EngineData = Class.Lookup[Data.Engine] + local OldClass = self.ClassData + local Feedback = "" + + if OldClass.OnLast then + OldClass.OnLast(self, OldClass) + end + + HookRun("ACF_OnEntityLast", "acf_engine", self, OldClass) + + ACF.SaveEntity(self) + + UpdateEngine(self, Data, Class, EngineData) + + ACF.RestoreEntity(self) + + if Class.OnUpdate then + Class.OnUpdate(self, Data, Class, EngineData) + end + + HookRun("ACF_OnEntityUpdate", "acf_engine", self, Data, Class, EngineData) + + if next(self.Gearboxes) then + local Count, Total = 0, 0 + + for Gearbox in pairs(self.Gearboxes) do + self:Unlink(Gearbox) + + local Result = self:Link(Gearbox) + + if not Result then Count = Count + 1 end + + Total = Total + 1 + end + + if Count == Total then + Feedback = Feedback .. "\nUnlinked all gearboxes due to excessive driveshaft angle." + elseif Count > 0 then + local Text = Feedback .. "\nUnlinked %s out of %s gearboxes due to excessive driveshaft angle." + + Feedback = Text:format(Count, Total) + end + end + + if next(self.FuelTanks) then + local Count, Total = 0, 0 + + for Tank in pairs(self.FuelTanks) do + if not self.FuelTypes[Tank.FuelType] then + self:Unlink(Tank) + + Count = Count + 1 + end + + Total = Total + 1 + end + + if Count == Total then + Feedback = Feedback .. "\nUnlinked all fuel tanks due to fuel type change." + elseif Count > 0 then + local Text = Feedback .. "\nUnlinked %s out of %s fuel tanks due to fuel type change." + + Feedback = Text:format(Count, Total) + end + end + + self:UpdateOverlay(true) + + net.Start("ACF_UpdateEntity") + net.WriteEntity(self) + net.Broadcast() + + return true, "Engine updated successfully!" .. Feedback + end +end --===============================================================================================-- -- Meta Funcs @@ -380,35 +488,6 @@ function ENT:Disable() self:UpdateOverlay() end -function ENT:Update(ArgsTable) - if self.Active then return false, "Turn off the engine before updating it!" end - if ArgsTable[1] ~= self.Owner then return false, "You don't own that engine!" end - - local Id = ArgsTable[4] -- Argtable[4] is the engine ID - local EngineData = ACF.Weapons.Mobility[Id] - - if not EngineData then return false, "Invalid engine type!" end - if EngineData.model ~= self.Model then return false, "The new engine must have the same model!" end - - local Feedback = "" - - if EngineData.fuel ~= self.FuelType then - Feedback = " Fuel type changed, fuel tanks unlinked." - - for Tank in pairs(self.FuelTanks) do - self:Unlink(Tank) - end - end - - UpdateEngineData(self, Id, EngineData) - - ACF_Activate(self, true) - - self.ACF.LegalMass = self.Mass - - return true, "Engine updated successfully!" .. Feedback -end - function ENT:UpdateOutputs() if not IsValid(self) then return end @@ -420,52 +499,26 @@ function ENT:UpdateOutputs() WireLib.TriggerOutput(self, "RPM", math.floor(self.FlyRPM)) end -local function Overlay(Ent) - if Ent.Disabled then - Ent:SetOverlayText("Disabled: " .. Ent.DisableReason .. "\n" .. Ent.DisableDescription) - else - local PowerbandMin = Ent.IsElectric and Ent.IdleRPM or Ent.PeakMinRPM - local PowerbandMax = Ent.IsElectric and math.floor(Ent.LimitRPM / 2) or Ent.PeakMaxRPM - local Text - - if Ent.DisableReason then - Text = "Disabled: " .. Ent.DisableReason - else - Text = Ent.Active and "Active" or "Idle" - end - - Text = Text .. "\n\n" .. Ent.Name .. "\n" .. - "Power: " .. Round(Ent.peakkw) .. " kW / " .. Round(Ent.peakkw * 1.34) .. " hp\n" .. - "Torque: " .. Round(Ent.PeakTorque) .. " Nm / " .. Round(Ent.PeakTorque * 0.73) .. " ft-lb\n" .. - "Powerband: " .. PowerbandMin .. " - " .. PowerbandMax .. " RPM\n" .. - "Redline: " .. Ent.LimitRPM .. " RPM" - - Ent:SetOverlayText(Text) - end -end +local Text = "%s\n\n%s\nPower: %s kW / %s hp\nTorque: %s Nm / %s ft-lb\nPowerband: %s - %s RPM\nRedline: %s RPM" -function ENT:UpdateOverlay() - if TimerExists("ACF Overlay Buffer" .. self:EntIndex()) then -- This entity has been updated too recently - self.OverlayBuffer = true -- Mark it to update when buffer time has expired - else - TimerCreate("ACF Overlay Buffer" .. self:EntIndex(), 1, 1, function() - if IsValid(self) and self.OverlayBuffer then - self.OverlayBuffer = nil - self:UpdateOverlay() - end - end) +function ENT:UpdateOverlayText() + local State, Name = self.Active and "Active" or "Idle", self.Name + local Power, PowerFt = Round(self.peakkw), Round(self.peakkw * 1.34) + local Torque, TorqueFt = Round(self.PeakTorque), Round(self.PeakTorque * 0.73) + local PowerbandMin = self.IsElectric and self.IdleRPM or self.PeakMinRPM + local PowerbandMax = self.IsElectric and math.floor(self.LimitRPM / 2) or self.PeakMaxRPM + local Redline = self.LimitRPM - Overlay(self) - end + return Text:format(State, Name, Power, PowerFt, Torque, TorqueFt, PowerbandMin, PowerbandMax, Redline) end -function ENT:TriggerInput(Input, Value) - if self.Disabled then return end +ACF.AddInputAction("acf_engine", "Throttle", function(Entity, Value) + Entity.Throttle = math.Clamp(Value, 0, 100) * 0.01 +end) - if Inputs[Input] then - Inputs[Input](self, Value) - end -end +ACF.AddInputAction("acf_engine", "Active", function(Entity, Value) + SetActive(Entity, tobool(Value)) +end) function ENT:ACF_Activate() --Density of steel = 7.8g cm3 so 7.8kg for a 1mx1m plate 1m thick @@ -499,8 +552,8 @@ function ENT:ACF_Activate() Percent = self.ACF.Health / self.ACF.MaxHealth end - self.ACF.Health = Health * Percent * ACF.EngineHPMult[self.EngineType] - self.ACF.MaxHealth = Health * ACF.EngineHPMult[self.EngineType] + self.ACF.Health = Health * Percent * self.HealthMult + self.ACF.MaxHealth = Health * self.HealthMult self.ACF.Armour = Armour * (0.5 + Percent / 2) self.ACF.MaxArmour = Armour * ACF.ArmorMod self.ACF.Type = nil @@ -509,9 +562,9 @@ function ENT:ACF_Activate() end --This function needs to return HitRes -function ENT:ACF_OnDamage(Entity, Energy, FrArea, Angle, Inflictor, _, Type) +function ENT:ACF_OnDamage(Energy, FrArea, Angle, Inflictor, _, Type) local Mul = Type == "HEAT" and ACF.HEATMulEngine or 1 --Heat penetrators deal bonus damage to engines - local Res = ACF.PropDamage(Entity, Energy, FrArea * Mul, Angle, Inflictor) + local Res = ACF.PropDamage(self, Energy, FrArea * Mul, Angle, Inflictor) --adjusting performance based on damage local TorqueMult = math.Clamp(((1 - self.TorqueScale) / 0.5) * ((self.ACF.Health / self.ACF.MaxHealth) - 1) + 1, self.TorqueScale, 1) @@ -578,13 +631,14 @@ function ENT:CalcRPM() --calculate fuel usage if IsValid(FuelTank) then self.FuelTank = FuelTank + self.FuelType = FuelTank.FuelType local Consumption = self:GetConsumption(self.Throttle, self.FlyRPM) * DeltaTime self.FuelUsage = 60 * Consumption / DeltaTime FuelTank:Consume(Consumption) - elseif Gamemode:GetInt() ~= 0 then -- Sandbox gamemode servers will require no fuel + elseif ACF.Gamemode ~= 1 then -- Sandbox gamemode servers will require no fuel SetActive(self, false) self.FuelUsage = 0 @@ -644,32 +698,6 @@ function ENT:CalcRPM() end) end -function ENT:Link(Target) - if not IsValid(Target) then return false, "Attempted to link an invalid entity." end - if self == Target then return false, "Can't link an engine to itself." end - - local Function = ClassLink(self:GetClass(), Target:GetClass()) - - if Function then - return Function(self, Target) - end - - return false, "Engines can't be linked to '" .. Target:GetClass() .. "'." -end - -function ENT:Unlink(Target) - if not IsValid(Target) then return false, "Attempted to unlink an invalid entity." end - if self == Target then return false, "Can't unlink an engine from itself." end - - local Function = ClassUnlink(self:GetClass(), Target:GetClass()) - - if Function then - return Function(self, Target) - end - - return false, "Engines can't be unlinked from '" .. Target:GetClass() .. "'." -end - function ENT:PreEntityCopy() if next(self.Gearboxes) then local Gearboxes = {} @@ -696,7 +724,7 @@ function ENT:PreEntityCopy() end function ENT:PostEntityPaste(Player, Ent, CreatedEntities) - local EntMods = Ent.EntityMods + local EntMods = Ent.EntityMods -- Backwards compatibility if EntMods.GearLink then @@ -741,6 +769,14 @@ function ENT:PostEntityPaste(Player, Ent, CreatedEntities) end function ENT:OnRemove() + local Class = self.ClassData + + if Class.OnLast then + Class.OnLast(self, Class) + end + + HookRun("ACF_OnEntityLast", "acf_engine", self, Class) + if self.Sound then self.Sound:Stop() end diff --git a/lua/entities/acf_engine/shared.lua b/lua/entities/acf_engine/shared.lua index 7f646f254..51de6b963 100644 --- a/lua/entities/acf_engine/shared.lua +++ b/lua/entities/acf_engine/shared.lua @@ -1,4 +1,8 @@ -DEFINE_BASECLASS("base_wire_entity") +DEFINE_BASECLASS("acf_base_simple") ENT.PrintName = "ACF Engine" -ENT.WireDebugName = "ACF Engine" \ No newline at end of file +ENT.WireDebugName = "ACF Engine" +ENT.PluralName = "ACF Engines" +ENT.IsEngine = true + +cleanup.Register("acf_engine") diff --git a/lua/entities/acf_fueltank/cl_init.lua b/lua/entities/acf_fueltank/cl_init.lua index 2ec5df2a8..f83220761 100644 --- a/lua/entities/acf_fueltank/cl_init.lua +++ b/lua/entities/acf_fueltank/cl_init.lua @@ -1,132 +1,16 @@ include("shared.lua") -local HideInfo = ACF.HideInfoBubble +language.Add("Cleanup_acf_fueltank", "ACF Fuel Tanks") +language.Add("Cleaned_acf_fueltank", "Cleaned up all ACF Fuel Tanks") +language.Add("SBoxLimit__acf_fueltank", "You've reached the ACF Fuel Tanks limit!") -function ENT:Initialize() +function ENT:Update() self.HitBoxes = { Main = { Pos = self:OBBCenter(), Scale = (self:OBBMaxs() - self:OBBMins()) - Vector(2, 2, 2), - Angle = Angle(0, 0, 0), + Angle = Angle(), Sensitive = false } } end - --- copied from base_wire_entity: DoNormalDraw's notip arg isn't accessible from ENT:Draw defined there. -function ENT:Draw() - self:DoNormalDraw(false, HideInfo()) - - Wire_Render(self) - - if self.GetBeamLength and (not self.GetShowBeam or self:GetShowBeam()) then - -- Every SENT that has GetBeamLength should draw a tracer. Some of them have the GetShowBeam boolean - Wire_DrawTracerBeam(self, 1, self.GetBeamHighlight and self:GetBeamHighlight() or false) - end -end - -function ACFFuelTankGUICreate(Table) - if not acfmenupanel.CustomDisplay then return end - - if not acfmenupanel.FuelTankData then - acfmenupanel.FuelTankData = {} - acfmenupanel.FuelTankData.Id = "Tank_4x4x2" - acfmenupanel.FuelTankData.FuelID = "Petrol" - end - - local Tanks = ACF.Weapons.FuelTanks - local SortedTanks = {} - - for n in pairs(Tanks) do - table.insert(SortedTanks, n) - end - - table.sort(SortedTanks) - acfmenupanel:CPanelText("Name", Table.name) - acfmenupanel:CPanelText("Desc", Table.desc) - -- tank size dropbox - acfmenupanel.CData.TankSizeSelect = vgui.Create("DComboBox", acfmenupanel.CustomDisplay) - acfmenupanel.CData.TankSizeSelect:SetSize(100, 30) - - for _, v in ipairs(SortedTanks) do - acfmenupanel.CData.TankSizeSelect:AddChoice(v) - end - - acfmenupanel.CData.TankSizeSelect.OnSelect = function(_, _, data) - RunConsoleCommand("acfmenu_data1", data) - acfmenupanel.FuelTankData.Id = data - ACFFuelTankGUIUpdate(Table) - end - - acfmenupanel.CData.TankSizeSelect:SetText(acfmenupanel.FuelTankData.Id) - RunConsoleCommand("acfmenu_data1", acfmenupanel.FuelTankData.Id) - acfmenupanel.CustomDisplay:AddItem(acfmenupanel.CData.TankSizeSelect) - -- fuel type dropbox - acfmenupanel.CData.FuelSelect = vgui.Create("DComboBox", acfmenupanel.CustomDisplay) - acfmenupanel.CData.FuelSelect:SetSize(100, 30) - - for Key in pairs(ACF.FuelDensity) do - acfmenupanel.CData.FuelSelect:AddChoice(Key) - end - - acfmenupanel.CData.FuelSelect.OnSelect = function(_, _, data) - RunConsoleCommand("acfmenu_data2", data) - acfmenupanel.FuelTankData.FuelID = data - ACFFuelTankGUIUpdate(Table) - end - - acfmenupanel.CData.FuelSelect:SetText(acfmenupanel.FuelTankData.FuelID) - RunConsoleCommand("acfmenu_data2", acfmenupanel.FuelTankData.FuelID) - acfmenupanel.CustomDisplay:AddItem(acfmenupanel.CData.FuelSelect) - ACFFuelTankGUIUpdate(Table) - acfmenupanel.CustomDisplay:PerformLayout() -end - -function ACFFuelTankGUIUpdate() - if not acfmenupanel.CustomDisplay then return end - local Tanks = ACF.Weapons.FuelTanks - local TankID = acfmenupanel.FuelTankData.Id - local FuelID = acfmenupanel.FuelTankData.FuelID - local Dims = Tanks[TankID].dims - local Wall = 0.03937 --wall thickness in inches (1mm) - local Volume = Dims.V - (Dims.S * Wall) -- total volume of tank (cu in), reduced by wall thickness - local Capacity = Volume * ACF.CuIToLiter * ACF.TankVolumeMul * 0.4774 --internal volume available for fuel in liters, with magic realism number - local EmptyMass = ((Dims.S * Wall) * 16.387) * (7.9 / 1000) -- total wall volume * cu in to cc * density of steel (kg/cc) - local Mass = EmptyMass + Capacity * ACF.FuelDensity[FuelID] -- weight of tank + weight of fuel - - --fuel and tank info - if FuelID == "Electric" then - local kwh = Capacity * ACF.LiIonED - acfmenupanel:CPanelText("TankName", Tanks[TankID].name .. " Li-Ion Battery") - acfmenupanel:CPanelText("TankDesc", Tanks[TankID].desc .. "\n") - acfmenupanel:CPanelText("Cap", "Charge: " .. math.Round(kwh, 1) .. " kW hours / " .. math.Round(kwh * 3.6, 1) .. " MJ") - acfmenupanel:CPanelText("Mass", "Mass: " .. math.Round(Mass, 1) .. " kg") - else - acfmenupanel:CPanelText("TankName", Tanks[TankID].name .. " fuel tank") - acfmenupanel:CPanelText("TankDesc", Tanks[TankID].desc .. "\n") - acfmenupanel:CPanelText("Cap", "Capacity: " .. math.Round(Capacity, 1) .. " liters / " .. math.Round(Capacity * 0.264172, 1) .. " gallons") - acfmenupanel:CPanelText("Mass", "Full mass: " .. math.Round(Mass, 1) .. " kg, Empty mass: " .. math.Round(EmptyMass, 1) .. " kg") - end - - local text = "\n" - - if Tanks[TankID].nolinks then - text = "\nThis fuel tank won\'t link to engines. It's intended to resupply fuel to other fuel tanks." - end - - acfmenupanel:CPanelText("Links", text) - - --fuel tank model display - if not acfmenupanel.CData.DisplayModel then - acfmenupanel.CData.DisplayModel = vgui.Create("DModelPanel", acfmenupanel.CustomDisplay) - acfmenupanel.CData.DisplayModel:SetModel(Tanks[TankID].model) - acfmenupanel.CData.DisplayModel:SetCamPos(Vector(250, 500, 200)) - acfmenupanel.CData.DisplayModel:SetLookAt(Vector(0, 0, 0)) - acfmenupanel.CData.DisplayModel:SetFOV(10) - acfmenupanel.CData.DisplayModel:SetSize(acfmenupanel:GetWide(), acfmenupanel:GetWide()) - acfmenupanel.CData.DisplayModel.LayoutEntity = function() end - acfmenupanel.CustomDisplay:AddItem(acfmenupanel.CData.DisplayModel) - end - - acfmenupanel.CData.DisplayModel:SetModel(Tanks[TankID].model) -end \ No newline at end of file diff --git a/lua/entities/acf_fueltank/init.lua b/lua/entities/acf_fueltank/init.lua index 3ad994622..c6fce5771 100644 --- a/lua/entities/acf_fueltank/init.lua +++ b/lua/entities/acf_fueltank/init.lua @@ -8,53 +8,17 @@ include("shared.lua") --===============================================================================================-- local CheckLegal = ACF_CheckLegal -local ClassLink = ACF.GetClassLink -local ClassUnlink = ACF.GetClassUnlink +local FuelTanks = ACF.Classes.FuelTanks +local FuelTypes = ACF.Classes.FuelTypes +local ActiveTanks = ACF.FuelTanks local RefillDist = ACF.RefillDistance * ACF.RefillDistance local TimerCreate = timer.Create local TimerExists = timer.Exists - -local function UpdateFuelData(Entity, Id, Data1, Data2, FuelData) - local Percentage = 1 --how full is the tank? - - --if updating existing tank, keep fuel level - if Entity.Capacity and Entity.Capacity ~= 0 then - Percentage = Entity.Fuel / Entity.Capacity - end - - local PhysObj = Entity:GetPhysicsObject() - local Area = PhysObj:GetSurfaceArea() - local Wall = 0.03937 --wall thickness in inches (1mm) - - Entity.Id = Id - Entity.SizeId = Data1 - Entity.FuelType = Data2 - Entity.Name = Data2 .. " " .. Data1 - Entity.ShortName = Entity.Name - Entity.EntType = Data2 - Entity.Model = FuelData.model - Entity.FuelDensity = ACF.FuelDensity[Data2] - Entity.Volume = PhysObj:GetVolume() - (Area * Wall) -- total volume of tank (cu in), reduced by wall thickness - Entity.Capacity = Entity.Volume * ACF.CuIToLiter * ACF.TankVolumeMul * 0.4774 --internal volume available for fuel in liters, with magic realism number - Entity.EmptyMass = (Area * Wall) * 16.387 * (7.9 / 1000) -- total wall volume * cu in to cc * density of steel (kg/cc) - Entity.IsExplosive = FuelData.explosive - Entity.NoLinks = FuelData.nolinks - - if Entity.FuelType == "Electric" then - Entity.Liters = Entity.Capacity --batteries capacity is different from internal volume - Entity.Capacity = Entity.Capacity * ACF.LiIonED - end - - Entity.Fuel = Percentage * Entity.Capacity - - Entity:UpdateMass() - Entity:UpdateOverlay() - - WireLib.TriggerOutput(Entity, "Fuel", Entity.Fuel) - WireLib.TriggerOutput(Entity, "Capacity", Entity.Capacity) -end +local HookRun = hook.Run +local Wall = 0.03937 --wall thickness in inches (1mm) local function CanRefuel(Refill, Tank, Distance) + if Refill == Tank then return false end if Refill.FuelType ~= Tank.FuelType then return false end if Tank.Disabled then return false end if Tank.SupplyFuel then return false end @@ -63,96 +27,217 @@ local function CanRefuel(Refill, Tank, Distance) return Distance <= RefillDist end -local Inputs = { - Active = function(Entity, Value) - Entity.Active = tobool(Value) +--===============================================================================================-- - WireLib.TriggerOutput(Entity, "Activated", Entity:CanConsume() and 1 or 0) - end, - ["Refuel Duty"] = function(Entity, Value) - Entity.SupplyFuel = tobool(Value) or nil - end -} +do -- Spawn and Update functions + local function VerifyData(Data) + if not Data.FuelTank then + Data.FuelTank = Data.SizeId or Data.Id or "Jerry_Can" + end ---===============================================================================================-- + local Class = ACF.GetClassGroup(FuelTanks, Data.FuelTank) -function MakeACF_FuelTank(Owner, Pos, Angle, Id, Data1, Data2, Data) - if not Owner:CheckLimit("_acf_misc") then return end + if not Class then + Data.FuelTank = "Jerry_Can" - local FuelData = ACF.Weapons.FuelTanks[Data1] + Class = ACF.GetClassGroup(FuelTanks, "Jerry_Can") + end - if not FuelData then return end + -- Making sure to provide a valid fuel type + if not (Data.FuelType and FuelTypes[Data.FuelType]) then + Data.FuelType = "Petrol" + end - local Tank = ents.Create("acf_fueltank") + do -- External verifications + if Class.VerifyData then + Class.VerifyData(Data, Class) + end - if not IsValid(Tank) then return end + HookRun("ACF_VerifyData", "acf_fueltank", Data, Class) + end + end - Tank:SetModel(FuelData.model) - Tank:SetPlayer(Owner) - Tank:SetAngles(Angle) - Tank:SetPos(Pos) - Tank:Spawn() + local function UpdateFuelTank(Entity, Data, Class, FuelTank) + local FuelData = FuelTypes[Data.FuelType] + local Percentage = 1 - Tank:PhysicsInit(SOLID_VPHYSICS) - Tank:SetMoveType(MOVETYPE_VPHYSICS) + Entity:SetModel(FuelTank.Model) - Owner:AddCount("_acf_misc", Tank) - Owner:AddCleanup("acfmenu", Tank) + Entity:PhysicsInit(SOLID_VPHYSICS) + Entity:SetMoveType(MOVETYPE_VPHYSICS) + + local PhysObj = Entity:GetPhysicsObject() + local Area = PhysObj:GetSurfaceArea() + + -- Storing all the relevant information on the entity for duping + for _, V in ipairs(Entity.DataStore) do + Entity[V] = Data[V] + end - Tank.Owner = Owner - Tank.Engines = {} - Tank.Leaking = 0 - Tank.CanUpdate = true - Tank.LastThink = 0 - Tank.Inputs = WireLib.CreateInputs(Tank, { "Active", "Refuel Duty" }) - Tank.Outputs = WireLib.CreateOutputs(Tank, { "Activated", "Fuel", "Capacity", "Leaking", "Entity [ENTITY]" }) - Tank.HitBoxes = { + -- If updating, keep the same fuel level + if Entity.Capacity then + Percentage = Entity.Fuel / Entity.Capacity + end + + Entity.Name = FuelTank.Name + Entity.ShortName = Entity.FuelTank + Entity.EntType = Class.Name + Entity.ClassData = Class + Entity.FuelDensity = FuelData.Density + Entity.Volume = PhysObj:GetVolume() - (Area * Wall) -- total volume of tank (cu in), reduced by wall thickness + Entity.Capacity = Entity.Volume * ACF.CuIToLiter * ACF.TankVolumeMul * 0.4774 --internal volume available for fuel in liters, with magic realism number + Entity.EmptyMass = (Area * Wall) * 16.387 * (7.9 / 1000) -- total wall volume * cu in to cc * density of steel (kg/cc) + Entity.IsExplosive = FuelTank.IsExplosive + Entity.NoLinks = FuelTank.Unlinkable + Entity.HitBoxes = { Main = { - Pos = Tank:OBBCenter(), - Scale = (Tank:OBBMaxs() - Tank:OBBMins()) - Vector(0.5, 0.5, 0.5), + Pos = Entity:OBBCenter(), + Scale = (Entity:OBBMaxs() - Entity:OBBMins()) - Vector(0.5, 0.5, 0.5), } } - UpdateFuelData(Tank, Id, Data1, Data2, FuelData) + Entity:SetNWString("WireName", "ACF " .. Entity.Name) - WireLib.TriggerOutput(Tank, "Entity", Tank) + if Entity.FuelType == "Electric" then + Entity.Liters = Entity.Capacity --batteries capacity is different from internal volume + Entity.Capacity = Entity.Capacity * ACF.LiIonED + end - -- Fuel tanks should be active by default - Tank:TriggerInput("Active", 1) + Entity.Fuel = Percentage * Entity.Capacity - ACF.FuelTanks[Tank] = true + ACF.Activate(Entity, true) - local PhysObj = Tank:GetPhysicsObject() - local Fuel = Tank.FuelType == "Electric" and Tank.Liters or Tank.Fuel - local Mass = math.floor(Tank.EmptyMass + Fuel * Tank.FuelDensity) + Entity.ACF.Model = FuelTank.Model - if IsValid(PhysObj) then - PhysObj:SetMass(Mass) + Entity:UpdateMass(true) - Tank.Mass = Mass + WireLib.TriggerOutput(Entity, "Fuel", Entity.Fuel) + WireLib.TriggerOutput(Entity, "Capacity", Entity.Capacity) end - ACF_Activate(Tank) + function MakeACF_FuelTank(Player, Pos, Angle, Data) + VerifyData(Data) + + local Class = ACF.GetClassGroup(FuelTanks, Data.FuelTank) + local FuelTank = Class.Lookup[Data.FuelTank] + local Limit = Class.LimitConVar.Name + + if not Player:CheckLimit(Limit) then return end + + local Tank = ents.Create("acf_fueltank") - Tank.ACF.LegalMass = Tank.Mass - Tank.ACF.Model = Tank.Model + if not IsValid(Tank) then return end - do -- Mass entity mod removal - local EntMods = Data and Data.EntityMods + Tank:SetPlayer(Player) + Tank:SetAngles(Angle) + Tank:SetPos(Pos) + Tank:Spawn() - if EntMods and EntMods.mass then - EntMods.mass = nil + Player:AddCleanup("acf_fueltank", Tank) + Player:AddCount(Limit, Tank) + + Tank.Owner = Player -- MUST be stored on ent for PP + Tank.Engines = {} + Tank.Leaking = 0 + Tank.LastThink = 0 + Tank.Inputs = WireLib.CreateInputs(Tank, { "Active", "Refuel Duty" }) + Tank.Outputs = WireLib.CreateOutputs(Tank, { "Activated", "Fuel", "Capacity", "Leaking", "Entity [ENTITY]" }) + Tank.DataStore = ACF.GetEntityArguments("acf_fueltank") + + WireLib.TriggerOutput(Tank, "Entity", Tank) + + UpdateFuelTank(Tank, Data, Class, FuelTank) + + if Class.OnSpawn then + Class.OnSpawn(Tank, Data, Class, FuelTank) end + + HookRun("ACF_OnEntitySpawn", "acf_fueltank", Tank, Data, Class, FuelTank) + + Tank:UpdateOverlay(true) + + do -- Mass entity mod removal + local EntMods = Data and Data.EntityMods + + if EntMods and EntMods.mass then + EntMods.mass = nil + end + end + + -- Fuel tanks should be active by default + Tank:TriggerInput("Active", 1) + + ActiveTanks[Tank] = true + + CheckLegal(Tank) + + return Tank end - CheckLegal(Tank) + ACF.RegisterEntityClass("acf_fueltank", MakeACF_FuelTank, "FuelTank", "FuelType") + ACF.RegisterLinkSource("acf_fueltank", "Engines") - return Tank -end + ------------------- Updating --------------------- + + function ENT:Update(Data) + VerifyData(Data) + + local Class = ACF.GetClassGroup(FuelTanks, Data.FuelTank) + local FuelTank = Class.Lookup[Data.FuelTank] + local OldClass = self.ClassData + local Feedback = "" + + if OldClass.OnLast then + OldClass.OnLast(self, OldClass) + end + + HookRun("ACF_OnEntityLast", "acf_fueltank", self, OldClass) + + ACF.SaveEntity(self) + + UpdateFuelTank(self, Data, Class, FuelTank) + + ACF.RestoreEntity(self) + + if Class.OnUpdate then + Class.OnUpdate(self, Data, Class, FuelTank) + end + + HookRun("ACF_OnEntityUpdate", "acf_fueltank", self, Data, Class, FuelTank) -list.Set("ACFCvars", "acf_fueltank", {"id", "data1", "data2"}) -duplicator.RegisterEntityClass("acf_fueltank", MakeACF_FuelTank, "Pos", "Angle", "Id", "SizeId", "FuelType", "Data") -ACF.RegisterLinkSource("acf_fueltank", "Engines") + if next(self.Engines) then + local FuelType = self.FuelType + local NoLinks = self.NoLinks + local Count, Total = 0, 0 + + for Engine in pairs(self.Engines) do + if NoLinks or not Engine.FuelTypes[FuelType] then + self:Unlink(Engine) + + Count = Count + 1 + end + + Total = Total + 1 + end + + if Count == Total then + Feedback = "\nUnlinked from all engines due to fuel type or model change." + elseif Count > 0 then + local Text = "\nUnlinked from %s out of %s engines due to fuel type or model change." + + Feedback = Text:format(Count, Total) + end + end + + self:UpdateOverlay(true) + + net.Start("ACF_UpdateEntity") + net.WriteEntity(self) + net.Broadcast() + + return true, "Fuel tank updated successfully!" .. Feedback + end +end --===============================================================================================-- -- Meta Funcs @@ -181,22 +266,18 @@ function ENT:ACF_Activate(Recalc) self.ACF.MaxHealth = Health self.ACF.Armour = Armour * (0.5 + Percent / 2) self.ACF.MaxArmour = Armour - self.ACF.Mass = self.Mass - self.ACF.Density = (PhysObj:GetMass() * 1000) / self.ACF.Volume self.ACF.Type = "Prop" end -function ENT:ACF_OnDamage(Entity, Energy, FrArea, Angle, Inflictor, _, Type) +function ENT:ACF_OnDamage(Energy, FrArea, Angle, Inflictor, _, Type) local Mul = Type == "HEAT" and ACF.HEATMulFuel or 1 --Heat penetrators deal bonus damage to fuel - local HitRes = ACF.PropDamage(Entity, Energy, FrArea * Mul, Angle, Inflictor) --Calling the standard damage prop function + local HitRes = ACF.PropDamage(self, Energy, FrArea * Mul, Angle, Inflictor) --Calling the standard damage prop function local NoExplode = self.FuelType == "Diesel" and not (Type == "HE" or Type == "HEAT") if self.Exploding or NoExplode or not self.IsExplosive then return HitRes end if HitRes.Kill then - if hook.Run("ACF_FuelExplode", self) == false then return HitRes end - - self.Exploding = true + if HookRun("ACF_FuelExplode", self) == false then return HitRes end if IsValid(Inflictor) and Inflictor:IsPlayer() then self.Inflictor = Inflictor @@ -212,10 +293,9 @@ function ENT:ACF_OnDamage(Entity, Energy, FrArea, Angle, Inflictor, _, Type) --it's gonna blow if math.random() < (ExplodeChance + Ratio) then - if hook.Run("ACF_FuelExplode", self) == false then return HitRes end + if HookRun("ACF_FuelExplode", self) == false then return HitRes end self.Inflictor = Inflictor - self.Exploding = true self:Detonate() else --spray some fuel around @@ -230,10 +310,12 @@ function ENT:ACF_OnDamage(Entity, Energy, FrArea, Angle, Inflictor, _, Type) end function ENT:Detonate() - self.Damaged = nil -- Prevent multiple explosions + if self.Exploding then return end + + self.Exploding = true -- Prevent multiple explosions local Pos = self:LocalToWorld(self:OBBCenter() + VectorRand() * (self:OBBMaxs() - self:OBBMins()) / 2) - local ExplosiveMass = (math.max(self.Fuel, self.Capacity * 0.0025) / ACF.FuelDensity[self.FuelType]) * 0.1 + local ExplosiveMass = (math.max(self.Fuel, self.Capacity * 0.0025) / self.FuelDensity) * 0.1 ACF_KillChildProps(self, Pos, ExplosiveMass) ACF_HE(Pos, ExplosiveMass, ExplosiveMass * 0.5, self.Inflictor, {self}, self) @@ -250,29 +332,6 @@ function ENT:Detonate() self:Remove() end -function ENT:Update(ArgsTable) - if ArgsTable[1] ~= self.Owner then return false, "You don't own that fuel tank!" end - - local FuelData = ACF.Weapons.FuelTanks[ArgsTable[5]] - - if not FuelData then return false, "Invalid fuel tank type!" end - if FuelData.model ~= self.Model then return false, "The new fuel tank must have the same model!" end - - local Feedback = "" - - if self.FuelType ~= ArgsTable[6] then - for Engine in pairs(self.Engines) do - self:Unlink(Engine) - end - - Feedback = " New fuel type loaded, fuel tank unlinked." - end - - UpdateFuelData(self, ArgsTable[4], ArgsTable[5], ArgsTable[6], FuelData) - - return true, "Fuel tank successfully updated." .. Feedback -end - function ENT:Enable() WireLib.TriggerOutput(self, "Activated", self:CanConsume() and 1 or 0) end @@ -281,106 +340,72 @@ function ENT:Disable() WireLib.TriggerOutput(self, "Activated", 0) end -function ENT:Link(Target) - if not IsValid(Target) then return false, "Attempted to link an invalid entity." end - if self == Target then return false, "Can't link a fuel tank to itself." end +do -- Mass Update + local function UpdateMass(Entity) + local Fuel = Entity.FuelType == "Electric" and Entity.Liters or Entity.Fuel + local Mass = math.floor(Entity.EmptyMass + Fuel * Entity.FuelDensity) + local PhysObj = Entity.ACF.PhysObj - local Function = ClassLink(self:GetClass(), Target:GetClass()) + Entity.ACF.LegalMass = Mass + Entity.ACF.Density = Mass * 1000 / Entity.ACF.Volume - if Function then - return Function(self, Target) + if IsValid(PhysObj) then + PhysObj:SetMass(Mass) + end end - return false, "Fuel tanks can't be linked to '" .. Target:GetClass() .. "'." -end + function ENT:UpdateMass(Instant) + if Instant then + return UpdateMass(self) + end -function ENT:Unlink(Target) - if not IsValid(Target) then return false, "Attempted to unlink an invalid entity." end - if self == Target then return false, "Can't unlink a fuel tank from itself." end + if TimerExists("ACF Mass Buffer" .. self:EntIndex()) then return end - local Function = ClassUnlink(self:GetClass(), Target:GetClass()) + TimerCreate("ACF Mass Buffer" .. self:EntIndex(), 1, 1, function() + if not IsValid(self) then return end - if Function then - return Function(self, Target) + UpdateMass(self) + end) end - - return false, "Fuel tanks can't be unlinked from '" .. Target:GetClass() .. "'." end -function ENT:UpdateMass() - if TimerExists("ACF Mass Buffer" .. self:EntIndex()) then return end - - TimerCreate("ACF Mass Buffer" .. self:EntIndex(), 5, 1, function() - if not IsValid(self) then return end - - local Fuel = self.FuelType == "Electric" and self.Liters or self.Fuel - local PhysObj = self.ACF.PhysObj - - self.Mass = math.floor(self.EmptyMass + Fuel * self.FuelDensity) - self.ACF.LegalMass = self.Mass - - if IsValid(PhysObj) then - PhysObj:SetMass(self.Mass) - end - end) -end +do -- Overlay Update + local Text = "%s\n\nFuel Type: %s\n%s" -local function Overlay(Ent) - if Ent.Disabled then - Ent:SetOverlayText("Disabled: " .. Ent.DisableReason .. "\n" .. Ent.DisableDescription) - else - local Text + function ENT:UpdateOverlayText() + local Status, Content - if Ent.DisableReason then - Text = "Disabled: " .. Ent.DisableReason - elseif Ent.Leaking > 0 then - Text = "Leaking" + if self.Leaking > 0 then + Status = "Leaking" else - Text = Ent:CanConsume() and "Providing Fuel" or "Idle" + Status = self:CanConsume() and "Providing Fuel" or "Idle" end - Text = Text .. "\n\nFuel Type: " .. Ent.FuelType + if self.FuelType == "Electric" then -- TODO: Replace hardcoded stuff + local KiloWatt = math.Round(self.Fuel, 1) + local Joules = math.Round(self.Fuel * 3.6, 1) - if Ent.FuelType == "Electric" then - local KiloWatt = math.Round(Ent.Fuel, 1) - local Joules = math.Round(Ent.Fuel * 3.6, 1) - - Text = Text .. "\nCharge Level: " .. KiloWatt .. " kWh / " .. Joules .. " MJ" + Content = "Charge Level: " .. KiloWatt .. " kWh / " .. Joules .. " MJ" else - local Liters = math.Round(Ent.Fuel, 1) - local Gallons = math.Round(Ent.Fuel * 0.264172, 1) + local Liters = math.Round(self.Fuel, 1) + local Gallons = math.Round(self.Fuel * 0.264172, 1) - Text = Text .. "\nFuel Remaining: " .. Liters .. " liters / " .. Gallons .. " gallons" + Content = "Fuel Remaining: " .. Liters .. " liters / " .. Gallons .. " gallons" end - Ent:SetOverlayText(Text) + return Text:format(Status, self.FuelType, Content) end end -function ENT:UpdateOverlay() - if TimerExists("ACF Overlay Buffer" .. self:EntIndex()) then -- This entity has been updated too recently - self.OverlayBuffer = true -- Mark it to update when buffer time has expired - else - TimerCreate("ACF Overlay Buffer" .. self:EntIndex(), 1, 1, function() - if IsValid(self) and self.OverlayBuffer then - self.OverlayBuffer = nil - self:UpdateOverlay() - end - end) +ACF.AddInputAction("acf_fueltank", "Active", function(Entity, Value) + Entity.Active = tobool(Value) - Overlay(self) - end -end - -function ENT:TriggerInput(Input, Value) - if self.Disabled then return end + WireLib.TriggerOutput(Entity, "Activated", Entity:CanConsume() and 1 or 0) +end) - if Inputs[Input] then - Inputs[Input](self, Value) - - self:UpdateOverlay() - end -end +ACF.AddInputAction("acf_fueltank", "Refuel Duty", function(Entity, Value) + Entity.SupplyFuel = tobool(Value) or nil +end) function ENT:CanConsume() if self.Disabled then return false end @@ -419,19 +444,19 @@ function ENT:Think() for Tank in pairs(ACF.FuelTanks) do if CanRefuel(self, Tank, Position:DistToSqr(Tank:GetPos())) then - local Exchange = math.min(DeltaTime * ACF.RefillSpeed * ACF.FuelRate / 1750, self.Fuel, Tank.Capacity - Tank.Fuel) + local Exchange = math.min(DeltaTime * ACF.RefuelSpeed * ACF.FuelRate, self.Fuel, Tank.Capacity - Tank.Fuel) - if hook.Run("ACF_CanRefuel", self, Tank, Exchange) == false then continue end + if HookRun("ACF_CanRefuel", self, Tank, Exchange) == false then continue end self:Consume(Exchange) Tank:Consume(-Exchange) if self.FuelType == "Electric" then - self:EmitSound("ambient/energy/newspark04.wav", 70, 100, 0.5 * ACF.SoundVolume) - Tank:EmitSound("ambient/energy/newspark04.wav", 70, 100, 0.5 * ACF.SoundVolume) + self:EmitSound("ambient/energy/newspark04.wav", 70, 100, 0.5 * ACF.Volume) + Tank:EmitSound("ambient/energy/newspark04.wav", 70, 100, 0.5 * ACF.Volume) else - self:EmitSound("vehicles/jetski/jetski_no_gas_start.wav", 70, 120, 0.5 * ACF.SoundVolume) - Tank:EmitSound("vehicles/jetski/jetski_no_gas_start.wav", 70, 120, 0.5 * ACF.SoundVolume) + self:EmitSound("vehicles/jetski/jetski_no_gas_start.wav", 70, 120, 0.5 * ACF.Volume) + Tank:EmitSound("vehicles/jetski/jetski_no_gas_start.wav", 70, 120, 0.5 * ACF.Volume) end end end @@ -443,11 +468,19 @@ function ENT:Think() end function ENT:OnRemove() + local Class = self.ClassData + + if Class.OnLast then + Class.OnLast(self, Class) + end + + HookRun("ACF_OnEntityLast", "acf_fueltank", self, Class) + for Engine in pairs(self.Engines) do self:Unlink(Engine) end - ACF.FuelTanks[self] = nil + ActiveTanks[self] = nil WireLib.Remove(self) end diff --git a/lua/entities/acf_fueltank/shared.lua b/lua/entities/acf_fueltank/shared.lua index ae6896b58..42d3e1d18 100644 --- a/lua/entities/acf_fueltank/shared.lua +++ b/lua/entities/acf_fueltank/shared.lua @@ -1,4 +1,8 @@ -DEFINE_BASECLASS("base_wire_entity") +DEFINE_BASECLASS("acf_base_simple") -ENT.PrintName = "ACF Fuel Tank" -ENT.WireDebugName = "ACF Fuel Tank" \ No newline at end of file +ENT.PrintName = "ACF Fuel Tank" +ENT.WireDebugName = "ACF Fuel Tank" +ENT.PluralName = "ACF Fuel Tanks" +ENT.IsFuelTank = true + +cleanup.Register("acf_fueltank") diff --git a/lua/entities/acf_gearbox/cl_init.lua b/lua/entities/acf_gearbox/cl_init.lua index 9265b5e5b..1de0c1e1c 100644 --- a/lua/entities/acf_gearbox/cl_init.lua +++ b/lua/entities/acf_gearbox/cl_init.lua @@ -1,249 +1,9 @@ include("shared.lua") -local HideInfo = ACF.HideInfoBubble +language.Add("Cleanup_acf_gearbox", "ACF Gearboxes") +language.Add("Cleaned_acf_gearbox", "Cleaned up all ACF Gearboxes") +language.Add("SBoxLimit__acf_gearbox", "You've reached the ACF Gearboxes limit!") -function ENT:Initialize() +function ENT:Update() self.HitBoxes = ACF.HitBoxes[self:GetModel()] end - --- copied from base_wire_entity: DoNormalDraw's notip arg isn't accessible from ENT:Draw defined there. -function ENT:Draw() - self:DoNormalDraw(false, HideInfo()) - - Wire_Render(self) - - if self.GetBeamLength and (not self.GetShowBeam or self:GetShowBeam()) then - -- Every SENT that has GetBeamLength should draw a tracer. Some of them have the GetShowBeam boolean - Wire_DrawTracerBeam(self, 1, self.GetBeamHighlight and self:GetBeamHighlight() or false) - end -end - -function ACFGearboxGUICreate(Table) - if not acfmenupanel.Serialize then - acfmenupanel.Serialize = function(tbl, factor) - local str = "" - - for i = 1, 7 do - str = str .. math.Round(tbl[i] * factor, 1) .. "," - end - - RunConsoleCommand("acfmenu_data9", str) - end - end - - if not acfmenupanel.GearboxData then - acfmenupanel.GearboxData = {} - end - - if not acfmenupanel.GearboxData[Table.id] then - acfmenupanel.GearboxData[Table.id] = {} - acfmenupanel.GearboxData[Table.id].GearTable = Table.geartable - end - - if Table.auto and not acfmenupanel.GearboxData[Table.id].ShiftTable then - acfmenupanel.GearboxData[Table.id].ShiftTable = {10, 20, 30, 40, 50, 60, 70} - end - - acfmenupanel:CPanelText("Name", Table.name) - acfmenupanel.CData.DisplayModel = vgui.Create("DModelPanel", acfmenupanel.CustomDisplay) - acfmenupanel.CData.DisplayModel:SetModel(Table.model) - acfmenupanel.CData.DisplayModel:SetCamPos(Vector(250, 500, 250)) - acfmenupanel.CData.DisplayModel:SetLookAt(Vector(0, 0, 0)) - acfmenupanel.CData.DisplayModel:SetFOV(20) - acfmenupanel.CData.DisplayModel:SetSize(acfmenupanel:GetWide(), acfmenupanel:GetWide()) - acfmenupanel.CData.DisplayModel.LayoutEntity = function() end - acfmenupanel.CustomDisplay:AddItem(acfmenupanel.CData.DisplayModel) - acfmenupanel:CPanelText("Desc", Table.desc) --Description (Name, Desc) - - if Table.auto and not acfmenupanel.CData.UnitsInput then - acfmenupanel.CData.UnitsInput = vgui.Create("DComboBox", acfmenupanel.CustomDisplay) - acfmenupanel.CData.UnitsInput.ID = Table.id - acfmenupanel.CData.UnitsInput.Gears = Table.gears - acfmenupanel.CData.UnitsInput:SetSize(60, 22) - acfmenupanel.CData.UnitsInput:SetTooltip("If using the shift point generator, recalc after changing units.") - acfmenupanel.CData.UnitsInput:AddChoice("KPH", 10.936, true) - acfmenupanel.CData.UnitsInput:AddChoice("MPH", 17.6) - acfmenupanel.CData.UnitsInput:AddChoice("GMU", 1) - acfmenupanel.CData.UnitsInput:SetDark(true) - - acfmenupanel.CData.UnitsInput.OnSelect = function(panel, _, _, data) - acfmenupanel.Serialize(acfmenupanel.GearboxData[panel.ID].ShiftTable, data) --dot intentional - end - - acfmenupanel.CustomDisplay:AddItem(acfmenupanel.CData.UnitsInput) - end - - if Table.cvt then - ACF_GearsSlider(2, acfmenupanel.GearboxData[Table.id].GearTable[2], Table.id) - ACF_GearsSlider(3, acfmenupanel.GearboxData[Table.id].GearTable[-3], Table.id, "Min Target RPM", true) - ACF_GearsSlider(4, acfmenupanel.GearboxData[Table.id].GearTable[-2], Table.id, "Max Target RPM", true) - ACF_GearsSlider(10, acfmenupanel.GearboxData[Table.id].GearTable[-1], Table.id, "Final Drive") - RunConsoleCommand("acfmenu_data1", 0.01) - else - for ID, Value in pairs(acfmenupanel.GearboxData[Table.id].GearTable) do - if ID > 0 and not (Table.auto and ID == 8) then - ACF_GearsSlider(ID, Value, Table.id) - - if Table.auto then - ACF_ShiftPoint(ID, acfmenupanel.GearboxData[Table.id].ShiftTable[ID], Table.id, "Gear " .. ID .. " upshift speed: ") - end - elseif Table.auto and (ID == -2 or ID == 8) then - ACF_GearsSlider(8, Value, Table.id, "Reverse") - elseif ID == -1 then - ACF_GearsSlider(10, Value, Table.id, "Final Drive") - end - end - end - - acfmenupanel:CPanelText("Desc", Table.desc) - acfmenupanel:CPanelText("MaxTorque", "Clutch Maximum Torque Rating : " .. Table.maxtq .. "n-m / " .. math.Round(Table.maxtq * 0.73) .. "ft-lb") - acfmenupanel:CPanelText("Weight", "Weight : " .. Table.weight .. "kg") - - if Table.parentable then - acfmenupanel:CPanelText("Parentable", "\nThis gearbox can be parented without welding.") - end - - if Table.auto then - acfmenupanel:CPanelText("ShiftPointGen", "\nShift Point Generator:") - - if not acfmenupanel.CData.ShiftGenPanel then - acfmenupanel.CData.ShiftGenPanel = vgui.Create("DPanel") - acfmenupanel.CData.ShiftGenPanel:SetPaintBackground(false) - acfmenupanel.CData.ShiftGenPanel:DockPadding(4, 0, 4, 0) - acfmenupanel.CData.ShiftGenPanel:SetTall(60) - acfmenupanel.CData.ShiftGenPanel:SizeToContentsX() - acfmenupanel.CData.ShiftGenPanel.Gears = Table.gears - acfmenupanel.CData.ShiftGenPanel.Calc = acfmenupanel.CData.ShiftGenPanel:Add("DButton") - acfmenupanel.CData.ShiftGenPanel.Calc:SetText("Calculate") - acfmenupanel.CData.ShiftGenPanel.Calc:Dock(BOTTOM) - --acfmenupanel.CData.ShiftGenPanel.Calc:SetWide( 80 ) - acfmenupanel.CData.ShiftGenPanel.Calc:SetTall(20) - - acfmenupanel.CData.ShiftGenPanel.Calc.DoClick = function() - local _, factor = acfmenupanel.CData.UnitsInput:GetSelected() - local mul = math.pi * acfmenupanel.CData.ShiftGenPanel.RPM:GetValue() * acfmenupanel.CData.ShiftGenPanel.Ratio:GetValue() * acfmenupanel.CData[10]:GetValue() * acfmenupanel.CData.ShiftGenPanel.Wheel:GetValue() / (60 * factor) - - for i = 1, acfmenupanel.CData.ShiftGenPanel.Gears do - acfmenupanel.CData[10 + i].Input:SetValue(math.Round(math.abs(mul * acfmenupanel.CData[i]:GetValue()), 2)) - acfmenupanel.GearboxData[acfmenupanel.CData.UnitsInput.ID].ShiftTable[i] = tonumber(acfmenupanel.CData[10 + i].Input:GetValue()) - end - - acfmenupanel.Serialize(acfmenupanel.GearboxData[acfmenupanel.CData.UnitsInput.ID].ShiftTable, factor) --dot intentional - end - - acfmenupanel.CData.WheelPanel = acfmenupanel.CData.ShiftGenPanel:Add("DPanel") - acfmenupanel.CData.WheelPanel:SetPaintBackground(false) - acfmenupanel.CData.WheelPanel:DockMargin(4, 0, 4, 0) - acfmenupanel.CData.WheelPanel:Dock(RIGHT) - acfmenupanel.CData.WheelPanel:SetWide(76) - acfmenupanel.CData.WheelPanel:SetTooltip("If you use default spherical settings, add 0.5 to your wheel diameter.\nFor treaded vehicles, use the diameter of road wheels, not drive wheels.") - acfmenupanel.CData.ShiftGenPanel.WheelLabel = acfmenupanel.CData.WheelPanel:Add("DLabel") - acfmenupanel.CData.ShiftGenPanel.WheelLabel:Dock(TOP) - acfmenupanel.CData.ShiftGenPanel.WheelLabel:SetDark(true) - acfmenupanel.CData.ShiftGenPanel.WheelLabel:SetText("Wheel Diameter:") - acfmenupanel.CData.ShiftGenPanel.Wheel = acfmenupanel.CData.WheelPanel:Add("DNumberWang") - acfmenupanel.CData.ShiftGenPanel.Wheel:HideWang() - acfmenupanel.CData.ShiftGenPanel.Wheel:SetDrawBorder(false) - acfmenupanel.CData.ShiftGenPanel.Wheel:Dock(BOTTOM) - acfmenupanel.CData.ShiftGenPanel.Wheel:SetDecimals(2) - acfmenupanel.CData.ShiftGenPanel.Wheel:SetMinMax(0, 9999) - acfmenupanel.CData.ShiftGenPanel.Wheel:SetValue(30) - acfmenupanel.CData.RatioPanel = acfmenupanel.CData.ShiftGenPanel:Add("DPanel") - acfmenupanel.CData.RatioPanel:SetPaintBackground(false) - acfmenupanel.CData.RatioPanel:DockMargin(4, 0, 4, 0) - acfmenupanel.CData.RatioPanel:Dock(RIGHT) - acfmenupanel.CData.RatioPanel:SetWide(76) - acfmenupanel.CData.RatioPanel:SetTooltip("Total ratio is the ratio of all gearboxes (exluding this one) multiplied together.\nFor example, if you use engine to automatic to diffs to wheels, your total ratio would be (diff gear ratio * diff final ratio).") - acfmenupanel.CData.ShiftGenPanel.RatioLabel = acfmenupanel.CData.RatioPanel:Add("DLabel") - acfmenupanel.CData.ShiftGenPanel.RatioLabel:Dock(TOP) - acfmenupanel.CData.ShiftGenPanel.RatioLabel:SetDark(true) - acfmenupanel.CData.ShiftGenPanel.RatioLabel:SetText("Total ratio:") - acfmenupanel.CData.ShiftGenPanel.Ratio = acfmenupanel.CData.RatioPanel:Add("DNumberWang") - acfmenupanel.CData.ShiftGenPanel.Ratio:HideWang() - acfmenupanel.CData.ShiftGenPanel.Ratio:SetDrawBorder(false) - acfmenupanel.CData.ShiftGenPanel.Ratio:Dock(BOTTOM) - acfmenupanel.CData.ShiftGenPanel.Ratio:SetDecimals(2) - acfmenupanel.CData.ShiftGenPanel.Ratio:SetMinMax(0, 9999) - acfmenupanel.CData.ShiftGenPanel.Ratio:SetValue(0.1) - acfmenupanel.CData.RPMPanel = acfmenupanel.CData.ShiftGenPanel:Add("DPanel") - acfmenupanel.CData.RPMPanel:SetPaintBackground(false) - acfmenupanel.CData.RPMPanel:DockMargin(4, 0, 4, 0) - acfmenupanel.CData.RPMPanel:Dock(RIGHT) - acfmenupanel.CData.RPMPanel:SetWide(76) - acfmenupanel.CData.RPMPanel:SetTooltip("Target engine RPM to upshift at.") - acfmenupanel.CData.ShiftGenPanel.RPMLabel = acfmenupanel.CData.RPMPanel:Add("DLabel") - acfmenupanel.CData.ShiftGenPanel.RPMLabel:Dock(TOP) - acfmenupanel.CData.ShiftGenPanel.RPMLabel:SetDark(true) - acfmenupanel.CData.ShiftGenPanel.RPMLabel:SetText("Upshift RPM:") - acfmenupanel.CData.ShiftGenPanel.RPM = acfmenupanel.CData.RPMPanel:Add("DNumberWang") - acfmenupanel.CData.ShiftGenPanel.RPM:HideWang() - acfmenupanel.CData.ShiftGenPanel.RPM:SetDrawBorder(false) - acfmenupanel.CData.ShiftGenPanel.RPM:Dock(BOTTOM) - acfmenupanel.CData.ShiftGenPanel.RPM:SetDecimals(2) - acfmenupanel.CData.ShiftGenPanel.RPM:SetMinMax(0, 9999) - acfmenupanel.CData.ShiftGenPanel.RPM:SetValue(5000) - acfmenupanel.CustomDisplay:AddItem(acfmenupanel.CData.ShiftGenPanel) - end - end - - acfmenupanel.CustomDisplay:PerformLayout() - maxtorque = Table.maxtq -end - -function ACF_GearsSlider(Gear, Value, ID, Desc, CVT) - if Gear and not acfmenupanel.CData[Gear] then - acfmenupanel.CData[Gear] = vgui.Create("DNumSlider", acfmenupanel.CustomDisplay) - acfmenupanel.CData[Gear]:SetText(Desc or "Gear " .. Gear) - acfmenupanel.CData[Gear].Label:SizeToContents() - acfmenupanel.CData[Gear]:SetDark(true) - acfmenupanel.CData[Gear]:SetMin(CVT and 1 or -1) - acfmenupanel.CData[Gear]:SetMax(CVT and 10000 or 1) - acfmenupanel.CData[Gear]:SetDecimals((not CVT) and 2 or 0) - acfmenupanel.CData[Gear].Gear = Gear - acfmenupanel.CData[Gear].ID = ID - acfmenupanel.CData[Gear]:SetValue(Value) - RunConsoleCommand("acfmenu_data" .. Gear, Value) - - acfmenupanel.CData[Gear].OnValueChanged = function(slider, val) - acfmenupanel.GearboxData[slider.ID].GearTable[slider.Gear] = val - RunConsoleCommand("acfmenu_data" .. Gear, val) - end - - acfmenupanel.CustomDisplay:AddItem(acfmenupanel.CData[Gear]) - end -end - -function ACF_ShiftPoint(Gear, Value, ID, Desc) - local Index = Gear + 10 - - if Gear and not acfmenupanel.CData[Index] then - acfmenupanel.CData[Index] = vgui.Create("DPanel") - acfmenupanel.CData[Index]:SetPaintBackground(false) - acfmenupanel.CData[Index]:SetTall(20) - acfmenupanel.CData[Index]:SizeToContentsX() - acfmenupanel.CData[Index].Input = acfmenupanel.CData[Index]:Add("DNumberWang") - acfmenupanel.CData[Index].Input.Gear = Gear - acfmenupanel.CData[Index].Input.ID = ID - acfmenupanel.CData[Index].Input:HideWang() - acfmenupanel.CData[Index].Input:SetDrawBorder(false) - acfmenupanel.CData[Index].Input:SetDecimals(2) - acfmenupanel.CData[Index].Input:SetMinMax(0, 9999) - acfmenupanel.CData[Index].Input:SetValue(Value) - acfmenupanel.CData[Index].Input:Dock(RIGHT) - acfmenupanel.CData[Index].Input:SetWide(45) - - acfmenupanel.CData[Index].Input.OnValueChanged = function(box, value) - acfmenupanel.GearboxData[box.ID].ShiftTable[box.Gear] = value - local _, factor = acfmenupanel.CData.UnitsInput:GetSelected() - acfmenupanel.Serialize(acfmenupanel.GearboxData[acfmenupanel.CData.UnitsInput.ID].ShiftTable, factor) --dot intentional - end - - RunConsoleCommand("acfmenu_data9", "10,20,30,40,50,60,70") - acfmenupanel.CData[Index].Label = acfmenupanel.CData[Index]:Add("DLabel") - acfmenupanel.CData[Index].Label:Dock(RIGHT) - acfmenupanel.CData[Index].Label:SetWide(120) - acfmenupanel.CData[Index].Label:SetDark(true) - acfmenupanel.CData[Index].Label:SetText(Desc) - acfmenupanel.CustomDisplay:AddItem(acfmenupanel.CData[Index]) - end -end diff --git a/lua/entities/acf_gearbox/init.lua b/lua/entities/acf_gearbox/init.lua index e8272c6e5..97ce866ea 100644 --- a/lua/entities/acf_gearbox/init.lua +++ b/lua/entities/acf_gearbox/init.lua @@ -7,9 +7,6 @@ include("shared.lua") -- Local Funcs and Vars --===============================================================================================-- -local TimerCreate = timer.Create -local TimerExists = timer.Exists - local function CheckLoopedGearbox(This, Target) local Queued = { [Target] = true } local Checked = {} @@ -150,197 +147,16 @@ ACF.RegisterClassUnlink("acf_gearbox", "acf_gearbox", UnlinkGearbox) ACF.RegisterClassUnlink("acf_gearbox", "tire", UnlinkWheel) local CheckLegal = ACF_CheckLegal -local ClassLink = ACF.GetClassLink -local ClassUnlink = ACF.GetClassUnlink -local Clamp = math.Clamp - -local function CreateInputsOutputs(Gearbox) - local Inputs = { "Gear", "Gear Up", "Gear Down" } - - if Gearbox.CVT then - Inputs[#Inputs + 1] = "CVT Ratio" - elseif Gearbox.DoubleDiff then - Inputs[#Inputs + 1] = "Steer Rate" - elseif Gearbox.Auto then - Inputs[#Inputs + 1] = "Hold Gear" - Inputs[#Inputs + 1] = "Shift Speed Scale" - - Gearbox.Hold = false - end - - if Gearbox.Dual then - Inputs[#Inputs + 1] = "Left Clutch" - Inputs[#Inputs + 1] = "Right Clutch" - Inputs[#Inputs + 1] = "Left Brake" - Inputs[#Inputs + 1] = "Right Brake" - else - Inputs[#Inputs + 1] = "Clutch" - Inputs[#Inputs + 1] = "Brake" - end - - local Outputs = { "Ratio", "Entity [ENTITY]", "Current Gear" } - - if Gearbox.CVT then - Outputs[#Outputs + 1] = "Min Target RPM" - Outputs[#Outputs + 1] = "Max Target RPM" - end - - Gearbox.Inputs = WireLib.CreateInputs(Gearbox, Inputs) - Gearbox.Outputs = WireLib.CreateOutputs(Gearbox, Outputs) - - WireLib.TriggerOutput(Gearbox, "Entity", Gearbox) -end - -local function ChangeGear(Entity, Value) - Value = Clamp(math.floor(Value), 0, Entity.Reverse or Entity.Gears) - - if Entity.Gear == Value then return end - - Entity.Gear = Value - Entity.GearRatio = Entity.GearTable[Value] * Entity.GearTable.Final - Entity.ChangeFinished = ACF.CurTime + Entity.SwitchTime - Entity.InGear = false - - Entity:EmitSound("buttons/lever7.wav", 70, 100, ACF.SoundVolume) - Entity:UpdateOverlay() - - WireLib.TriggerOutput(Entity, "Current Gear", Value) - WireLib.TriggerOutput(Entity, "Ratio", Entity.GearRatio) -end - ---handles gearing for automatics; 0=neutral, 1=forward autogearing, 2=reverse -local function ChangeDrive(Entity, Value) - Value = Clamp(math.floor(Value), 0, 2) - - if Entity.Drive == Value then return end - - Entity.Drive = Value - - ChangeGear(Entity, Entity.Drive == 2 and Entity.Reverse or Entity.Drive) -end - -local function UpdateGearboxData(Entity, GearboxData, Id, Data1, Data2, Data3, Data4, Data5, Data6, Data7, Data8, Data9, Data10, Data) - if Entity.Id ~= Id then - Entity.Id = Id - Entity.Name = GearboxData.name - Entity.ShortName = Entity.Id - Entity.EntType = GearboxData.category - Entity.Model = GearboxData.model - Entity.Mass = GearboxData.weight - Entity.SwitchTime = GearboxData.switch - Entity.MaxTorque = GearboxData.maxtq - Entity.Gears = GearboxData.gears - Entity.Dual = GearboxData.doubleclutch - Entity.CVT = GearboxData.cvt - Entity.DoubleDiff = GearboxData.doublediff - Entity.Auto = GearboxData.auto - Entity.LClutch = 1 - Entity.RClutch = 1 - Entity.MainClutch = 1 - Entity.LastBrakeThink = 0 - Entity.Braking = false - - Entity.HitBoxes = ACF.HitBoxes[GearboxData.model] - - CreateInputsOutputs(Entity) - - local PhysObj = Entity:GetPhysicsObject() - - if IsValid(PhysObj) then - PhysObj:SetMass(Entity.Mass) - end - end - - Entity.GearTable = {} - - for K, V in pairs(GearboxData.geartable) do - Entity.GearTable[K] = V - end - - Entity.GearTable[1] = Data1 - Entity.GearTable[2] = Data2 - Entity.GearTable[3] = Data3 - Entity.GearTable[4] = Data4 - Entity.GearTable[5] = Data5 - Entity.GearTable[6] = Data6 - Entity.GearTable[7] = Data7 - Entity.GearTable[8] = Data8 - Entity.GearTable[9] = Data9 - Entity.GearTable.Final = Data10 - - Entity.Gear0 = Data10 - Entity.Gear1 = Data1 - Entity.Gear2 = Data2 - Entity.Gear3 = Data3 - Entity.Gear4 = Data4 - Entity.Gear5 = Data5 - Entity.Gear6 = Data6 - Entity.Gear7 = Data7 - Entity.Gear8 = Data8 - Entity.Gear9 = Data9 - Entity.GearRatio = Entity.GearTable[0] * Entity.GearTable.Final - - if Entity.CVT then - Entity.TargetMinRPM = Data3 - Entity.TargetMaxRPM = math.max(Data4, Data3 + 100) - Entity.CVTRatio = 0 - - WireLib.TriggerOutput(Entity, "Min Target RPM", Entity.TargetMinRPM) - WireLib.TriggerOutput(Entity, "Max Target RPM", Entity.TargetMaxRPM) - - elseif Entity.Auto then - Entity.ShiftPoints = {} - - for part in string.gmatch(Data9, "[^,]+") do - Entity.ShiftPoints[#Entity.ShiftPoints + 1] = tonumber(part) - end - - Entity.ShiftPoints[0] = -1 - Entity.Reverse = Entity.Gears + 1 - Entity.GearTable[Entity.Reverse] = Data8 - Entity.ShiftScale = 1 - end - - if Entity.Dual or Entity.DoubleDiff then - Entity:SetBodygroup(1, 1) - else - Entity:SetBodygroup(1, 0) - end - - -- Force gearboxes to forget their gear and drive - Entity.Drive = nil - Entity.Gear = nil - - if Entity.Auto then - ChangeDrive(Entity, 1) - else - ChangeGear(Entity, 1) - end - - Entity:SetNWString("WireName", "ACF " .. Entity.Name) - - ACF_Activate(Entity, true) - - Entity.ACF.LegalMass = Entity.Mass - Entity.ACF.Model = Entity.Model - - do -- Mass entity mod removal - local EntMods = Data and Data.EntityMods - - if EntMods and EntMods.mass then - EntMods.mass = nil - end - end - - Entity:UpdateOverlay(true) -end +local Gearboxes = ACF.Classes.Gearboxes +local Clamp = math.Clamp +local HookRun = hook.Run local function CheckRopes(Entity, Target) if not next(Entity[Target]) then return end for Ent, Link in pairs(Entity[Target]) do local OutPos = Entity:LocalToWorld(Link.Output) - local InPos = Ent.IsGeartrain and Ent:LocalToWorld(Ent.In) or Ent:GetPos() + local InPos = Ent.In and Ent:LocalToWorld(Ent.In) or Ent:GetPos() -- make sure it is not stretched too far if OutPos:Distance(InPos) > Link.RopeLen * 1.5 then @@ -406,147 +222,391 @@ local function SetCanApplyBrakes(Gearbox) Gearbox:ApplyBrakes() end - end -local Inputs = { - Gear = function(Entity, Value) - if Entity.Auto then - ChangeDrive(Entity, Value) - else - ChangeGear(Entity, Value) +--===============================================================================================-- + +do -- Spawn and Update functions + local function VerifyData(Data) + if not Data.Gearbox then + Data.Gearbox = Data.Id or "2Gear-T-S" + end + + local Class = ACF.GetClassGroup(Gearboxes, Data.Gearbox) + + if not Class then + Data.Gearbox = "2Gear-T-S" + + Class = ACF.GetClassGroup(Gearboxes, "2Gear-T-S") + end + + do -- Gears table verification + local Gears = Data.Gears + + if not istable(Gears) then + Gears = { [0] = 0 } + + Data.Gears = Gears + else + Gears[0] = 0 + end + + for I = 1, Class.Gears.Max do + local Gear = ACF.CheckNumber(Gears[I]) + + if not Gear then + Gear = ACF.CheckNumber(Data["Gear" .. I], I * 0.1) + + Data["Gear" .. I] = nil + end + + Gears[I] = Clamp(Gear, -1, 1) + end end - end, - ["Gear Up"] = function(Entity, Value) - if Value == 0 then return end - if Entity.Auto then - ChangeDrive(Entity, Entity.Drive + 1) + do -- Final drive verification + local Final = ACF.CheckNumber(Data.FinalDrive) + + if not Final then + Final = ACF.CheckNumber(Data.Gear0, 1) + + Data.Gear0 = nil + end + + Data.FinalDrive = Clamp(Final, -1, 1) + end + + do -- External verifications + if Class.VerifyData then + Class.VerifyData(Data, Class) + end + + HookRun("ACF_VerifyData", "acf_gearbox", Data, Class) + end + end + + local function CreateInputs(Entity, Data, Class, Gearbox) + local List = { "Gear", "Gear Up", "Gear Down" } + + if Class.SetupInputs then + Class.SetupInputs(List, Entity, Data, Class, Gearbox) + end + + HookRun("ACF_OnSetupInputs", "acf_gearbox", List, Entity, Data, Class, Gearbox) + + if Entity.Inputs then + Entity.Inputs = WireLib.AdjustInputs(Entity, List) else - ChangeGear(Entity, Entity.Gear + 1) + Entity.Inputs = WireLib.CreateInputs(Entity, List) end - end, - ["Gear Down"] = function(Entity, Value) - if Value == 0 then return end + end - if Entity.Auto then - ChangeDrive(Entity, Entity.Drive - 1) + local function CreateOutputs(Entity, Data, Class, Gearbox) + local List = { "Current Gear", "Ratio", "Entity [ENTITY]" } + + if Class.SetupOutputs then + Class.SetupOutputs(List, Entity, Data, Class, Gearbox) + end + + HookRun("ACF_OnSetupOutputs", "acf_gearbox", List, Entity, Data, Class, Gearbox) + + if Entity.Outputs then + Entity.Outputs = WireLib.AdjustOutputs(Entity, List) else - ChangeGear(Entity, Entity.Gear - 1) - end - end, - Clutch = function(Entity, Value) - Entity.MainClutch = Clamp(1 - Value, 0, 1) - end, - Brake = function(Entity, Value) - Entity.LBrake = Clamp(Value, 0, 100) - Entity.RBrake = Clamp(Value, 0, 100) - SetCanApplyBrakes(Entity) - end, - ["Left Brake"] = function(Entity, Value) - Entity.LBrake = Clamp(Value, 0, 100) - SetCanApplyBrakes(Entity) - end, - ["Right Brake"] = function(Entity, Value) - Entity.RBrake = Clamp(Value, 0, 100) - SetCanApplyBrakes(Entity) - end, - ["Left Clutch"] = function(Entity, Value) - Entity.LClutch = Clamp(1 - Value, 0, 1) - end, - ["Right Clutch"] = function(Entity, Value) - Entity.RClutch = Clamp(1 - Value, 0, 1) - end, - ["CVT Ratio"] = function(Entity, Value) - Entity.CVTRatio = Clamp(Value, 0, 1) - end, - ["Steer Rate"] = function(Entity, Value) - Entity.SteerRate = Clamp(Value, -1, 1) - end, - ["Hold Gear"] = function(Entity, Value) - Entity.Hold = tobool(Value) - end, - ["Shift Speed Scale"] = function(Entity, Value) - Entity.ShiftScale = Clamp(Value, 0.1, 1.5) - end -} + Entity.Outputs = WireLib.CreateOutputs(Entity, List) + end + end ---===============================================================================================-- + local function UpdateGearbox(Entity, Data, Class, Gearbox) + Entity:SetModel(Gearbox.Model) -function MakeACF_Gearbox(Owner, Pos, Angle, Id, ...) - if not Owner:CheckLimit("_acf_misc") then return end + Entity:PhysicsInit(SOLID_VPHYSICS) + Entity:SetMoveType(MOVETYPE_VPHYSICS) - local GearboxData = ACF.Weapons.Mobility[Id] + -- Storing all the relevant information on the entity for duping + for _, V in ipairs(Entity.DataStore) do + Entity[V] = Data[V] + end - if not GearboxData then return end + Entity.Name = Gearbox.Name + Entity.ShortName = Gearbox.ID + Entity.EntType = Class.Name + Entity.ClassData = Class + Entity.DefaultSound = Class.Sound + Entity.SwitchTime = Gearbox.Switch + Entity.MaxTorque = Gearbox.MaxTorque + Entity.MinGear = Class.Gears.Min + Entity.MaxGear = Class.Gears.Max + Entity.GearCount = Entity.MaxGear + Entity.DualClutch = Gearbox.DualClutch + Entity.In = Entity:WorldToLocal(Entity:GetAttachment(Entity:LookupAttachment("input")).Pos) + Entity.OutL = Entity:WorldToLocal(Entity:GetAttachment(Entity:LookupAttachment("driveshaftL")).Pos) + Entity.OutR = Entity:WorldToLocal(Entity:GetAttachment(Entity:LookupAttachment("driveshaftR")).Pos) + Entity.HitBoxes = ACF.HitBoxes[Gearbox.Model] - local Gearbox = ents.Create("acf_gearbox") + CreateInputs(Entity, Data, Class, Gearbox) + CreateOutputs(Entity, Data, Class, Gearbox) - if not IsValid(Gearbox) then return end + Entity:SetNWString("WireName", "ACF " .. Entity.Name) - Gearbox:SetModel(GearboxData.model) - Gearbox:SetPlayer(Owner) - Gearbox:SetAngles(Angle) - Gearbox:SetPos(Pos) - Gearbox:Spawn() + ACF.Activate(Entity, true) - Gearbox:PhysicsInit(SOLID_VPHYSICS) - Gearbox:SetMoveType(MOVETYPE_VPHYSICS) + Entity.ACF.LegalMass = Gearbox.Mass + Entity.ACF.Model = Gearbox.Model - Owner:AddCount("_acf_misc", Gearbox) - Owner:AddCleanup("acfmenu", Gearbox) + local Phys = Entity:GetPhysicsObject() + if IsValid(Phys) then Phys:SetMass(Gearbox.Mass) end - Gearbox.Owner = Owner - Gearbox.IsGeartrain = true - Gearbox.Engines = {} - Gearbox.Wheels = {} -- a "Link" has these components: Ent, Side, Axis, Rope, RopeLen, Output, ReqTq, Vel - Gearbox.GearboxIn = {} - Gearbox.GearboxOut = {} - Gearbox.TotalReqTq = 0 - Gearbox.TorqueOutput = 0 - Gearbox.LBrake = 0 - Gearbox.RBrake = 0 - Gearbox.SteerRate = 0 - Gearbox.ChangeFinished = 0 - Gearbox.InGear = false - Gearbox.CanUpdate = true - Gearbox.LastActive = 0 - Gearbox.Braking = false + Entity:ChangeGear(1) + end - Gearbox.In = Gearbox:WorldToLocal(Gearbox:GetAttachment(Gearbox:LookupAttachment("input")).Pos) - Gearbox.OutL = Gearbox:WorldToLocal(Gearbox:GetAttachment(Gearbox:LookupAttachment("driveshaftL")).Pos) - Gearbox.OutR = Gearbox:WorldToLocal(Gearbox:GetAttachment(Gearbox:LookupAttachment("driveshaftR")).Pos) + -- Some information may still be passed from the menu tool + -- We don't want to save it on the entity if it's not needed + local function CleanupData(Class, Gearbox) + if Class ~= "acf_gearbox" then return end - UpdateGearboxData(Gearbox, GearboxData, Id, ...) + if not Gearbox.Automatic then + Gearbox.Reverse = nil + end - CheckLegal(Gearbox) + if not Gearbox.CVT then + Gearbox.MinRPM = nil + Gearbox.MaxRPM = nil + end - TimerCreate("ACF Gearbox Clock " .. Gearbox:EntIndex(), 3, 0, function() - if not IsValid(Gearbox) then return end + if Gearbox.DualClutch then + Gearbox:SetBodygroup(1, 1) + end + end + + hook.Add("ACF_OnEntitySpawn", "ACF Cleanup Gearbox Data", CleanupData) + hook.Add("ACF_OnEntityUpdate", "ACF Cleanup Gearbox Data", CleanupData) + hook.Add("ACF_OnSetupInputs", "ACF Cleanup Gearbox Data", function(Class, List, Entity) + if Class ~= "acf_gearbox" then return end - CheckRopes(Gearbox, "GearboxOut") - CheckRopes(Gearbox, "Wheels") + local Count = #List + + if Entity.DualClutch then + List[Count + 1] = "Left Clutch" + List[Count + 2] = "Right Clutch" + List[Count + 3] = "Left Brake" + List[Count + 4] = "Right Brake" + else + List[Count + 1] = "Clutch" + List[Count + 2] = "Brake" + end end) + hook.Add("ACF_OnEntityLast", "ACF Cleanup Gearbox Data", function(Class, Gearbox) + if Class ~= "acf_gearbox" then return end - return Gearbox -end + Gearbox:SetBodygroup(1, 0) + end) + + ------------------------------------------------------------------------------- + + function MakeACF_Gearbox(Player, Pos, Angle, Data) + VerifyData(Data) + + local Class = ACF.GetClassGroup(Gearboxes, Data.Gearbox) + local GearboxData = Class.Lookup[Data.Gearbox] + local Limit = Class.LimitConVar.Name + + if not Player:CheckLimit(Limit) then return end + + local Gearbox = ents.Create("acf_gearbox") + + if not IsValid(Gearbox) then return end + + Gearbox:SetPlayer(Player) + Gearbox:SetAngles(Angle) + Gearbox:SetPos(Pos) + Gearbox:Spawn() + + Player:AddCleanup("acf_gearbox", Gearbox) + Player:AddCount(Limit, Gearbox) + + Gearbox.Owner = Player -- MUST be stored on ent for PP + Gearbox.SoundPath = Class.Sound + Gearbox.Engines = {} + Gearbox.Wheels = {} -- a "Link" has these components: Ent, Side, Axis, Rope, RopeLen, Output, ReqTq, Vel + Gearbox.GearboxIn = {} + Gearbox.GearboxOut = {} + Gearbox.TotalReqTq = 0 + Gearbox.TorqueOutput = 0 + Gearbox.LBrake = 0 + Gearbox.RBrake = 0 + Gearbox.ChangeFinished = 0 + Gearbox.InGear = false + Gearbox.Braking = false + Gearbox.LastBrakeThink = 0 + Gearbox.LastActive = 0 + Gearbox.LClutch = 1 + Gearbox.RClutch = 1 + Gearbox.DataStore = ACF.GetEntityArguments("acf_gearbox") + + UpdateGearbox(Gearbox, Data, Class, GearboxData) + + WireLib.TriggerOutput(Gearbox, "Entity", Gearbox) + + if Class.OnSpawn then + Class.OnSpawn(Gearbox, Data, Class, GearboxData) + end + + HookRun("ACF_OnEntitySpawn", "acf_gearbox", Gearbox, Data, Class, GearboxData) -list.Set("ACFCvars", "acf_gearbox", {"id", "data1", "data2", "data3", "data4", "data5", "data6", "data7", "data8", "data9", "data10"}) -duplicator.RegisterEntityClass("acf_gearbox", MakeACF_Gearbox, "Pos", "Angle", "Id", "Gear1", "Gear2", "Gear3", "Gear4", "Gear5", "Gear6", "Gear7", "Gear8", "Gear9", "Gear0", "Data") -ACF.RegisterLinkSource("acf_gearbox", "GearboxIn") -ACF.RegisterLinkSource("acf_gearbox", "GearboxOut") -ACF.RegisterLinkSource("acf_gearbox", "Engines") -ACF.RegisterLinkSource("acf_gearbox", "Wheels") + Gearbox:UpdateOverlay(true) + + do -- Mass entity mod removal + local EntMods = Data and Data.EntityMods + + if EntMods and EntMods.mass then + EntMods.mass = nil + end + end + + CheckLegal(Gearbox) + + timer.Create("ACF Gearbox Clock " .. Gearbox:EntIndex(), 3, 0, function() + if IsValid(Gearbox) then + CheckRopes(Gearbox, "GearboxOut") + CheckRopes(Gearbox, "Wheels") + else + timer.Remove("ACF Engine Clock " .. Gearbox:EntIndex()) + end + end) + + return Gearbox + end + + ACF.RegisterEntityClass("acf_gearbox", MakeACF_Gearbox, "Gearbox", "Gears", "FinalDrive", "ShiftPoints", "Reverse", "MinRPM", "MaxRPM") + ACF.RegisterLinkSource("acf_gearbox", "GearboxIn") + ACF.RegisterLinkSource("acf_gearbox", "GearboxOut") + ACF.RegisterLinkSource("acf_gearbox", "Engines") + ACF.RegisterLinkSource("acf_gearbox", "Wheels") + + ------------------- Updating --------------------- + + function ENT:Update(Data) + VerifyData(Data) + + local Class = ACF.GetClassGroup(Gearboxes, Data.Gearbox) + local GearboxData = Class.Lookup[Data.Gearbox] + local OldClass = self.ClassData + local Feedback = "" + + if OldClass.OnLast then + OldClass.OnLast(self, OldClass) + end + + HookRun("ACF_OnEntityLast", "acf_gearbox", self, OldClass) + + ACF.SaveEntity(self) + + UpdateGearbox(self, Data, Class, GearboxData) + + ACF.RestoreEntity(self) + + if Class.OnUpdate then + Class.OnUpdate(self, Data, Class, GearboxData) + end + + HookRun("ACF_OnEntityUpdate", "acf_gearbox", self, Data, Class, GearboxData) + + if next(self.Engines) then + local Count, Total = 0, 0 + + for Engine in pairs(self.Engines) do + self:Unlink(Engine) + + local Result = self:Link(Engine) + + if not Result then Count = Count + 1 end + + Total = Total + 1 + end + + if Count == Total then + Feedback = Feedback .. "\nUnlinked all engines due to excessive driveshaft angle." + elseif Count > 0 then + local Text = Feedback .. "\nUnlinked %s out of %s engines due to excessive driveshaft angle." + + Feedback = Text:format(Count, Total) + end + end + + if next(self.Wheels) then + local Count, Total = 0, 0 + + for Wheel in pairs(self.Wheels) do + self:Unlink(Wheel) + + local Result = self:Link(Wheel) + + if not Result then Count = Count + 1 end + + Total = Total + 1 + end + + if Count == Total then + Feedback = Feedback .. "\nUnlinked all wheels due to excessive driveshaft angle." + elseif Count > 0 then + local Text = Feedback .. "\nUnlinked %s out of %s wheels due to excessive driveshaft angle." + + Feedback = Text:format(Count, Total) + end + end + + if next(self.GearboxIn) or next(self.GearboxOut) then + local Count, Total = 0, 0 + + for Gearbox in pairs(self.GearboxIn) do + Gearbox:Unlink(self) + + local Result = Gearbox:Link(self) + + if not Result then Count = Count + 1 end + + Total = Total + 1 + end + + for Gearbox in pairs(self.GearboxOut) do + self:Unlink(Gearbox) + + local Result = self:Link(Gearbox) + + if not Result then Count = Count + 1 end + + Total = Total + 1 + end + + if Count == Total then + Feedback = Feedback .. "\nUnlinked all gearboxes due to excessive driveshaft angle." + elseif Count > 0 then + local Text = Feedback .. "\nUnlinked %s out of %s gearboxes due to excessive driveshaft angle." + + Feedback = Text:format(Count, Total) + end + end + + self:UpdateOverlay(true) + + net.Start("ACF_UpdateEntity") + net.WriteEntity(self) + net.Broadcast() + + return true, "Gearbox updated successfully!" .. Feedback + end +end --===============================================================================================-- -- Meta Funcs --===============================================================================================-- function ENT:Enable() - if self.Auto then - ChangeDrive(self, self.OldGear) + if self.Automatic then + self:ChangeDrive(self.OldGear) else - ChangeGear(self, self.OldGear) + self:ChangeGear(self.OldGear) end self.OldGear = nil @@ -555,96 +615,160 @@ function ENT:Enable() end function ENT:Disable() - self.OldGear = self.Auto and self.Drive or self.Gear + self.OldGear = self.Automatic and self.Drive or self.Gear - if self.Auto then - ChangeDrive(self, 0) + if self.Automatic then + self:ChangeDrive(0) else - ChangeGear(self, 0) + self:ChangeGear(0) end self:UpdateOverlay() end -function ENT:Update(ArgsTable) - if ArgsTable[1] ~= self.Owner then return false, "You don't own that gearbox!" end - - local Id = ArgsTable[4] -- Argtable[4] is the engine ID - local GearboxData = ACF.Weapons.Mobility[Id] - - if not GearboxData then return false, "Invalid gearbox type!" end - if GearboxData.model ~= self.Model then return false, "The new gearbox must have the same model!" end +local Text = "%s\nCurrent Gear: %s\n\n%s\nFinal Driver: %s\nTorque Rating: %s Nm / %s fl-lb\nTorque Output: %s Nm / %s fl-lb" - UpdateGearboxData(self, GearboxData, unpack(ArgsTable, 4, 14)) +function ENT:UpdateOverlayText() + local GearsText = self.ClassData.GetGearsText and self.ClassData.GetGearsText(self) + local Final = math.Round(self.FinalDrive, 2) + local Torque = math.Round(self.MaxTorque * 0.73) + local Output = math.Round(self.TorqueOutput * 0.73) - return true, "Gearbox updated successfully!" -end + if not GearsText or GearsText == "" then + local Gears = self.Gears -local function Overlay(Ent) - if Ent.Disabled then - Ent:SetOverlayText("Disabled: " .. Ent.DisableReason .. "\n" .. Ent.DisableDescription) - else - local Text + GearsText = "" - if Ent.DisableReason then - Text = "Disabled: " .. Ent.DisableReason - else - Text = "Current Gear: " .. Ent.Gear + for I = 1, self.MaxGear do + GearsText = GearsText .. "Gear " .. I .. ": " .. math.Round(Gears[I], 2) .. "\n" end + end - Text = Text .. "\n\n" .. Ent.Name .. "\n" + return Text:format(self.Name, self.Gear, GearsText, Final, self.MaxTorque, Torque, math.floor(self.TorqueOutput), Output) +end - if Ent.CVT then - Text = "Reverse Gear: " .. math.Round(Ent.GearTable[2], 2) .. - "\nTarget: " .. math.Round(Ent.TargetMinRPM) .. " - " .. math.Round(Ent.TargetMaxRPM) .. " RPM\n" - elseif Ent.Auto then - for i = 1, Ent.Gears do - Text = Text .. "Gear " .. i .. ": " .. math.Round(Ent.GearTable[i], 2) .. - ", Upshift @ " .. math.Round(Ent.ShiftPoints[i] / 10.936, 1) .. " kph / " .. - math.Round(Ent.ShiftPoints[i] / 17.6, 1) .. " mph\n" - end +-- prevent people from changing bodygroup +function ENT:CanProperty(_, Property) + return Property ~= "bodygroups" +end - Text = Text .. "Reverse gear: " .. math.Round(Ent.GearTable[Ent.Reverse], 2) .. "\n" - else - for i = 1, Ent.Gears do - Text = Text .. "Gear " .. i .. ": " .. math.Round(Ent.GearTable[i], 2) .. "\n" - end - end +ACF.AddInputAction("acf_gearbox", "Gear", function(Entity, Value) + if Entity.Automatic then + Entity:ChangeDrive(Value) + else + Entity:ChangeGear(Value) + end +end) - Text = Text .. "Final Drive: " .. math.Round(Ent.Gear0, 2) .. "\n" - Text = Text .. "Torque Rating: " .. Ent.MaxTorque .. " Nm / " .. math.Round(Ent.MaxTorque * 0.73) .. " ft-lb\n" - Text = Text .. "Torque Output: " .. math.floor(Ent.TorqueOutput) .. " Nm / " .. math.Round(Ent.TorqueOutput * 0.73) .. " ft-lb" +ACF.AddInputAction("acf_gearbox", "Gear Up", function(Entity, Value) + if not tobool(Value) then return end - Ent:SetOverlayText(Text) + if Entity.Automatic then + Entity:ChangeDrive(Entity.Drive + 1) + else + Entity:ChangeGear(Entity.Gear + 1) end -end +end) -function ENT:UpdateOverlay() - if TimerExists("ACF Overlay Buffer" .. self:EntIndex()) then -- This entity has been updated too recently - self.OverlayBuffer = true -- Mark it to update when buffer time has expired - else - TimerCreate("ACF Overlay Buffer" .. self:EntIndex(), 1, 1, function() - if IsValid(self) and self.OverlayBuffer then - self.OverlayBuffer = nil - self:UpdateOverlay() - end - end) +ACF.AddInputAction("acf_gearbox", "Gear Down", function(Entity, Value) + if not tobool(Value) then return end - Overlay(self) + if Entity.Automatic then + Entity:ChangeDrive(Entity.Drive - 1) + else + Entity:ChangeGear(Entity.Gear - 1) end -end +end) --- prevent people from changing bodygroup -function ENT:CanProperty(_, Property) - return Property ~= "bodygroups" +ACF.AddInputAction("acf_gearbox", "Clutch", function(Entity, Value) + Entity.LClutch = Clamp(1 - Value, 0, 1) + Entity.RClutch = Clamp(1 - Value, 0, 1) +end) + +ACF.AddInputAction("acf_gearbox", "Left Clutch", function(Entity, Value) + if not Entity.DualClutch then return end + + Entity.LClutch = Clamp(1 - Value, 0, 1) +end) + +ACF.AddInputAction("acf_gearbox", "Right Clutch", function(Entity, Value) + if not Entity.DualClutch then return end + + Entity.RClutch = Clamp(1 - Value, 0, 1) +end) + +ACF.AddInputAction("acf_gearbox", "Brake", function(Entity, Value) + Entity.LBrake = Clamp(Value, 0, 100) + Entity.RBrake = Clamp(Value, 0, 100) + + SetCanApplyBrakes(Entity) +end) + +ACF.AddInputAction("acf_gearbox", "Left Brake", function(Entity, Value) + if not Entity.DualClutch then return end + + Entity.LBrake = Clamp(Value, 0, 100) + + SetCanApplyBrakes(Entity) +end) + +ACF.AddInputAction("acf_gearbox", "Right Brake", function(Entity, Value) + if not Entity.DualClutch then return end + + Entity.RBrake = Clamp(Value, 0, 100) + + SetCanApplyBrakes(Entity) +end) + +ACF.AddInputAction("acf_gearbox", "CVT Ratio", function(Entity, Value) + if not Entity.CVT then return end + + Entity.CVTRatio = Clamp(Value, 0, 1) +end) + +ACF.AddInputAction("acf_gearbox", "Steer Rate", function(Entity, Value) + if not Entity.DoubleDiff then return end + + Entity.SteerRate = Clamp(Value, -1, 1) +end) + +ACF.AddInputAction("acf_gearbox", "Hold Gear", function(Entity, Value) + if not Entity.Automatic then return end + + Entity.Hold = tobool(Value) +end) + +ACF.AddInputAction("acf_gearbox", "Shift Speed Scale", function(Entity, Value) + if not Entity.Automatic then return end + + Entity.ShiftScale = Clamp(Value, 0.1, 1.5) +end) + +-- Handles gearing for automatic gearboxes. 0 = Neutral, 1 = Drive, 2 = Reverse +function ENT:ChangeDrive(Value) + Value = Clamp(math.floor(Value), 0, 2) + + if self.Drive == Value then return end + + self.Drive = Value + + self:ChangeGear(Value == 2 and self.GearCount or Value) end -function ENT:TriggerInput(Input, Value) - if self.Disabled then return end +function ENT:ChangeGear(Value) + Value = Clamp(math.floor(Value), self.MinGear, self.GearCount) - if Inputs[Input] then - Inputs[Input](self, Value) - end + if self.Gear == Value then return end + + self.Gear = Value + self.InGear = false + self.GearRatio = self.Gears[Value] * self.FinalDrive + self.ChangeFinished = ACF.CurTime + self.SwitchTime + + self:EmitSound(self.SoundPath, 70, 100, 0.5 * ACF.Volume) + + WireLib.TriggerOutput(self, "Current Gear", Value) + WireLib.TriggerOutput(self, "Ratio", self.GearRatio) end function ENT:Calc(InputRPM, InputInertia) @@ -660,23 +784,23 @@ function ENT:Calc(InputRPM, InputInertia) if self.CVT and self.Gear == 1 then if self.CVTRatio > 0 then - self.GearTable[1] = Clamp(self.CVTRatio, 0.01, 1) + self.Gears[1] = Clamp(self.CVTRatio, 0.01, 1) else - self.GearTable[1] = Clamp((InputRPM - self.TargetMinRPM) / (self.TargetMaxRPM - self.TargetMinRPM), 0.05, 1) + self.Gears[1] = Clamp((InputRPM - self.MinRPM) / (self.MaxRPM - self.MinRPM), 0.05, 1) end - self.GearRatio = self.GearTable[1] * self.GearTable.Final + self.GearRatio = self.Gears[1] * self.FinalDrive WireLib.TriggerOutput(self, "Ratio", self.GearRatio) end - if self.Auto and self.Drive == 1 and self.InGear then + if self.Automatic and self.Drive == 1 and self.InGear then local PhysVel = BoxPhys:GetVelocity():Length() - if not self.Hold and self.Gear ~= self.Gears and PhysVel > (self.ShiftPoints[self.Gear] * self.ShiftScale) then - ChangeGear(self, self.Gear + 1) + if not self.Hold and self.Gear ~= self.MaxGear and PhysVel > (self.ShiftPoints[self.Gear] * self.ShiftScale) then + self:ChangeGear(self.Gear + 1) elseif PhysVel < (self.ShiftPoints[self.Gear - 1] * self.ShiftScale) then - ChangeGear(self, self.Gear - 1) + self:ChangeGear(self.Gear - 1) end end @@ -684,7 +808,7 @@ function ENT:Calc(InputRPM, InputInertia) self.TorqueOutput = 0 for Ent, Link in pairs(self.GearboxOut) do - local Clutch = self.MainClutch + local Clutch = Link.Side == 0 and self.LClutch or self.RClutch Link.ReqTq = 0 @@ -706,7 +830,7 @@ function ENT:Calc(InputRPM, InputInertia) Link.ReqTq = 0 if self.GearRatio ~= 0 then - local Clutch = self.Dual and ((Link.Side == 0 and self.LClutch) or self.RClutch) or self.MainClutch + local Clutch = Link.Side == 0 and self.LClutch or self.RClutch local OnRPM = ((InputRPM > 0 and RPM < InputRPM) or (InputRPM < 0 and RPM > InputRPM)) if Clutch > 0 and OnRPM then @@ -768,7 +892,7 @@ function ENT:Act(Torque, DeltaTime, MassRatio) if self.Disabled then return end local Loss = Clamp(((1 - 0.4) / 0.5) * ((self.ACF.Health / self.ACF.MaxHealth) - 1) + 1, 0.4, 1) --internal torque loss from damaged - local Slop = self.Auto and 0.9 or 1 --internal torque loss from inefficiency + local Slop = self.Automatic and 0.9 or 1 --internal torque loss from inefficiency local ReactTq = 0 -- Calculate the ratio of total requested torque versus what's avaliable, and then multiply it but the current gearratio local AvailTq = 0 @@ -800,32 +924,6 @@ function ENT:Act(Torque, DeltaTime, MassRatio) self.LastActive = ACF.CurTime end -function ENT:Link(Target) - if not IsValid(Target) then return false, "Attempted to link an invalid entity." end - if self == Target then return false, "Can't link a gearbox to itself." end - - local Function = ClassLink(self:GetClass(), Target:GetClass()) - - if Function then - return Function(self, Target) - end - - return false, "Gearboxes can't be linked to '" .. Target:GetClass() .. "'." -end - -function ENT:Unlink(Target) - if not IsValid(Target) then return false, "Attempted to unlink an invalid entity." end - if self == Target then return false, "Can't unlink a gearbox from itself." end - - local Function = ClassUnlink(self:GetClass(), Target:GetClass()) - - if Function then - return Function(self, Target) - end - - return false, "Gearboxes can't be unlinked from '" .. Target:GetClass() .. "'." -end - function ENT:PreEntityCopy() if next(self.Wheels) then local Wheels = {} @@ -838,13 +936,13 @@ function ENT:PreEntityCopy() end if next(self.GearboxOut) then - local Gearboxes = {} + local Entities = {} for Ent in pairs(self.GearboxOut) do - Gearboxes[#Gearboxes + 1] = Ent:EntIndex() + Entities[#Entities + 1] = Ent:EntIndex() end - duplicator.StoreEntityModifier(self, "ACFGearboxes", Gearboxes) + duplicator.StoreEntityModifier(self, "ACFGearboxes", Entities) end --Wire dupe info @@ -885,6 +983,14 @@ function ENT:PostEntityPaste(Player, Ent, CreatedEntities) end function ENT:OnRemove() + local Class = self.ClassData + + if Class.OnLast then + Class.OnLast(self, Class) + end + + HookRun("ACF_OnEntityLast", "acf_gearbox", self, Class) + for Engine in pairs(self.Engines) do self:Unlink(Engine) end diff --git a/lua/entities/acf_gearbox/shared.lua b/lua/entities/acf_gearbox/shared.lua index 8d0a23bf3..a8d0d4aae 100644 --- a/lua/entities/acf_gearbox/shared.lua +++ b/lua/entities/acf_gearbox/shared.lua @@ -1,4 +1,8 @@ -DEFINE_BASECLASS("base_wire_entity") +DEFINE_BASECLASS("acf_base_simple") -ENT.PrintName = "ACF Gearbox" -ENT.WireDebugName = "ACF Gearbox" \ No newline at end of file +ENT.PrintName = "ACF Gearbox" +ENT.WireDebugName = "ACF Gearbox" +ENT.PluralName = "ACF Gearboxes" +ENT.IsGearbox = true + +cleanup.Register("acf_gearbox") diff --git a/lua/entities/acf_gun/cl_init.lua b/lua/entities/acf_gun/cl_init.lua index d2ce08f7b..cd4305e5b 100644 --- a/lua/entities/acf_gun/cl_init.lua +++ b/lua/entities/acf_gun/cl_init.lua @@ -1,8 +1,15 @@ +DEFINE_BASECLASS("acf_base_simple") -- Required to get the local BaseClass + include("shared.lua") -local HideInfo = ACF.HideInfoBubble +language.Add("Cleanup_acf_gun", "ACF Weapons") +language.Add("Cleaned_acf_gun", "Cleaned up all ACF Weapons") +language.Add("Cleanup_acf_smokelauncher", "ACF Smoke Launchers") +language.Add("SBoxLimit__acf_gun", "You've reached the ACF Weapons limit!") +language.Add("Cleaned_acf_smokelauncher", "Cleaned up all ACF Smoke Launchers") +language.Add("SBoxLimit__acf_smokelauncher", "You've reached the ACF Smoke Launcher limit!") -function ENT:Initialize() +function ENT:Initialize(...) self.LastFire = 0 self.Reload = 0 self.CloseTime = 0 @@ -10,31 +17,22 @@ function ENT:Initialize() self.RateScale = 0 self.FireAnim = self:LookupSequence("shoot") self.CloseAnim = self:LookupSequence("load") - self.HitBoxes = ACF.HitBoxes[self:GetModel()] - self.BaseClass.Initialize(self) + BaseClass.Initialize(self, ...) end --- copied from base_wire_entity: DoNormalDraw's notip arg isn't accessible from ENT:Draw defined there. -function ENT:Draw() - self:DoNormalDraw(false, HideInfo()) - - Wire_Render(self) - - if self.GetBeamLength and (not self.GetShowBeam or self:GetShowBeam()) then - -- Every SENT that has GetBeamLength should draw a tracer. Some of them have the GetShowBeam boolean - Wire_DrawTracerBeam(self, 1, self.GetBeamHighlight and self:GetBeamHighlight() or false) - end +function ENT:Update() + self.HitBoxes = ACF.HitBoxes[self:GetModel()] end function ENT:Think() - self.BaseClass.Think(self) + BaseClass.Think(self) - local SinceFire = CurTime() - self.LastFire + local SinceFire = ACF.CurTime - self.LastFire self:SetCycle(SinceFire * self.Rate / self.RateScale) - if CurTime() > self.LastFire + self.CloseTime and self.CloseAnim then + if ACF.CurTime > self.LastFire + self.CloseTime and self.CloseAnim then self:ResetSequence(self.CloseAnim) self:SetCycle((SinceFire - self.CloseTime) * self.Rate / self.RateScale) self.Rate = 1 / (self.Reload - self.CloseTime) -- Base anim time is 1s, rate is in 1/10 of a second @@ -61,37 +59,6 @@ function ENT:Animate(ReloadTime, LoadOnly) end self:SetPlaybackRate(self.Rate) - self.LastFire = CurTime() + self.LastFire = ACF.CurTime self.Reload = ReloadTime end - -function ACFGunGUICreate(Table) - acfmenupanel:CPanelText("Name", Table.name) - acfmenupanel.CData.DisplayModel = vgui.Create("DModelPanel", acfmenupanel.CustomDisplay) - acfmenupanel.CData.DisplayModel:SetModel(Table.model) - acfmenupanel.CData.DisplayModel:SetCamPos(Vector(250, 500, 250)) - acfmenupanel.CData.DisplayModel:SetLookAt(Vector(0, 0, 0)) - acfmenupanel.CData.DisplayModel:SetFOV(20) - acfmenupanel.CData.DisplayModel:SetSize(acfmenupanel:GetWide(), acfmenupanel:GetWide()) - acfmenupanel.CData.DisplayModel.LayoutEntity = function() end - acfmenupanel.CustomDisplay:AddItem(acfmenupanel.CData.DisplayModel) - local GunClass = ACF.Classes.GunClass[Table.gunclass] - acfmenupanel:CPanelText("ClassDesc", GunClass.desc) - acfmenupanel:CPanelText("GunDesc", Table.desc) - acfmenupanel:CPanelText("Caliber", "Caliber : " .. (Table.caliber * 10) .. "mm") - acfmenupanel:CPanelText("Weight", "Weight : " .. Table.weight .. "kg") - - if not Table.rack then - local RoundVolume = 3.1416 * (Table.caliber / 2) ^ 2 * Table.round.maxlength - local RoF = 60 / (((RoundVolume / 500) ^ 0.60) * GunClass.rofmod * (Table.rofmod or 1)) -- TODO: FIX THIS (USING OLD RELOAD CALC) - acfmenupanel:CPanelText("Firerate", "RoF : " .. math.Round(RoF, 1) .. " rounds/min") - - if Table.magsize then - acfmenupanel:CPanelText("Magazine", "Magazine : " .. Table.magsize .. " rounds\nReload : " .. Table.magreload .. " s") - end - - acfmenupanel:CPanelText("Spread", "Spread : " .. GunClass.spread .. " degrees") - end - - acfmenupanel.CustomDisplay:PerformLayout() -end \ No newline at end of file diff --git a/lua/entities/acf_gun/init.lua b/lua/entities/acf_gun/init.lua index 41748927c..7abbf96f2 100644 --- a/lua/entities/acf_gun/init.lua +++ b/lua/entities/acf_gun/init.lua @@ -5,15 +5,16 @@ include("shared.lua") -- Local Vars ----------------------------------- -local ACF_RECOIL = CreateConVar("acf_recoilpush", 1, FCVAR_ARCHIVE, "Whether or not ACF guns apply recoil", 0, 1) +local ACF = ACF local UnlinkSound = "physics/metal/metal_box_impact_bullet%s.wav" local CheckLegal = ACF_CheckLegal local Shove = ACF.KEShove local Overpressure = ACF.Overpressure +local Weapons = ACF.Classes.Weapons +local AmmoTypes = ACF.Classes.AmmoTypes local TraceRes = {} -- Output for traces local TraceData = {start = true, endpos = true, filter = true, mask = MASK_SOLID, output = TraceRes} local Trace = util.TraceLine -local TimerExists = timer.Exists local TimerCreate = timer.Create local HookRun = hook.Run local EMPTY = { Type = "Empty", PropMass = 0, ProjMass = 0, Tracer = 0 } @@ -31,117 +32,178 @@ local function UpdateTotalAmmo(Entity) WireLib.TriggerOutput(Entity, "Total Ammo", Total) end -do -- Spawn Func -------------------------------- - function MakeACF_Gun(Player, Pos, Angle, Id, Data) - local List = ACF.Weapons - local EID = List.Guns[Id] and Id or "50mmC" - local Lookup = List.Guns[EID] - local Ext = Lookup.gunclass == "SL" and "_acf_smokelauncher" or "_acf_gun" - local ClassData = ACF.Classes.GunClass[Lookup.gunclass] - local Caliber = Lookup.caliber +do -- Spawn and Update functions -------------------------------- + local Updated = { + ["20mmHRAC"] = "20mmRAC", + ["30mmHRAC"] = "30mmRAC", + ["40mmCL"] = "40mmGL", + } - if not Player:CheckLimit(Ext) then return false end -- Check gun spawn limits + local function VerifyData(Data) + if not Data.Weapon then + Data.Weapon = Data.Id or "50mmC" + end - local Gun = ents.Create("acf_gun") + local Class = ACF.GetClassGroup(Weapons, Data.Weapon) - if not IsValid(Gun) then return end + if not Class then + Data.Weapon = Data.Weapon and Updated[Data.Weapon] or "50mmC" - Player:AddCleanup("acfmenu", Gun) - Player:AddCount(Ext, Gun) + Class = ACF.GetClassGroup(Weapons, Data.Weapon) + end - Gun:SetModel(Lookup.model) - Gun:SetPlayer(Player) - Gun:SetAngles(Angle) - Gun:SetPos(Pos) - Gun:Spawn() + do -- External verifications + if Class.VerifyData then + Class.VerifyData(Data, Class) + end - Gun:PhysicsInit(SOLID_VPHYSICS) - Gun:SetMoveType(MOVETYPE_VPHYSICS) + HookRun("ACF_VerifyData", "acf_gun", Data, Class) + end + end - Gun.Id = Id -- MUST be stored on ent to be duped - Gun.Owner = Player -- MUST be stored on ent for PP - Gun.Outputs = WireLib.CreateOutputs(Gun, { "Ready", "Status [STRING]", "Total Ammo", "Entity [ENTITY]", "Shots Left", "Rate of Fire", "Reload Time", "Projectile Mass", "Muzzle Velocity" }) + local function CreateInputs(Entity, Data, Class, Weapon) + local List = { "Fire", "Unload", "Reload" } + + if Class.SetupInputs then + Class.SetupInputs(List, Entity, Data, Class, Weapon) + end - if Caliber > ACF.MinFuzeCaliber then - Gun.Inputs = WireLib.CreateInputs(Gun, { "Fire", "Unload", "Reload", "Fuze" }) + HookRun("ACF_OnSetupInputs", "acf_gun", List, Entity, Data, Class, Weapon) + + if Entity.Inputs then + Entity.Inputs = WireLib.AdjustInputs(Entity, List) else - Gun.Inputs = WireLib.CreateInputs(Gun, { "Fire", "Unload", "Reload" }) - end - - -- ACF Specific vars - Gun.BarrelFilter = { Gun } - Gun.State = "Empty" - Gun.Crates = {} - Gun.Name = Lookup.name - Gun.ShortName = Id - Gun.EntType = ClassData.name - Gun.Caliber = Caliber - Gun.Class = Lookup.gunclass - Gun.MagReload = Lookup.magreload - Gun.MagSize = Lookup.magsize or 1 - Gun.Cyclic = Lookup.Cyclic and 60 / Lookup.Cyclic or nil - Gun.ReloadTime = Gun.Cyclic or 1 - Gun.CurrentShot = 0 - Gun.Spread = ClassData.spread - Gun.MinLengthBonus = 0.75 * 3.1416 * (Caliber / 2) ^ 2 * Lookup.round.maxlength - Gun.Muzzleflash = ClassData.muzzleflash - Gun.PGRoFmod = math.max(0.01, Lookup.rofmod or 1) - Gun.RoFmod = ClassData.rofmod - Gun.Sound = ClassData.sound - Gun.BulletData = { Type = "Empty", PropMass = 0, ProjMass = 0, Tracer = 0 } - Gun.HitBoxes = ACF.HitBoxes[Lookup.model] - Gun.Long = ClassData.longbarrel - Gun.NormalMuzzle = Gun:WorldToLocal(Gun:GetAttachment(Gun:LookupAttachment("muzzle")).Pos) - Gun.Muzzle = Gun.NormalMuzzle + Entity.Inputs = WireLib.CreateInputs(Entity, List) + end + end + + local function CreateOutputs(Entity, Data, Class, Weapon) + local List = { "Ready", "Status [STRING]", "Total Ammo", "Entity [ENTITY]", "Shots Left", "Rate of Fire", "Reload Time", "Projectile Mass", "Muzzle Velocity" } + + if Class.SetupOutputs then + Class.SetupOutputs(List, Entity, Data, Class, Weapon) + end + + HookRun("ACF_OnSetupOutputs", "acf_gun", List, Entity, Data, Class, Weapon) + + if Entity.Outputs then + Entity.Outputs = WireLib.AdjustOutputs(Entity, List) + else + Entity.Outputs = WireLib.CreateOutputs(Entity, List) + end + end + + local function UpdateWeapon(Entity, Data, Class, Weapon) + Entity:SetModel(Weapon.Model) + + Entity:PhysicsInit(SOLID_VPHYSICS) + Entity:SetMoveType(MOVETYPE_VPHYSICS) + + -- Storing all the relevant information on the entity for duping + for _, V in ipairs(Entity.DataStore) do + Entity[V] = Data[V] + end + + Entity.Name = Weapon.Name + Entity.ShortName = Weapon.ID + Entity.EntType = Class.Name + Entity.ClassData = Class + Entity.WeaponData = Weapon + Entity.Class = Class.ID -- Needed for custom killicons + Entity.Caliber = Weapon.Caliber + Entity.MagReload = Weapon.MagReload + Entity.MagSize = Weapon.MagSize or 1 + Entity.Cyclic = Weapon.Cyclic and 60 / Weapon.Cyclic + Entity.ReloadTime = Entity.Cyclic or 1 + Entity.Spread = Class.Spread + Entity.DefaultSound = Class.Sound + Entity.HitBoxes = ACF.HitBoxes[Weapon.Model] + Entity.Long = Class.LongBarrel + Entity.NormalMuzzle = Entity:WorldToLocal(Entity:GetAttachment(Entity:LookupAttachment("muzzle")).Pos) + Entity.Muzzle = Entity.NormalMuzzle + + CreateInputs(Entity, Data, Class, Weapon) + CreateOutputs(Entity, Data, Class, Weapon) -- Set NWvars - Gun:SetNWString("Sound", Gun.Sound) - Gun:SetNWString("WireName", "ACF " .. Lookup.name) - Gun:SetNWString("ID", Id) - Gun:SetNWString("Class", Gun.Class) + Entity:SetNWString("WireName", "ACF " .. Weapon.Name) + Entity:SetNWString("Class", Entity.Class) + Entity:SetNWString("ID", Entity.Weapon) -- Adjustable barrel length - if Gun.Long then - local Attachment = Gun:GetAttachment(Gun:LookupAttachment(Gun.Long.newpos)) + if Entity.Long then + local Attachment = Entity:GetAttachment(Entity:LookupAttachment(Entity.Long.NewPos)) - Gun.LongMuzzle = Attachment and Gun:WorldToLocal(Attachment.Pos) + Entity.LongMuzzle = Attachment and Entity:WorldToLocal(Attachment.Pos) + end - timer.Simple(0, function() - if not IsValid(Gun) then return end - if not Attachment then return end + if Entity.Cyclic then -- Automatics don't change their rate of fire + WireLib.TriggerOutput(Entity, "Reload Time", Entity.Cyclic) + WireLib.TriggerOutput(Entity, "Rate of Fire", 60 / Entity.Cyclic) + end - local Long = Gun.Long + ACF.Activate(Entity, true) - if Gun:GetBodygroup(Long.index) == Long.submodel then - Gun.Muzzle = Gun.LongMuzzle - end - end) - end + Entity.ACF.LegalMass = Weapon.Mass + Entity.ACF.Model = Weapon.Model - TimerCreate("ACF Ammo Left " .. Gun:EntIndex(), 1, 0, function() - if not IsValid(Gun) then return end + local Phys = Entity:GetPhysicsObject() + if IsValid(Phys) then Phys:SetMass(Weapon.Mass) end + end - UpdateTotalAmmo(Gun) - end) + hook.Add("ACF_OnSetupInputs", "ACF Weapon Fuze", function(Class, List, Entity) + if Class ~= "acf_gun" then return end + if Entity.Caliber <= ACF.MinFuzeCaliber then return end + + List[#List + 1] = "Fuze" + end) + + ------------------------------------------------------------------------------- + + function MakeACF_Weapon(Player, Pos, Angle, Data) + VerifyData(Data) + + local Class = ACF.GetClassGroup(Weapons, Data.Weapon) + local Weapon = Class.Lookup[Data.Weapon] + local Limit = Class.LimitConVar.Name + + if not Player:CheckLimit(Limit) then return false end -- Check gun spawn limits + + local Gun = ents.Create("acf_gun") + + if not IsValid(Gun) then return end + + Player:AddCleanup(Class.Cleanup, Gun) + Player:AddCount(Limit, Gun) + + Gun:SetPlayer(Player) + Gun:SetAngles(Angle) + Gun:SetPos(Pos) + Gun:Spawn() + + Gun.Owner = Player -- MUST be stored on ent for PP + Gun.SoundPath = Class.Sound + Gun.BarrelFilter = { Gun } + Gun.State = "Empty" + Gun.Crates = {} + Gun.CurrentShot = 0 + Gun.BulletData = { Type = "Empty", PropMass = 0, ProjMass = 0, Tracer = 0 } + Gun.DataStore = ACF.GetEntityArguments("acf_gun") + + Gun:SetNWString("Sound", Class.Sound) + + UpdateWeapon(Gun, Data, Class, Weapon) WireLib.TriggerOutput(Gun, "Status", "Empty") WireLib.TriggerOutput(Gun, "Entity", Gun) WireLib.TriggerOutput(Gun, "Projectile Mass", 1000) WireLib.TriggerOutput(Gun, "Muzzle Velocity", 1000) - if Gun.Cyclic then -- Automatics don't change their rate of fire - WireLib.TriggerOutput(Gun, "Reload Time", Gun.Cyclic) - WireLib.TriggerOutput(Gun, "Rate of Fire", 60 / Gun.Cyclic) + if Class.OnSpawn then + Class.OnSpawn(Gun, Data, Class, Weapon) end - local Mass = Lookup.weight - local Phys = Gun:GetPhysicsObject() - if IsValid(Phys) then Phys:SetMass(Mass) end - - ACF_Activate(Gun) - - Gun.ACF.LegalMass = Mass - Gun.ACF.Model = Lookup.model + HookRun("ACF_OnEntitySpawn", "acf_gun", Gun, Data, Class, Weapon) Gun:UpdateOverlay(true) @@ -153,70 +215,96 @@ do -- Spawn Func -------------------------------- end end + TimerCreate("ACF Ammo Left " .. Gun:EntIndex(), 1, 0, function() + if not IsValid(Gun) then return end + + UpdateTotalAmmo(Gun) + end) + CheckLegal(Gun) return Gun end - list.Set("ACFCvars", "acf_gun", {"id"} ) - duplicator.RegisterEntityClass("acf_gun", MakeACF_Gun, "Pos", "Angle", "Id", "Data") + ACF.RegisterEntityClass("acf_gun", MakeACF_Weapon, "Weapon") ACF.RegisterLinkSource("acf_gun", "Crates") - function ENT:ACF_Activate(Recalc) - local PhysObj = self.ACF.PhysObj + ------------------- Updating --------------------- + + function ENT:Update(Data) + if self.Firing then return false, "Stop firing before updating the weapon!" end + + VerifyData(Data) + + local Class = ACF.GetClassGroup(Weapons, Data.Weapon) + local Weapon = Class.Lookup[Data.Weapon] + local OldClass = self.ClassData - if not self.ACF.Area then - self.ACF.Area = PhysObj:GetSurfaceArea() * 6.45 + if self.State ~= "Empty" then + self:Unload() end - local Volume = PhysObj:GetVolume() * 2 + if OldClass.OnLast then + OldClass.OnLast(self, OldClass) + end + + HookRun("ACF_OnEntityLast", "acf_gun", self, OldClass) + + ACF.SaveEntity(self) + + UpdateWeapon(self, Data, Class, Weapon) - local Armour = self.Caliber * 10 - local Health = Volume / ACF.Threshold --Setting the threshold of the prop Area gone - local Percent = 1 + ACF.RestoreEntity(self) - if Recalc and self.ACF.Health and self.ACF.MaxHealth then - Percent = self.ACF.Health / self.ACF.MaxHealth + if Class.OnUpdate then + Class.OnUpdate(self, Data, Class, Weapon) end - self.ACF.Health = Health * Percent - self.ACF.MaxHealth = Health - self.ACF.Armour = Armour * (0.5 + Percent / 2) - self.ACF.MaxArmour = Armour - self.ACF.Type = "Prop" + HookRun("ACF_OnEntityUpdate", "acf_gun", self, Data, Class, Weapon) + + if next(self.Crates) then + for Crate in pairs(self.Crates) do + self:Unlink(Crate) + end + end + + self:UpdateOverlay(true) + + net.Start("ACF_UpdateEntity") + net.WriteEntity(self) + net.Broadcast() + + return true, "Weapon updated successfully!" end end --------------------------------------------- do -- Metamethods -------------------------------- do -- Inputs/Outputs/Linking ---------------- - local ClassLink = ACF.GetClassLink - local ClassUnlink = ACF.GetClassUnlink - WireLib.AddOutputAlias("AmmoCount", "Total Ammo") WireLib.AddOutputAlias("Muzzle Weight", "Projectile Mass") - ACF.RegisterClassLink("acf_gun", "acf_ammo", function(Weapon, Target) - if Weapon.Crates[Target] then return false, "This weapon is already linked to this crate." end - if Target.Weapons[Weapon] then return false, "This weapon is already linked to this crate." end - if Target.RoundType == "Refill" then return false, "Refill crates cannot be linked to weapons." end - if Weapon.Id ~= Target.BulletData.Id then return false, "Wrong ammo type for this weapon." end + ACF.RegisterClassLink("acf_gun", "acf_ammo", function(This, Crate) + if This.Crates[Crate] then return false, "This weapon is already linked to this crate." end + if Crate.Weapons[This] then return false, "This weapon is already linked to this crate." end + if Crate.IsRefill then return false, "Refill crates cannot be linked to weapons." end + if This.Weapon ~= Crate.Weapon then return false, "Wrong ammo type for this weapon." end - local Blacklist = ACF.AmmoBlacklist[Target.RoundType] + local Blacklist = Crate.RoundData.Blacklist - if table.HasValue(Blacklist, Weapon.Class) then - return false, "That round type can't be used with this weapon." + if Blacklist[This.Class] then + return false, "The ammo type in this crate cannot be used for this weapon." end - Weapon.Crates[Target] = true - Target.Weapons[Weapon] = true + This.Crates[Crate] = true + Crate.Weapons[This] = true - Weapon:UpdateOverlay(true) - Target:UpdateOverlay(true) + This:UpdateOverlay(true) + Crate:UpdateOverlay(true) - if Weapon.State == "Empty" then -- When linked to an empty weapon, attempt to load it + if This.State == "Empty" then -- When linked to an empty weapon, attempt to load it timer.Simple(0.5, function() -- Delay by 500ms just in case the wiring isn't applied at the same time or whatever weird dupe shit happens - if IsValid(Weapon) and IsValid(Target) and Weapon.State == "Empty" and Target:CanConsume() then - Weapon:Load() + if IsValid(This) and IsValid(Crate) and This.State == "Empty" and Crate:CanConsume() then + This:Load() end end) end @@ -224,118 +312,53 @@ do -- Metamethods -------------------------------- return true, "Weapon linked successfully." end) - ACF.RegisterClassUnlink("acf_gun", "acf_ammo", function(Weapon, Target) - if Weapon.Crates[Target] or Target.Weapons[Weapon] then - Weapon.Crates[Target] = nil - Target.Weapons[Weapon] = nil - - Weapon:UpdateOverlay(true) - Target:UpdateOverlay(true) - - return true, "Weapon unlinked successfully." - end - - - if not Weapon.Crates[Target] then return false, "This weapon is not linked to this crate." end - end) - - local WireTable = { - gmod_wire_adv_pod = true, - gmod_wire_joystick = true, - gmod_wire_expression2 = true, - gmod_wire_joystick_multi = true, - gmod_wire_pod = function(_, Input) - if IsValid(Input.Pod) then - return Input.Pod:GetDriver() - end - end, - gmod_wire_keyboard = function(_, Input) - if Input.ply then - return Input.ply + ACF.RegisterClassUnlink("acf_gun", "acf_ammo", function(This, Crate) + if This.Crates[Crate] or Crate.Weapons[This] then + if This.CurrentCrate == Crate then + This.CurrentCrate = next(This.Crates, Crate) end - end, - } - local function FindUser(Entity, Input, Checked) - local Function = WireTable[Input:GetClass()] + This.Crates[Crate] = nil + Crate.Weapons[This] = nil - return Function and Function(Entity, Input, Checked or {}) - end - - WireTable.gmod_wire_adv_pod = WireTable.gmod_wire_pod - WireTable.gmod_wire_joystick = WireTable.gmod_wire_pod - WireTable.gmod_wire_joystick_multi = WireTable.gmod_wire_pod - WireTable.gmod_wire_expression2 = function(This, Input, Checked) - for _, V in pairs(Input.Inputs) do - if IsValid(V.Src) and not Checked[V.Src] and WireTable[V.Src:GetClass()] then - Checked[V.Src] = true -- We don't want to start an infinite loop + This:UpdateOverlay(true) + Crate:UpdateOverlay(true) - return FindUser(This, V.Src, Checked) - end + return true, "Weapon unlinked successfully." end - end - - function ENT:GetUser(Input) - if not IsValid(Input) then return self.Owner end - - local User = FindUser(self, Input) - - return IsValid(User) and User or self.Owner - end - function ENT:TriggerInput(Input, Value) - if self.Disabled then return end -- Ignore all input if the gun is disabled + return false, "This weapon is not linked to this crate." + end) + ACF.AddInputAction("acf_gun", "Fire", function(Entity, Value) local Bool = tobool(Value) - if Input == "Fire" then - self.Firing = Bool + Entity.Firing = Bool - if Bool and self:CanFire() then - self:Shoot() - end - elseif Input == "Fuze" then - self.SetFuze = Bool and math.abs(Value) or nil - elseif Input == "Unload" then - if Bool and self.State == "Loaded" then - self:Unload() - end - elseif Input == "Reload" then - if Bool then - if self.State == "Loaded" then - self:Unload(true) -- Unload, then reload - elseif self.State == "Empty" then - self:Load() - end - end + if Bool and Entity:CanFire() then + Entity:Shoot() end - end - - function ENT:Link(Target) - if not IsValid(Target) then return false, "Attempted to link an invalid entity." end - if self == Target then return false, "Can't link a weapon to itself." end - - local Function = ClassLink(self:GetClass(), Target:GetClass()) + end) - if Function then - return Function(self, Target) + ACF.AddInputAction("acf_gun", "Unload", function(Entity, Value) + if tobool(Value) and Entity.State == "Loaded" then + Entity:Unload() end + end) - return false, "Guns can't be linked to '" .. Target:GetClass() .. "'." - end - - function ENT:Unlink(Target) - if not IsValid(Target) then return false, "Attempted to unlink an invalid entity." end - if self == Target then return false, "Can't unlink a weapon from itself." end - - local Function = ClassUnlink(self:GetClass(), Target:GetClass()) - - if Function then - return Function(self, Target) + ACF.AddInputAction("acf_gun", "Reload", function(Entity, Value) + if tobool(Value) then + if Entity.State == "Loaded" then + Entity:Unload(true) -- Unload, then reload + elseif Entity.State == "Empty" then + Entity:Load() + end end + end) - return false, "Guns can't be unlinked from '" .. Target:GetClass() .. "'." - end + ACF.AddInputAction("acf_gun", "Fuze", function(Entity, Value) + Entity.SetFuze = tobool(Value) and math.abs(Value) + end) end ----------------------------------------- do -- Shooting ------------------------------ @@ -349,7 +372,7 @@ do -- Metamethods -------------------------------- if TraceRes.Hit then local Entity = TraceRes.Entity - if Entity == self.CurrentUser or Entity:CPPIGetOwner() == self.Owner then + if Entity == self.CurrentUser or Entity:CPPIGetOwner() == self:GetPlayer() then self.BarrelFilter[#self.BarrelFilter + 1] = Entity return self:BarrelCheck(Offset) @@ -360,13 +383,12 @@ do -- Metamethods -------------------------------- end function ENT:CanFire() - if not IsValid(self) then return false end -- Weapon doesn't exist if not self.Firing then return false end -- Nobody is holding the trigger if self.Disabled then return false end -- Disabled if self.State ~= "Loaded" then -- Weapon is not loaded if self.State == "Empty" and not self.Retry then if not self:Load() then - self:EmitSound("weapons/pistol/pistol_empty.wav", 70, 100, ACF.SoundVolume) -- Click! + self:EmitSound("weapons/pistol/pistol_empty.wav", 70, 100, ACF.Volume) -- Click! end self.Retry = true @@ -404,7 +426,7 @@ do -- Metamethods -------------------------------- local Velocity = ACF_GetAncestor(self):GetVelocity() if self.BulletData.CanFuze and self.SetFuze then - local Variance = math.Rand(-0.015, 0.015) * (20.3 - self.Caliber) * 0.1 + local Variance = math.Rand(-0.015, 0.015) * math.max(0, 203 - self.Caliber) * 0.01 self.Fuze = math.max(self.SetFuze, 0.02) + Variance -- If possible, we're gonna update the fuze time else @@ -414,13 +436,13 @@ do -- Metamethods -------------------------------- self.CurrentUser = self:GetUser(self.Inputs.Fire.Src) -- Must be updated on every shot self.BulletData.Owner = self.CurrentUser - self.BulletData.Gun = self -- because other guns share this table + self.BulletData.Gun = self -- because other guns share this table self.BulletData.Pos = self:BarrelCheck(Velocity * engine.TickInterval()) self.BulletData.Flight = Dir * self.BulletData.MuzzleVel * 39.37 + Velocity self.BulletData.Fuze = self.Fuze -- Must be set when firing as the table is shared self.BulletData.Filter = self.BarrelFilter - ACF.RoundTypes[self.BulletData.Type].create(self, self.BulletData) -- Spawn projectile + AmmoTypes[self.BulletData.Type]:Create(self, self.BulletData) -- Spawn projectile self:MuzzleEffect() self:Recoil() @@ -464,7 +486,7 @@ do -- Metamethods -------------------------------- end function ENT:Recoil() - if not ACF_RECOIL:GetBool() then return end + if not ACF.RecoilPush then return end local MassCenter = self:LocalToWorld(self:GetPhysicsObject():GetMassCenter()) local Energy = self.BulletData.ProjMass * self.BulletData.MuzzleVel * 39.37 + self.BulletData.PropMass * 3000 * 39.37 @@ -478,9 +500,7 @@ do -- Metamethods -------------------------------- if not next(Gun.Crates) then return end -- Find the next available crate to pull ammo from -- - local Current = Gun.CurrentCrate - local NextKey = (IsValid(Current) and Gun.Crates[Current]) and Current or nil - local Select = next(Gun.Crates, NextKey) or next(Gun.Crates) + local Select = next(Gun.Crates, Gun.CurrentCrate) or next(Gun.Crates) local Start = Select repeat @@ -501,7 +521,7 @@ do -- Metamethods -------------------------------- self:ReloadEffect(Reload and Time * 2 or Time) self:SetState("Unloading") - self:EmitSound("weapons/357/357_reload4.wav", 70, 100, ACF.SoundVolume) + self:EmitSound("weapons/357/357_reload4.wav", 70, 100, ACF.Volume) self.CurrentShot = 0 self.BulletData = EMPTY @@ -584,7 +604,7 @@ do -- Metamethods -------------------------------- self:SetState("Loading") if self.MagReload then -- Mag-fed/Automatically loaded - self:EmitSound("weapons/357/357_reload4.wav", 500, 100) + self:EmitSound("weapons/357/357_reload4.wav", 70, 100, ACF.Volume) self.NextFire = ACF.CurTime + self.MagReload @@ -646,50 +666,55 @@ do -- Metamethods -------------------------------- end ----------------------------------------- do -- Overlay ------------------------------- - local function Overlay(Ent) - if Ent.Disabled then - Ent:SetOverlayText("Disabled: " .. Ent.DisableReason .. "\n" .. Ent.DisableDescription) + local Text = "%s\n\nRate of Fire: %s rpm\nShots Left: %s\nAmmo Available: %s" + + function ENT:UpdateOverlayText() + local AmmoType = self.BulletData.Type .. (self.BulletData.Tracer ~= 0 and "-T" or "") + local Firerate = math.floor(60 / self.ReloadTime) + local CrateAmmo = 0 + local Status + + if not next(self.Crates) then + Status = "Not linked to an ammo crate!" else - local Status - local AmmoType = Ent.BulletData.Type .. (Ent.BulletData.Tracer ~= 0 and "-T" or "") - local Firerate = math.floor(60 / Ent.ReloadTime) - local CrateAmmo = 0 - - if Ent.DisableReason then - Status = "Disabled: " .. Ent.DisableReason - elseif not next(Ent.Crates) then - Status = "Not linked to an ammo crate!" - else - Status = Ent.State == "Loaded" and "Loaded with " .. AmmoType or Ent.State - end + Status = self.State == "Loaded" and "Loaded with " .. AmmoType or self.State + end - for Crate in pairs(Ent.Crates) do -- Tally up the amount of ammo being provided by active crates - if Crate:CanConsume() then - CrateAmmo = CrateAmmo + Crate.Ammo - end + for Crate in pairs(self.Crates) do -- Tally up the amount of ammo being provided by active crates + if Crate:CanConsume() then + CrateAmmo = CrateAmmo + Crate.Ammo end - - Ent:SetOverlayText(string.format("%s\n\nRate of Fire: %s rpm\nShots Left: %s\nAmmo Available: %s", Status, Firerate, Ent.CurrentShot, CrateAmmo)) end + + return Text:format(Status, Firerate, self.CurrentShot, CrateAmmo) end + end ----------------------------------------- - function ENT:UpdateOverlay() - if TimerExists("ACF Overlay Buffer" .. self:EntIndex()) then -- This entity has been updated too recently - self.OverlayBuffer = true -- Mark it to update when buffer time has expired - else - TimerCreate("ACF Overlay Buffer" .. self:EntIndex(), 1, 1, function() - if IsValid(self) and self.OverlayBuffer then - self.OverlayBuffer = nil - self:UpdateOverlay() - end - end) + do -- Misc ---------------------------------- + function ENT:ACF_Activate(Recalc) + local PhysObj = self.ACF.PhysObj + + if not self.ACF.Area then + self.ACF.Area = PhysObj:GetSurfaceArea() * 6.45 + end + + local Volume = PhysObj:GetVolume() * 2 + + local Armour = self.Caliber + local Health = Volume / ACF.Threshold --Setting the threshold of the prop Area gone + local Percent = 1 - Overlay(self) + if Recalc and self.ACF.Health and self.ACF.MaxHealth then + Percent = self.ACF.Health / self.ACF.MaxHealth end + + self.ACF.Health = Health * Percent + self.ACF.MaxHealth = Health + self.ACF.Armour = Armour * (0.5 + Percent * 0.5) + self.ACF.MaxArmour = Armour + self.ACF.Type = "Prop" end - end ----------------------------------------- - do -- Misc ---------------------------------- function ENT:SetState(State) self.State = State @@ -709,8 +734,8 @@ do -- Metamethods -------------------------------- if Crate:GetPos():DistToSqr(Pos) > 62500 then -- 250 unit radius self:Unlink(Crate) - self:EmitSound(UnlinkSound:format(math.random(1, 3)), 70, 100, ACF.SoundVolume) - Crate:EmitSound(UnlinkSound:format(math.random(1, 3)), 70, 100, ACF.SoundVolume) + self:EmitSound(UnlinkSound:format(math.random(1, 3)), 70, 100, ACF.Volume) + Crate:EmitSound(UnlinkSound:format(math.random(1, 3)), 70, 100, ACF.Volume) end end end @@ -737,7 +762,7 @@ do -- Metamethods -------------------------------- if not IsValid(self) then return end local Long = self.Long - local IsLong = self:GetBodygroup(Long.index) == Long.submodel + local IsLong = self:GetBodygroup(Long.Index) == Long.Submodel self.Muzzle = IsLong and self.LongMuzzle or self.NormalMuzzle end) @@ -747,6 +772,14 @@ do -- Metamethods -------------------------------- end function ENT:OnRemove() + local Class = self.ClassData + + if Class.OnLast then + Class.OnLast(self, Class) + end + + HookRun("ACF_OnEntityLast", "acf_gun", self, Class) + for Crate in pairs(self.Crates) do self:Unlink(Crate) end @@ -756,4 +789,4 @@ do -- Metamethods -------------------------------- WireLib.Remove(self) end end ----------------------------------------- -end \ No newline at end of file +end diff --git a/lua/entities/acf_gun/shared.lua b/lua/entities/acf_gun/shared.lua index 29b5b272a..071fd82cd 100644 --- a/lua/entities/acf_gun/shared.lua +++ b/lua/entities/acf_gun/shared.lua @@ -1,4 +1,9 @@ -DEFINE_BASECLASS("base_wire_entity") +DEFINE_BASECLASS("acf_base_simple") -ENT.PrintName = "ACF Gun" -ENT.WireDebugName = "ACF Gun" \ No newline at end of file +ENT.PrintName = "ACF Weapon" +ENT.WireDebugName = "ACF Weapon" +ENT.PluralName = "ACF Weapons" +ENT.IsWeapon = true + +cleanup.Register("acf_gun") +cleanup.Register("acf_smokelauncher") diff --git a/lua/entities/acf_piledriver/cl_init.lua b/lua/entities/acf_piledriver/cl_init.lua new file mode 100644 index 000000000..d445a2dce --- /dev/null +++ b/lua/entities/acf_piledriver/cl_init.lua @@ -0,0 +1,4 @@ +include("shared.lua") + +language.Add("Cleanup_acf_piledriver", "ACF Piledrivers") +language.Add("Cleaned_acf_piledriver", "Cleaned up all ACF Piledrivers") diff --git a/lua/entities/acf_piledriver/init.lua b/lua/entities/acf_piledriver/init.lua new file mode 100644 index 000000000..1cdbd10bc --- /dev/null +++ b/lua/entities/acf_piledriver/init.lua @@ -0,0 +1,467 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +local ACF = ACF +local hook = hook + +do -- Spawning and Updating -------------------- + local Piledrivers = ACF.Classes.Piledrivers + local AmmoTypes = ACF.Classes.AmmoTypes + local CheckLegal = ACF_CheckLegal + + local function VerifyData(Data) + if isstring(Data.Id) then + local OldClass = ACF.GetClassGroup(Piledrivers, Data.Id) + + if OldClass then + Data.Weapon = OldClass.ID + Data.Caliber = OldClass.Lookup[Data.Id].Caliber + end + end + + if not (isstring(Data.Weapon) and Piledrivers[Data.Weapon]) then + Data.Weapon = "PD" + end + + if not isstring(Data.Destiny) then + Data.Destiny = ACF.FindWeaponrySource(Data.Weapon) or "Piledrivers" + end + + local Class = Piledrivers[Data.Weapon] + local Bounds = Class.Caliber + + if not isnumber(Data.Caliber) then + Data.Caliber = Bounds.Base + else + Data.Caliber = math.Clamp(math.Round(Data.Caliber, 2), Bounds.Min, Bounds.Max) + end + + do -- External verifications + if Class.VerifyData then + Class.VerifyData(Data, Class) + end + + hook.Run("ACF_VerifyData", "acf_piledriver", Data, Class) + end + end + + local function CreateInputs(Entity, Data, Class) + local List = { "Fire" } + + if Class.SetupInputs then + Class.SetupInputs(List, Entity, Data, Class) + end + + hook.Run("ACF_OnSetupInputs", "acf_piledriver", List, Entity, Data, Class) + + if Entity.Inputs then + Entity.Inputs = WireLib.AdjustInputs(Entity, List) + else + Entity.Inputs = WireLib.CreateInputs(Entity, List) + end + end + + local function CreateOutputs(Entity, Data, Class) + local List = { "Ready", "Status [STRING]", "Shots Left", "Reload Time", "Rate of Fire", "Spike Mass", "Muzzle Velocity", "Entity [ENTITY]" } + + if Class.SetupOutputs then + Class.SetupOutputs(List, Entity, Data, Class) + end + + hook.Run("ACF_OnSetupOutputs", "acf_piledriver", List, Entity, Data, Class) + + if Entity.Outputs then + Entity.Outputs = WireLib.AdjustOutputs(Entity, List) + else + Entity.Outputs = WireLib.CreateOutputs(Entity, List) + end + end + + local function UpdatePiledriver(Entity, Data, Class) + local Caliber = Data.Caliber + local Scale = Caliber / Class.Caliber.Base + local Mass = math.floor(Class.Mass * Scale) + + Entity:SetModel(Class.Model) + Entity:SetScale(Scale) + + Entity.Name = Caliber .. "mm " .. Class.Name + Entity.ShortName = Caliber .. "mm" .. Class.ID + Entity.EntType = Class.Name + Entity.ClassData = Class + Entity.Caliber = Caliber + Entity.Cyclic = 60 / Class.Cyclic + Entity.MagSize = Class.MagSize or 1 + Entity.ChargeRate = Class.ChargeRate or 0.1 + Entity.SpikeLength = Class.Round.MaxLength * Scale + Entity.Muzzle = Entity:WorldToLocal(Entity:GetAttachment(Entity:LookupAttachment("muzzle")).Pos) + + CreateInputs(Entity, Data, Class) + CreateOutputs(Entity, Data, Class) + + WireLib.TriggerOutput(Entity, "Reload Time", Entity.Cyclic) + WireLib.TriggerOutput(Entity, "Rate of Fire", 60 / Entity.Cyclic) + + do -- Updating bulletdata + local Ammo = Entity.RoundData + + Data.AmmoType = "HP" + Data.Projectile = Entity.SpikeLength + + Ammo.SpikeLength = Entity.SpikeLength + + local BulletData = Ammo:ServerConvert(Data) + BulletData.Crate = Entity:EntIndex() + BulletData.Filter = { Entity } + BulletData.Gun = Entity + BulletData.Hide = true + + -- Bullet dies on the next tick + function BulletData:PreCalcFlight() + if self.KillTime then return end + if not self.DeltaTime then return end + if self.LastThink == ACF.CurTime then return end + + self.KillTime = ACF.CurTime + end + + function BulletData:OnEndFlight(Trace) + if not ACF.RecoilPush then return end + if not IsValid(Entity) then return end + if not Trace.HitWorld then return end + if Trace.Fraction == 0 then return end + + local Fraction = 1 - Trace.Fraction + local MassCenter = Entity:LocalToWorld(Entity:GetPhysicsObject():GetMassCenter()) + local Energy = self.ProjMass * self.MuzzleVel * 39.37 * Fraction + + ACF.KEShove(Entity, MassCenter, -Entity:GetForward(), Energy) + end + + Entity.BulletData = BulletData + + if Ammo.OnFirst then + Ammo:OnFirst(Entity) + end + + hook.Run("ACF_OnAmmoFirst", Ammo, Entity, Data, Class) + + Ammo:Network(Entity, Entity.BulletData) + + WireLib.TriggerOutput(Entity, "Spike Mass", math.Round(BulletData.ProjMass * 1000, 2)) + WireLib.TriggerOutput(Entity, "Muzzle Velocity", math.Round(BulletData.MuzzleVel * ACF.Scale, 2)) + end + + -- Storing all the relevant information on the entity for duping + for _, V in ipairs(Entity.DataStore) do + Entity[V] = Data[V] + end + + -- Set NWvars + Entity:SetNWString("WireName", "ACF " .. Entity.Name) + + ACF.Activate(Entity, true) + + Entity.ACF.LegalMass = Mass + Entity.ACF.Model = Class.Model + + local Phys = Entity:GetPhysicsObject() + + if IsValid(Phys) then + Phys:SetMass(Mass) + end + end + + ------------------------------------------------------------------------------- + + function MakeACF_Piledriver(Player, Pos, Angle, Data) + VerifyData(Data) + + local Class = Piledrivers[Data.Weapon] + local Limit = Class.LimitConVar.Name + + if not Player:CheckLimit(Limit) then return end + + local Entity = ents.Create("acf_piledriver") + + if not IsValid(Entity) then return end + + Player:AddCleanup(Class.Cleanup, Entity) + Player:AddCount(Limit, Entity) + + Entity:SetModel(Class.Model) -- NOTE: ENT:SetScale didn't work properly without this + Entity:SetPlayer(Player) + Entity:SetAngles(Angle) + Entity:SetPos(Pos) + Entity:Spawn() + + Entity.Owner = Player -- MUST be stored on ent for PP + Entity.RoundData = AmmoTypes.HP() + Entity.LastThink = ACF.CurTime + Entity.State = "Loading" + Entity.Firing = false + Entity.Charge = 0 + Entity.SingleCharge = 0 + Entity.CurrentShot = 0 + Entity.DataStore = ACF.GetEntityArguments("acf_piledriver") + + UpdatePiledriver(Entity, Data, Class) + + WireLib.TriggerOutput(Entity, "State", "Loading") + WireLib.TriggerOutput(Entity, "Entity", Entity) + + Entity:UpdateOverlay(true) + + CheckLegal(Entity) + + return Entity + end + + ACF.RegisterEntityClass("acf_piledriver", MakeACF_Piledriver, "Weapon", "Caliber") + + ------------------- Updating --------------------- + + function ENT:Update(Data) + VerifyData(Data) + + local Class = Piledrivers[Data.Weapon] + local OldClass = self.ClassData + + if OldClass.OnLast then + OldClass.OnLast(self, OldClass) + end + + hook.Run("ACF_OnEntityLast", "acf_piledriver", self, OldClass) + + ACF.SaveEntity(self) + + UpdatePiledriver(self, Data, Class) + + ACF.RestoreEntity(self) + + if Class.OnUpdate then + Class.OnUpdate(self, Data, Class) + end + + hook.Run("ACF_OnEntityUpdate", "acf_piledriver", self, Data, Class) + + self:UpdateOverlay(true) + + net.Start("ACF_UpdateEntity") + net.WriteEntity(self) + net.Broadcast() + + return true, "Piledriver updated successfully!" + end +end -------------------------------------------- + +do -- Entity Activation ------------------------ + function ENT:ACF_Activate(Recalc) + local PhysObj = self.ACF.PhysObj + + if not self.ACF.Area then + self.ACF.Area = PhysObj:GetSurfaceArea() + end + + local Armour = self.Caliber + local Health = PhysObj:GetVolume() * 2 / ACF.Threshold + local Percent = 1 + + if Recalc and self.ACF.Health and self.ACF.MaxHealth then + Percent = self.ACF.Health / self.ACF.MaxHealth + end + + self.ACF.Health = Health * Percent + self.ACF.MaxHealth = Health + self.ACF.Armour = Armour * (0.5 + Percent * 0.5) + self.ACF.MaxArmour = Armour + self.ACF.Type = "Prop" + end +end -------------------------------------------- + +do -- Entity Inputs ---------------------------- + ACF.AddInputAction("acf_piledriver", "Fire", function(Entity, Value) + Entity.Firing = tobool(Value) + + Entity:Shoot() + end) +end --------------------------------------------- + +do -- Entity Overlay ---------------------------- + local Text = "%s\n\nCharges Left:\n%s / %s\n[%s]\n\nRecharge State:\n%s%%\n[%s]\n\nRecharge Rate: %s charges/s\nRate of Fire: %s rpm\n\nMax Penetration: %s mm\nSpike Velocity: %s m/s\nSpike Length: %s cm\nSpike Mass: %s" + local Empty = "â–¯" + local Full = "â–®" + + local function GetChargeBar(Percentage) + local Bar = "" + + for I = 0.05, 0.95, 0.1 do + Bar = Bar .. (I <= Percentage and Full or Empty) + end + + return Bar + end + + ------------------------------------------------------------------------------- + + ENT.OverlayDelay = 0.1 + + function ENT:UpdateOverlayText() + local Shots = GetChargeBar(self.Charge / self.MagSize) + local State = GetChargeBar(self.SingleCharge) + local Current = self.CurrentShot + local Total = self.MagSize + local Percent = math.floor(self.SingleCharge * 100) + local Rate = self.ChargeRate + local RoF = self.Cyclic * 60 + local Bullet = self.BulletData + local Display = self.RoundData:GetDisplayData(Bullet) + local MaxPen = math.Round(Display.MaxPen, 2) + local Mass = ACF.GetProperMass(Bullet.ProjMass) + local MuzVel = math.Round(Bullet.MuzzleVel, 2) + local Length = Bullet.ProjLength + + return Text:format(self.State, Current, Total, Shots, Percent, State, Rate, RoF, MaxPen, MuzVel, Length, Mass) + end +end --------------------------------------------- + +do -- Firing ------------------------------------ + local Impact = "physics/metal/metal_barrel_impact_hard%s.wav" + + -- The entity won't even attempt to shoot if this function returns false + function ENT:AllowShoot() + if self.Disabled then return false end + if self.RetryShoot then return false end + + return self.Firing + end + + -- The entity should produce a "click" sound if this function returns false + function ENT:CanShoot() + if not ACF.GunfireEnabled then return false end + if not ACF.AllowFunEnts then return false end + if hook.Run("ACF_FireShell", self) == false then return false end + + return self.CurrentShot > 0 + end + + function ENT:Shoot() + if not self:AllowShoot() then return end + + local Delay = self.Cyclic + + if self:CanShoot() then + local Sound = self.SoundPath or Impact:format(math.random(5, 6)) + local Bullet = self.BulletData + + self:EmitSound(Sound, 70, math.Rand(98, 102), ACF.Volume) + self:SetSequence("load") + + Bullet.Owner = self:GetUser(self.Inputs.Fire.Src) -- Must be updated on every shot + Bullet.Pos = self:LocalToWorld(self.Muzzle) + Bullet.Flight = self:GetForward() * Bullet.MuzzleVel * 39.37 + + self.RoundData:Create(self, Bullet) + + self:Consume() + self:SetState("Loading") + + self.Loading = true + + timer.Simple(0.35, function() + if not IsValid(self) then return end + + self:SetSequence("idle") + end) + else + self:EmitSound("weapons/pistol/pistol_empty.wav", 70, math.Rand(98, 102), ACF.Volume) + + Delay = 1 + end + + if not self.RetryShoot then + self.RetryShoot = true + + timer.Simple(Delay, function() + if not IsValid(self) then return end + + self.RetryShoot = nil + + if self.Loading then + self.Loading = nil + + if self.CurrentShot > 0 then + self:SetState("Loaded") + end + end + + self:Shoot() + end) + end + end +end --------------------------------------------- + +do -- Misc -------------------------------------- + function ENT:Disable() + self.Charge = 0 + self.SingleCharge = 0 + self.CurrentShot = 0 + + self:SetState("Loading") + end + + function ENT:SetState(State) + self.State = State + + self:UpdateOverlay() + + WireLib.TriggerOutput(self, "Status", State) + WireLib.TriggerOutput(self, "Ready", State == "Loaded" and 1 or 0) + end + + function ENT:Consume(Num) + self.Charge = math.Clamp(self.Charge - (Num or 1), 0, self.MagSize) + self.CurrentShot = math.floor(self.Charge) + + WireLib.TriggerOutput(self, "Shots Left", self.CurrentShot) + + self:UpdateOverlay() + end + + function ENT:Think() + local Time = ACF.CurTime + + if not self.Disabled and self.CurrentShot < self.MagSize then + local Delta = Time - self.LastThink + local Amount = self.ChargeRate * Delta + + self:Consume(-Amount) -- Slowly recharging the piledriver + + self.SingleCharge = self.Charge - self.CurrentShot + + if not self.Loading and self.State == "Loading" and self.CurrentShot > 0 then + self:SetState("Loaded") + end + end + + self:NextThink(Time) + + self.LastThink = Time + + return true + end + + function ENT:OnRemove() + local Class = self.ClassData + + if Class.OnLast then + Class.OnLast(self, Class) + end + + hook.Run("ACF_OnEntityLast", "acf_piledriver", self, Class) + + WireLib.Remove(self) + end +end --------------------------------------------- diff --git a/lua/entities/acf_piledriver/shared.lua b/lua/entities/acf_piledriver/shared.lua new file mode 100644 index 000000000..23e0dc1f0 --- /dev/null +++ b/lua/entities/acf_piledriver/shared.lua @@ -0,0 +1,24 @@ +DEFINE_BASECLASS("acf_base_scalable") + +ENT.PrintName = "ACF Piledriver" +ENT.WireDebugName = "ACF Piledriver" +ENT.PluralName = "ACF Piledrivers" +ENT.IsPiledriver = true + +cleanup.Register("acf_piledriver") + +hook.Add("ACF_UpdateRoundData", "ACF Piledriver Ammo", function(Ammo, _, Data, GUIData) + if not Ammo.SpikeLength then return end + + local HollowCavity = GUIData.MaxCavVol * math.min(0.1 + Data.Caliber * 0.01, 1) + local ExpRatio = HollowCavity / GUIData.ProjVolume + + Data.MuzzleVel = Ammo.SpikeLength * 0.01 / engine.TickInterval() + Data.CavVol = HollowCavity + Data.ProjMass = (Data.FrArea * Data.ProjLength - HollowCavity) * 0.0079 + Data.ShovePower = 0.2 + ExpRatio * 0.5 + Data.ExpCaliber = Data.Caliber + ExpRatio * Data.ProjLength + Data.PenArea = (3.1416 * Data.ExpCaliber * 0.5) ^ 2 ^ ACF.PenAreaMod + Data.DragCoef = Data.FrArea * 0.0001 / Data.ProjMass + Data.CartMass = Data.PropMass + Data.ProjMass +end) diff --git a/lua/entities/base_scalable/cl_init.lua b/lua/entities/base_scalable/cl_init.lua index c8dba0a65..843b407c1 100644 --- a/lua/entities/base_scalable/cl_init.lua +++ b/lua/entities/base_scalable/cl_init.lua @@ -4,6 +4,30 @@ include("shared.lua") local Queued = {} +local function ChangeSize(Entity, Size) + if not isvector(Size) then return false end + if Entity.Size == Size then return false end + if not Entity:GetOriginalSize() then return false end + + local Original = Entity:GetOriginalSize() + local Scale = Vector(Size.x / Original.x, Size.y / Original.y, Size.z / Original.z) + + if Entity.ApplyNewSize then Entity:ApplyNewSize(Size, Scale) end + + Entity.Size = Size + Entity.Scale = Scale + + local PhysObj = Entity:GetPhysicsObject() + + if IsValid(PhysObj) then + if Entity.OnResized then Entity:OnResized(Size, Scale) end + + hook.Run("OnEntityResized", Entity, PhysObj, Size, Scale) + end + + return true, Size, Scale +end + function ENT:Initialize() BaseClass.Initialize(self) @@ -28,26 +52,20 @@ function ENT:GetOriginalSize() return self.OriginalSize end -function ENT:GetSize() - return self.Size -end - -function ENT:SetSize(NewSize) - if not isvector(NewSize) then return end - if self.Size == NewSize then return end - if not self:GetOriginalSize() then return end +function ENT:SetSize(Size) + if not isvector(Size) then return false end - if self.ApplyNewSize then self:ApplyNewSize(NewSize) end - - self.Size = NewSize + return ChangeSize(self, Size) +end - local PhysObj = self:GetPhysicsObject() +function ENT:SetScale(Scale) + if isnumber(Scale) then Scale = Vector(Scale, Scale, Scale) end + if not isvector(Scale) then return false end - if IsValid(PhysObj) then - if self.OnResized then self:OnResized() end + local Original = self:GetOriginalSize() + local Size = Vector(Original.x, Original.y, Original.z) * Scale - hook.Run("OnEntityResized", self, PhysObj, NewSize) - end + return ChangeSize(self, Size) end function ENT:CalcAbsolutePosition() -- Faking sync @@ -83,6 +101,10 @@ net.Receive("RequestSize", function() if not Ent.Initialized then continue end if Data.Size ~= Ent.Size or Data.Original ~= Ent.OriginalSize then + if Ent.SetExtraInfo then + Ent:SetExtraInfo(Data.Extra) + end + Ent.OriginalSize = Data.Original Ent:SetSize(Data.Size) end @@ -91,11 +113,10 @@ net.Receive("RequestSize", function() end end) --- Commented out for the moment, something's causing crashes --- TODO: Maybe hijack PhysObj:EnableMotion instead? ---hook.Add("PhysgunPickup", "Scalable Ent Physgun", function(_, Ent) - --if Ent.IsScalable then return false end ---end) +-- NOTE: Someone reported this could maybe be causing crashes. Please confirm. +hook.Add("PhysgunPickup", "Scalable Ent Physgun", function(_, Ent) + if Ent.IsScalable then return false end +end) hook.Add("NetworkEntityCreated", "Scalable Ent Full Update", function(Ent) if Ent.IsScalable then diff --git a/lua/entities/base_scalable/init.lua b/lua/entities/base_scalable/init.lua index 3a84e68fb..5686a2adb 100644 --- a/lua/entities/base_scalable/init.lua +++ b/lua/entities/base_scalable/init.lua @@ -15,7 +15,8 @@ local function GenerateJSON(Table) Data[Entity:EntIndex()] = { Original = Entity:GetOriginalSize(), - Size = Entity:GetSize() + Size = Entity:GetSize(), + Extra = Entity.GetExtraInfo and Entity:GetExtraInfo(), } end @@ -62,53 +63,76 @@ local function NetworkSize(Entity, Player) timer.Create("ACF Network Sizes", 0, 1, SendQueued) end +local function ChangeSize(Entity, Size) + if Entity.Size == Size then return false end + + local Original = Entity:GetOriginalSize() + local Scale = Vector(1 / Original.x, 1 / Original.y, 1 / Original.z) * Size + + if Entity.ApplyNewSize then Entity:ApplyNewSize(Size, Scale) end + + -- If it's not a new entity, then network the new size + -- Otherwise, the entity will request its size by itself + if Entity.Size then NetworkSize(Entity) end + + Entity.Size = Size + Entity.Scale = Scale + + local PhysObj = Entity:GetPhysicsObject() + + if IsValid(PhysObj) then + if Entity.OnResized then Entity:OnResized(Size, Scale) end + + hook.Run("OnEntityResized", Entity, PhysObj, Size, Scale) + end + + if Entity.UpdateExtraInfo then Entity:UpdateExtraInfo() end + + return true, Size, Scale +end + function ENT:Initialize() BaseClass.Initialize(self) self:GetOriginalSize() -- Instantly saving the original size end -function ENT:GetOriginalSize() - if not self.OriginalSize then - local Size = Sizes[self:GetModel()] +function ENT:FindOriginalSize(SizeTable) + local Key = self:GetModel() + local Stored = SizeTable[Key] - if not Size then - local Min, Max = self:GetPhysicsObject():GetAABB() + if Stored then return Stored end - Size = -Min + Max + local Min, Max = self:GetPhysicsObject():GetAABB() + local Size = -Min + Max - Sizes[self:GetModel()] = Size - end + SizeTable[Key] = Size + + return Size +end - self.OriginalSize = Size +function ENT:GetOriginalSize() + if not self.OriginalSize then + self.OriginalSize = self:FindOriginalSize(Sizes) end return self.OriginalSize end -function ENT:GetSize() - return self.Size -end - -function ENT:SetSize(NewSize) - if not isvector(NewSize) then return end - if self.Size == NewSize then return end +function ENT:SetSize(Size) + if not isvector(Size) then return false end - if self.ApplyNewSize then self:ApplyNewSize(NewSize) end - - -- If it's not a new entity, then network the new size - -- Otherwise, the entity will request its size by itself - if self.Size then NetworkSize(self) end - - self.Size = NewSize + return ChangeSize(self, Size) +end - local PhysObj = self:GetPhysicsObject() +function ENT:SetScale(Scale) + if isnumber(Scale) then Scale = Vector(Scale, Scale, Scale) end + if not isvector(Scale) then return false end - if IsValid(PhysObj) then - if self.OnResized then self:OnResized() end + local Original = self:GetOriginalSize() + local Size = Vector(Original.x, Original.y, Original.z) * Scale - hook.Run("OnEntityResized", self, PhysObj, NewSize) - end + return ChangeSize(self, Size) end do -- AdvDupe2 duped parented ammo workaround @@ -140,11 +164,12 @@ do -- AdvDupe2 duped parented ammo workaround hook.Add("AdvDupe_FinishPasting", "ACF Parented Scalable Ent Fix", function(DupeInfo) DupeInfo = unpack(DupeInfo) - local Entities = DupeInfo.CreatedEntities + local CanParent = tobool(DupeInfo.Player:GetInfo("advdupe2_paste_parents")) + local Entities = DupeInfo.CreatedEntities for _, Entity in pairs(Entities) do if Entity.IsScalable and Entity.ParentEnt then - Entity:SetParent(Entity.ParentEnt) + if CanParent then Entity:SetParent(Entity.ParentEnt) end Entity.ParentEnt = nil end diff --git a/lua/entities/base_scalable/shared.lua b/lua/entities/base_scalable/shared.lua index 4ea49fa76..267cd8cae 100644 --- a/lua/entities/base_scalable/shared.lua +++ b/lua/entities/base_scalable/shared.lua @@ -2,5 +2,13 @@ DEFINE_BASECLASS("base_wire_entity") ENT.PrintName = "Base Scalable Entity" ENT.WireDebugName = "Base Scalable Entity" -ENT.Contact = "Don't" +ENT.Contact = "Don't" ENT.IsScalable = true + +function ENT:GetSize() + return self.Size +end + +function ENT:GetScale() + return self.Scale +end diff --git a/lua/entities/base_scalable_box/cl_init.lua b/lua/entities/base_scalable_box/cl_init.lua index 7c67768e5..0f41ff4f1 100644 --- a/lua/entities/base_scalable_box/cl_init.lua +++ b/lua/entities/base_scalable_box/cl_init.lua @@ -1,19 +1,16 @@ include("shared.lua") -function ENT:ApplyNewSize(NewSize) - local Size = self:GetOriginalSize() - local Scale = Vector(1 / Size.x, 1 / Size.y, 1 / Size.z) * NewSize +function ENT:ApplyNewSize(NewSize, NewScale) local Bounds = NewSize * 0.5 self.Matrix = Matrix() - self.Matrix:Scale(Scale) + self.Matrix:Scale(NewScale) self:EnableMatrix("RenderMultiply", self.Matrix) self:PhysicsInitBox(-Bounds, Bounds) self:SetRenderBounds(-Bounds, Bounds) self:EnableCustomCollisions(true) - self:SetMoveType(MOVETYPE_NONE) self:DrawShadow(false) local PhysObj = self:GetPhysicsObject() diff --git a/lua/entities/base_scalable_box/init.lua b/lua/entities/base_scalable_box/init.lua index 4d23ed005..9f8a8d612 100644 --- a/lua/entities/base_scalable_box/init.lua +++ b/lua/entities/base_scalable_box/init.lua @@ -1,5 +1,6 @@ AddCSLuaFile("shared.lua") AddCSLuaFile("cl_init.lua") + include("shared.lua") function CreateScalableBox(Player, Pos, Angle, Size) @@ -13,7 +14,7 @@ function CreateScalableBox(Player, Pos, Angle, Size) Ent:SetPos(Pos) Ent:Spawn() - Ent:SetSize(Size) + Ent:SetSize(Size or VectorRand(3, 96)) Ent.Owner = Player @@ -27,7 +28,6 @@ function ENT:ApplyNewSize(NewSize) self:PhysicsInitBox(-Bounds, Bounds) self:EnableCustomCollisions(true) - self:SetSolid(SOLID_VPHYSICS) local PhysObj = self:GetPhysicsObject() diff --git a/lua/entities/base_scalable_box/shared.lua b/lua/entities/base_scalable_box/shared.lua index b1891a64e..2c2fc2b5b 100644 --- a/lua/entities/base_scalable_box/shared.lua +++ b/lua/entities/base_scalable_box/shared.lua @@ -1,4 +1,6 @@ DEFINE_BASECLASS("base_scalable") ENT.PrintName = "Scalable Box" -ENT.WireDebugName = "Scalable Box" \ No newline at end of file +ENT.WireDebugName = "Scalable Box" + +-- TODO: Either client or serverside are remaining static, fix this before using this base again. diff --git a/lua/entities/base_scalable_mconvex/cl_init.lua b/lua/entities/base_scalable_mconvex/cl_init.lua new file mode 100644 index 000000000..cd7a82fa6 --- /dev/null +++ b/lua/entities/base_scalable_mconvex/cl_init.lua @@ -0,0 +1,32 @@ +include("shared.lua") + +function ENT:SetExtraInfo(Extra) + self.Mesh = Extra.Mesh +end + +function ENT:ApplyNewSize(_, NewScale) + local Mesh = self.Mesh + + self.Matrix = Matrix() + self.Matrix:Scale(NewScale) + + self:EnableMatrix("RenderMultiply", self.Matrix) + + for I, Hull in ipairs(Mesh) do + for J, Vertex in ipairs(Hull) do + Mesh[I][J] = (Vertex.pos or Vertex) * NewScale + end + end + + self:PhysicsInitMultiConvex(Mesh) + self:EnableCustomCollisions(true) + self:SetRenderBounds(self:GetCollisionBounds()) + self:DrawShadow(false) + + local PhysObj = self:GetPhysicsObject() + + if IsValid(PhysObj) then + PhysObj:EnableMotion(false) + PhysObj:Sleep() + end +end diff --git a/lua/entities/base_scalable_mconvex/init.lua b/lua/entities/base_scalable_mconvex/init.lua new file mode 100644 index 000000000..d1852b6b7 --- /dev/null +++ b/lua/entities/base_scalable_mconvex/init.lua @@ -0,0 +1,88 @@ +AddCSLuaFile("shared.lua") +AddCSLuaFile("cl_init.lua") + +include("shared.lua") + +-- TODO: Add support for creation via vertices table instead of model + +local Meshes = {} + +function CreateScalableMultiConvex(Player, Pos, Angle, Size) + local Ent = ents.Create("base_scalable_mconvex") + + if not IsValid(Ent) then return end + + Ent:SetModel("models/props_interiors/pot01a.mdl") + Ent:SetPlayer(Player) + Ent:SetAngles(Angle) + Ent:SetPos(Pos) + Ent:Spawn() + + Ent:SetSize(Size or VectorRand(3, 96)) + + Ent.Owner = Player + + return Ent +end + +duplicator.RegisterEntityClass("base_scalable_mconvex", CreateScalableMultiConvex, "Pos", "Angle", "Size") + +function ENT:FindOriginalSize(SizeTable) + local Key = self:GetModel() + local Stored = SizeTable[Key] + + if Stored then + self.Mesh = table.Copy(Meshes[Key]) + + return Stored + end + + local PhysObj = self:GetPhysicsObject() + + if not IsValid(PhysObj) then + self:PhysicsInit(SOLID_VPHYSICS) + + PhysObj = self:GetPhysicsObject() + end + + local Min, Max = PhysObj:GetAABB() + local Mesh = PhysObj:GetMeshConvexes() + local Size = -Min + Max + + self.Mesh = table.Copy(Mesh) + + SizeTable[Key] = Size + Meshes[Key] = Mesh + + return Size +end + +function ENT:ApplyNewSize(NewSize) + local Size = self:GetSize() or self:GetOriginalSize() + local Factor = Vector(1 / Size.x, 1 / Size.y, 1 / Size.z) * NewSize + local Mesh = self.Mesh + + for I, Hull in ipairs(Mesh) do + for J, Vertex in ipairs(Hull) do + Mesh[I][J] = (Vertex.pos or Vertex) * Factor + end + end + + self:PhysicsInitMultiConvex(Mesh) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:EnableCustomCollisions(true) + self:DrawShadow(false) + + local PhysObj = self:GetPhysicsObject() + + if IsValid(PhysObj) then + PhysObj:EnableMotion(false) + end +end + +function ENT:GetExtraInfo() + return { + Mesh = Meshes[self:GetModel()] + } +end diff --git a/lua/entities/base_scalable_mconvex/shared.lua b/lua/entities/base_scalable_mconvex/shared.lua new file mode 100644 index 000000000..e83c37d82 --- /dev/null +++ b/lua/entities/base_scalable_mconvex/shared.lua @@ -0,0 +1,4 @@ +DEFINE_BASECLASS("base_scalable") + +ENT.PrintName = "Scalable Multi Convex" +ENT.WireDebugName = "Scalable Multi Convex" diff --git a/lua/entities/gmod_wire_expression2/core/custom/acffunctions.lua b/lua/entities/gmod_wire_expression2/core/custom/acffunctions.lua index 7a160eff7..7019870b4 100644 --- a/lua/entities/gmod_wire_expression2/core/custom/acffunctions.lua +++ b/lua/entities/gmod_wire_expression2/core/custom/acffunctions.lua @@ -11,13 +11,13 @@ E2Lib.RegisterExtension("acf", true) -- Local Variables and Helper Functions --===============================================================================================-- -local RestrictInfoConVar = GetConVar("sbox_acf_restrictinfo") +local ACF = ACF local AllLinkSources = ACF.GetAllLinkSources -local LinkSource = ACF.GetLinkSource -local RoundTypes = ACF.RoundTypes -local match = string.match -local floor = math.floor -local Round = math.Round +local LinkSource = ACF.GetLinkSource +local AmmoTypes = ACF.Classes.AmmoTypes +local match = string.match +local floor = math.floor +local Round = math.Round local function IsACFEntity(Entity) if not validPhysics(Entity) then return false end @@ -28,7 +28,7 @@ local function IsACFEntity(Entity) end local function RestrictInfo(Player, Entity) - if not RestrictInfoConVar:GetBool() then return false end + if not ACF.RestrictInfo then return false end return not isOwner(Player, Entity) end @@ -102,27 +102,17 @@ end -- Returns 1 if functions returning sensitive info are restricted to owned props e2function number acfInfoRestricted() - return RestrictInfoConVar:GetBool() and 1 or 0 + return ACF.RestrictInfo and 1 or 0 end __e2setcost(5) --- Returns the full name of an ACF entity, or the next projectile on a rack +-- Returns the full name of an ACF entity e2function string entity:acfName() if not IsACFEntity(this) then return "" end if RestrictInfo(self, this) then return "" end - if not this.Name then - if not this.BulletData then return "" end -- If not a a rack - if not this.BulletData.Id then return "" end - - local GunData = ACF.Weapons.Guns[this.BulletData.Id] - if not GunData then return "" end - - return GunData.name or "" - end - - return this.Name + return this.Name or "" end -- Returns the short name of an ACF entity @@ -146,7 +136,7 @@ e2function number entity:acfIsEngine() if not validPhysics(this) then return 0 end if RestrictInfo(self, this) then return 0 end - return this:GetClass() == "acf_engine" and 1 or 0 + return this.IsEngine and 1 or 0 end -- Returns 1 if the entity is an ACF gearbox @@ -154,7 +144,7 @@ e2function number entity:acfIsGearbox() if not validPhysics(this) then return 0 end if RestrictInfo(self, this) then return 0 end - return this:GetClass() == "acf_gearbox" and 1 or 0 + return this.IsGearbox and 1 or 0 end -- Returns 1 if the entity is an ACF gun @@ -162,7 +152,7 @@ e2function number entity:acfIsGun() if not validPhysics(this) then return 0 end if RestrictInfo(self, this) then return 0 end - return this:GetClass() == "acf_gun" and 1 or 0 + return this.IsWeapon and 1 or 0 end -- Returns 1 if the entity is an ACF ammo crate @@ -170,7 +160,7 @@ e2function number entity:acfIsAmmo() if not validPhysics(this) then return 0 end if RestrictInfo(self, this) then return 0 end - return this:GetClass() == "acf_ammo" and 1 or 0 + return this.IsAmmoCrate and 1 or 0 end -- Returns 1 if the entity is an ACF fuel tank @@ -178,7 +168,7 @@ e2function number entity:acfIsFuel() if not validPhysics(this) then return 0 end if RestrictInfo(self, this) then return 0 end - return this:GetClass() == "acf_fueltank" and 1 or 0 + return this.IsFuelTank and 1 or 0 end -- Returns the capacity of an acf ammo crate or fuel tank @@ -211,8 +201,6 @@ e2function void entity:acfActive(number On) if not IsACFEntity(this) then return end if not isOwner(self, this) then return end - -- Both have the same function on different entities - this:TriggerInput("Load", On) this:TriggerInput("Active", On) end @@ -220,49 +208,55 @@ end e2function number entity:acfPropHealth() if not validPhysics(this) then return 0 end if RestrictInfo(self, this) then return 0 end - if not ACF_Check(this) then return 0 end - if not this.ACF.Health then return 0 end + if not ACF.Check(this) then return 0 end - return Round(this.ACF.Health, 2) + local Health = this.ACF.Health + + return Health and Round(Health, 2) or 0 end -- Returns the current armor of an entity e2function number entity:acfPropArmor() if not validPhysics(this) then return 0 end if RestrictInfo(self, 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 ACF.Check(this) then return 0 end + + local Armor = this.ACF.Armour - return Round(this.ACF.Armour, 2) + return Armor and Round(Armor, 2) or 0 end -- Returns the max health of an entity e2function number entity:acfPropHealthMax() if not validPhysics(this) then return 0 end if RestrictInfo(self, 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 ACF.Check(this) then return 0 end + + local MaxHealth = this.ACF.MaxHealth - return Round(this.ACF.MaxHealth, 2) + return MaxHealth and Round(MaxHealth, 2) or 0 end -- Returns the max armor of an entity e2function number entity:acfPropArmorMax() if not validPhysics(this) then return 0 end if RestrictInfo(self, this) then return 0 end - if not ACF_Check(this) then return 0 end + if not ACF.Check(this) then return 0 end - return Round(this.ACF.MaxArmour or 0, 2) + local MaxArmor = this.ACF.MaxArmour + + return MaxArmor and Round(MaxArmor, 2) or 0 end -- Returns the ductility of an entity e2function number entity:acfPropDuctility() if not validPhysics(this) then return 0 end if RestrictInfo(self, 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 ACF.Check(this) then return 0 end + + local Ductility = this.ACF.Ductility - return this.ACF.Ductility * 100 + return Ductility and Ductility * 100 or 0 end __e2setcost(10) @@ -276,12 +270,12 @@ end e2function number ranger:acfEffectiveArmor() if not (this and validPhysics(this.Entity)) then return 0 end if RestrictInfo(self, this.Entity) then return 0 end - if not ACF_Check(this.Entity) then return 0 end - if not this.Entity.ACF.Armour then return 0 end + if not ACF.Check(this.Entity) then return 0 end + local Armor = this.Entity.ACF.Armour local HitAngle = ACF_GetHitAngle(this.HitNormal , this.HitPos - this.StartPos) - return Round(this.Entity.ACF.Armour / math.abs(math.cos(math.rad(HitAngle))), 2) + return Round(Armor / math.abs(math.cos(math.rad(HitAngle))), 2) end __e2setcost(20) @@ -291,7 +285,7 @@ e2function number entity:acfHitClip(vector HitPos) if not validPhysics(this) then return 0 end if RestrictInfo(self, this) then return 0 end - return ACF_CheckClips(this, HitPos) and 1 or 0 + return ACF.CheckClips(this, HitPos) and 1 or 0 end -- Returns all the linked entities @@ -299,15 +293,13 @@ e2function array entity:acfLinks() if not IsACFEntity(this) then return {} end if RestrictInfo(self, this) then return {} end - local Sources = AllLinkSources(this:GetClass()) local Result = {} local Count = 0 - for _, Function in pairs(Sources) do - for Entity in pairs(Function(this)) do - Count = Count + 1 - Result[Count] = Entity - end + for Entity in pairs(ACF.GetLinkedEntities(this)) do + Count = Count + 1 + + Result[Count] = Entity end return Result @@ -319,7 +311,7 @@ e2function number entity:acfLinkTo(entity Target, number Notify) if not validPhysics(Target) then return 0 end if not (isOwner(self, this) and isOwner(self, Target)) then if Notify ~= 0 then - ACF_SendNotify(self.player, 0, "Must be called on entities you own.") + ACF.SendNotify(self.player, 0, "Must be called on entities you own.") end return 0 @@ -327,7 +319,7 @@ e2function number entity:acfLinkTo(entity Target, number Notify) if not this.Link then if Notify ~= 0 then - ACF_SendNotify(self.player, 0, "This entity is not linkable.") + ACF.SendNotify(self.player, 0, "This entity is not linkable.") end return 0 @@ -336,7 +328,7 @@ e2function number entity:acfLinkTo(entity Target, number Notify) local Sucess, Message = this:Link(Target) if Notify ~= 0 then - ACF_SendNotify(self.player, Sucess, Message) + ACF.SendNotify(self.player, Sucess, Message) end return Sucess and 1 or 0 @@ -348,7 +340,7 @@ e2function number entity:acfUnlinkFrom(entity Target, number Notify) if not validPhysics(Target) then return 0 end if not (isOwner(self, this) and isOwner(self, Target)) then if Notify ~= 0 then - ACF_SendNotify(self.player, 0, "Must be called on entities you own.") + ACF.SendNotify(self.player, 0, "Must be called on entities you own.") end return 0 @@ -356,7 +348,7 @@ e2function number entity:acfUnlinkFrom(entity Target, number Notify) if not this.Unlink then if Notify ~= 0 then - ACF_SendNotify(self.player, 0, "This entity is not linkable.") + ACF.SendNotify(self.player, 0, "This entity is not linkable.") end return 0 @@ -365,7 +357,7 @@ e2function number entity:acfUnlinkFrom(entity Target, number Notify) local Sucess, Message = this:Unlink(Target) if Notify > 0 then - ACF_SendNotify(self.player, Sucess, Message) + ACF.SendNotify(self.player, Sucess, Message) end return Sucess and 1 or 0 @@ -461,18 +453,20 @@ end e2function number entity:acfRPM() if not IsACFEntity(this) then return 0 end if RestrictInfo(self, this) then return 0 end - if not this.FlyRPM then return 0 end - return floor(this.FlyRPM) + local FlyRPM = this.FlyRPM + + return FlyRPM and floor(FlyRPM) or 0 end -- Returns the current torque of an ACF engine e2function number entity:acfTorque() if not IsACFEntity(this) then return 0 end if RestrictInfo(self, this) then return 0 end - if not this.Torque then return 0 end - return floor(this.Torque) + local Torque = this.Torque + + return Torque and floor(Torque) or 0 end -- Returns the inertia of an ACF engine's flywheel @@ -487,9 +481,8 @@ end e2function number entity:acfFlyMass() if not IsACFEntity(this) then return 0 end if RestrictInfo(self, this) then return 0 end - if not this.Inertia then return 0 end - return (this.Inertia / 3.1416) * (this.Inertia / 3.1416) + return this.FlywheelMass or 0 end -- Returns the current power of an ACF engine @@ -531,9 +524,10 @@ end e2function number entity:acfThrottle() if not IsACFEntity(this) then return 0 end if RestrictInfo(self, this) then return 0 end - if not this.Throttle then return 0 end - return this.Throttle * 100 + local Throttle = this.Throttle + + return Throttle and Throttle * 100 or 0 end -- Sets the throttle value for an ACF engine @@ -557,16 +551,15 @@ e2function number entity:acfNumGears() if not IsACFEntity(this) then return 0 end if RestrictInfo(self, this) then return 0 end - return this.Gears or 0 + return this.GearCount or 0 end -- Returns the final ratio for an ACF gearbox e2function number entity:acfFinalRatio() if not IsACFEntity(this) then return 0 end if RestrictInfo(self, this) then return 0 end - if not this.GearTable then return 0 end - return this.GearTable.Final or 0 + return this.FinalDrive or 0 end -- Returns the total ratio (current gear * final) for an ACF gearbox @@ -590,16 +583,17 @@ e2function number entity:acfIsDual() if not IsACFEntity(this) then return 0 end if RestrictInfo(self, this) then return 0 end - return this.Dual and 1 or 0 + return this.DualClutch and 1 or 0 end -- Returns the time in ms an ACF gearbox takes to change gears e2function number entity:acfShiftTime() if not IsACFEntity(this) then return 0 end if RestrictInfo(self, this) then return 0 end - if not this.SwitchTime then return 0 end - return this.SwitchTime * 1000 + local Time = this.SwitchTime + + return Time and Time * 1000 or 0 end -- Returns 1 if an ACF gearbox is in gear @@ -614,12 +608,9 @@ end e2function number entity:acfGearRatio(number Gear) if not IsACFEntity(this) then return 0 end if RestrictInfo(self, this) then return 0 end - if not this.GearTable then return 0 end if not this.Gears then return 0 end - local GearNum = math.Clamp(floor(Gear), 1, this.Gears) - - return this.GearTable[GearNum] or 0 + return this.Gears[floor(Gear)] or 0 end -- Returns the current torque output for an ACF gearbox @@ -634,7 +625,6 @@ end e2function void entity:acfCVTRatio(number Ratio) if not IsACFEntity(this) then return end if not isOwner(self, this) then return end - if not this.CVT then return end this:TriggerInput("CVT Ratio", math.Clamp(Ratio, 0, 1)) end @@ -675,7 +665,6 @@ end e2function void entity:acfBrakeLeft(number Brake) if not IsACFEntity(this) then return end if not isOwner(self, this) then return end - if not this.Dual then return end this:TriggerInput("Left Brake", Brake) end @@ -684,7 +673,6 @@ end e2function void entity:acfBrakeRight(number Brake) if not IsACFEntity(this) then return end if not isOwner(self, this) then return end - if not this.Dual then return end this:TriggerInput("Right Brake", Brake) end @@ -701,7 +689,6 @@ end e2function void entity:acfClutchLeft(number Clutch) if not IsACFEntity(this) then return end if not isOwner(self, this) then return end - if not this.Dual then return end this:TriggerInput("Left Clutch", Clutch) end @@ -710,7 +697,6 @@ end e2function void entity:acfClutchRight(number Clutch) if not IsACFEntity(this) then return end if not isOwner(self, this) then return end - if not this.Dual then return end this:TriggerInput("Right Clutch", Clutch) end @@ -719,7 +705,6 @@ end e2function void entity:acfSteerRate(number Rate) if not IsACFEntity(this) then return end if not isOwner(self, this) then return end - if not this.DoubleDiff then return end this:TriggerInput("Steer Rate", Rate) end @@ -728,7 +713,6 @@ end e2function void entity:acfHoldGear(number Hold) if not IsACFEntity(this) then return end if not isOwner(self, this) then return end - if not this.Auto then return end this:TriggerInput("Hold Gear", Hold) end @@ -737,7 +721,6 @@ end e2function void entity:acfShiftPointScale(number Scale) if not IsACFEntity(this) then return end if not isOwner(self, this) then return end - if not this.Auto then return end this:TriggerInput("Shift Speed Scale", Scale) end @@ -911,9 +894,10 @@ end e2function number entity:acfFireRate() if not IsACFEntity(this) then return 0 end if RestrictInfo(self, this) then return 0 end - if not this.ReloadTime then return 0 end - return Round(60 / this.ReloadTime, 2) + local Time = this.ReloadTime + + return Time and Round(60 / Time, 2) or 0 end -- Returns the number of rounds left in a magazine for an ACF gun @@ -961,135 +945,113 @@ e2function string entity:acfRoundType() if not IsACFEntity(this) then return "" end if RestrictInfo(self, this) then return "" end - return this.RoundType or "" + local BulletData = this.BulletData + + return BulletData and BulletData.Id or "" end -- Returns the type of ammo in a crate or gun e2function string entity:acfAmmoType() if not IsACFEntity(this) then return "" end if RestrictInfo(self, this) then return "" end - if not this.BulletData then return "" end - return this.BulletData.Type or "" + local BulletData = this.BulletData + + return BulletData and BulletData.Type or "" end --- Returns the caliber of an ammo, gun or rack +-- Returns the caliber of an ammo e2function number entity:acfCaliber() if not IsACFEntity(this) then return 0 end if RestrictInfo(self, this) then return 0 end - if not this.Caliber then -- If not a gun or ammo crate - if not this.BulletData then return 0 end -- If not a a rack - if not this.BulletData.Id then return 0 end - local GunData = ACF.Weapons.Guns[this.BulletData.Id] - - if not GunData then return 0 end - - return GunData.caliber * 10 or 0 - end - - return this.Caliber * 10 + return this.Caliber or 0 end -- Returns the muzzle velocity of the ammo in a crate or gun e2function number entity:acfMuzzleVel() if not IsACFEntity(this) then return 0 end if RestrictInfo(self, this) then return 0 end - if not this.BulletData then return 0 end - if not this.BulletData.MuzzleVel then return 0 end - return this.BulletData.MuzzleVel * ACF.Scale + local BulletData = this.BulletData + local MuzzleVel = BulletData and BulletData.MuzzleVel + + return MuzzleVel and MuzzleVel * ACF.Scale or 0 end -- Returns the mass of the projectile in a crate or gun e2function number entity:acfProjectileMass() if not IsACFEntity(this) then return 0 end if RestrictInfo(self, this) then return 0 end - if not this.BulletData then return 0 end - if not this.BulletData.ProjMass then return 0 end - return this.BulletData.ProjMass + local BulletData = this.BulletData + + return BulletData and BulletData.ProjMass or 0 end e2function number entity:acfDragCoef() if not IsACFEntity(this) then return 0 end if RestrictInfo(self, this) then return 0 end - if not this.BulletData then return 0 end - if not this.BulletData.DragCoef then return 0 end - return this.BulletData.DragCoef / ACF.DragDiv + local BulletData = this.BulletData + local DragCoef = BulletData and BulletData.DragCoef + + return DragCoef and DragCoef / ACF.DragDiv or 0 end -- Returns the fin multiplier of the missile/bomb e2function number entity:acfFinMul() if not IsACFEntity(this) then return 0 end if RestrictInfo(self, this) then return 0 end - if not this.BulletData then return 0 end - if not this.BulletData.Id then return 0 end - - local GunData = ACF.Weapons.Guns[this.BulletData.Id] - - if not GunData then return 0 end - if not GunData.round then return 0 end - return GunData.round.finmul or 0 + return this.FinMultiplier or 0 end -- Returns the weight of the missile e2function number entity:acfMissileWeight() if not IsACFEntity(this) then return 0 end if RestrictInfo(self, this) then return 0 end - if not this.BulletData then return 0 end - if not this.BulletData.Id then return 0 end - local GunData = ACF.Weapons.Guns[this.BulletData.Id] - - if not GunData then return 0 end - - return GunData.weight or 0 + return this.ForcedMass or 0 end -- Returns the length of the missile e2function number entity:acfMissileLength() if not IsACFEntity(this) then return 0 end if RestrictInfo(self, this) then return 0 end - if not this.BulletData then return 0 end - if not this.BulletData.Id then return 0 end - - local GunData = ACF.Weapons.Guns[this.BulletData.Id] - if not GunData then return 0 end - - return GunData.length or 0 + return this.Length or 0 end -- Returns the number of projectiles in a flechette round e2function number entity:acfFLSpikes() if not IsACFEntity(this) then return 0 end if RestrictInfo(self, this) then return 0 end - if not this.BulletData then return 0 end - return this.BulletData.Flechettes or 0 + local BulletData = this.BulletData + + return BulletData and BulletData.Flechettes or 0 end -- Returns the mass of a single spike in a FL round in a crate or gun e2function number entity:acfFLSpikeMass() if not IsACFEntity(this) then return 0 end if RestrictInfo(self, this) then return 0 end - if not this.BulletData then return 0 end - if not this.BulletData.FlechetteMass then return 0 end - return this.BulletData.FlechetteMass + local BulletData = this.BulletData + + return BulletData and BulletData.FlechetteMass or 0 end -- Returns the radius of the spikes in a flechette round in mm e2function number entity:acfFLSpikeRadius() if not IsACFEntity(this) then return 0 end if RestrictInfo(self, this) then return 0 end - if not this.BulletData then return 0 end - if not this.BulletData.FlechetteRadius then return 0 end - return Round(this.BulletData.FlechetteRadius * 10, 2) + local BulletData = this.BulletData + local Radius = BulletData and BulletData.FlechetteRadius + + return Radius and Round(Radius * 10, 2) or 0 end __e2setcost(10) @@ -1098,38 +1060,32 @@ __e2setcost(10) e2function number entity:acfPenetration() if not IsACFEntity(this) then return 0 end if RestrictInfo(self, this) then return 0 end - if not this.BulletData then return 0 end - if not this.BulletData.Type then return 0 end local BulletData = this.BulletData - local RoundData = RoundTypes[BulletData.Type] - - if not RoundData then return 0 end + local AmmoType = BulletData and AmmoTypes[BulletData.Type] - local DisplayData = RoundData.getDisplayData(BulletData) + if not AmmoType then return 0 end - if not DisplayData.MaxPen then return 0 end + local DisplayData = AmmoType:GetDisplayData(BulletData) + local MaxPen = DisplayData and DisplayData.MaxPen - return Round(DisplayData.MaxPen, 2) + return MaxPen and Round(MaxPen, 2) or 0 end -- Returns the blast radius of an ACF round e2function number entity:acfBlastRadius() if not IsACFEntity(this) then return 0 end if RestrictInfo(self, this) then return 0 end - if not this.BulletData then return 0 end - if not this.BulletData.Type then return 0 end local BulletData = this.BulletData - local RoundData = RoundTypes[BulletData.Type] - - if not RoundData then return 0 end + local AmmoType = BulletData and AmmoTypes[BulletData.Type] - local DisplayData = RoundData.getDisplayData(BulletData) + if not AmmoType then return 0 end - if not DisplayData.BlastRadius then return 0 end + local DisplayData = AmmoType:GetDisplayData(BulletData) + local Radius = DisplayData and DisplayData.BlastRadius - return Round(DisplayData.BlastRadius, 2) + return Radius and Round(Radius, 2) or 0 end --Returns the number of rounds in active ammo crates linked to an ACF weapon diff --git a/lua/starfall/libs_sv/acffunctions.lua b/lua/starfall/libs_sv/acffunctions.lua index 791b07ea4..80c2e44a1 100644 --- a/lua/starfall/libs_sv/acffunctions.lua +++ b/lua/starfall/libs_sv/acffunctions.lua @@ -7,7 +7,7 @@ -- #gearbox -- #gun ---use an input to set reload manually, to remove timer? +--use an input to set reload manually, to remove timer? (what?) -- #ammo @@ -18,57 +18,104 @@ -- #fuel -local checkluatype = SF.CheckLuaType -local checkpermission = SF.Permissions.check -local registerprivilege = SF.Permissions.registerPrivilege +local ACF = ACF +local math = math +local match = string.match +local AmmoTypes = ACF.Classes.AmmoTypes +local Engines = ACF.Classes.Engines +local FuelTanks = ACF.Classes.FuelTanks +local FuelTypes = ACF.Classes.FuelTypes +local Gearboxes = ACF.Classes.Gearboxes +local Weapons = ACF.Classes.Weapons +local CheckLuaType = SF.CheckLuaType +local CheckPerms = SF.Permissions.check +local RegisterPrivilege = SF.Permissions.registerPrivilege + +local Ignored = { + LimitConVar = true, + BaseClass = true, + Loaded = true, + Lookup = true, + Class = true, + Count = true, + Items = true, +} -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 = {} }) +RegisterPrivilege("acf.createAmmo", "Create ACF Ammo Crate", "Allows the user to create ACF Ammo Crates", { usergroups = { default = 3 } }) +RegisterPrivilege("acf.createEngine", "Create ACF Engine", "Allows the user to create ACF Engines", { usergroups = { default = 3 } }) +RegisterPrivilege("acf.createFuelTank", "Create ACF Fuel Tank", "Allows the user to create ACF Fuel Tanks", { usergroups = { default = 3 } }) +RegisterPrivilege("acf.createGearbox", "Create ACF Gearbox", "Allows the user to create ACF Gearboxes", { usergroups = { default = 3 } }) +RegisterPrivilege("acf.createWeapon", "Create ACF Weapon", "Allows the user to create ACF Weapons", { usergroups = { default = 3 } }) +RegisterPrivilege("entities.acf", "ACF", "Allows the user to control ACF components", { entities = {} }) 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.") -- [ Helper Functions ] -- -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 IsACFEntity(Entity) + if not ACF.Check(Entity) then return false end -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 Match = match(Entity:GetClass(), "^acf_") -local function isGun ( ent ) - if not validPhysics( ent ) then return false end - if ( ent:GetClass() == "acf_gun" ) then return true else return false end + return Match and true or false end -local function isRack ( ent ) - if not validPhysics( ent ) then return false end - if ( ent:GetClass() == "acf_rack" ) then return true else return false end -end +local function GetReloadTime(Entity) + local Unloading = Entity.State == "Unloading" + local NewLoad = Entity.State ~= "Loaded" and Entity.CurrentShot == 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 + return (Unloading or NewLoad) and Entity.MagReload or Entity.ReloadTime or 0 end -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 +local function GetMaxPower(Entity) + if not Entity.PeakTorque then return 0 end + + local MaxPower + + if Entity.IsElectric then + if not Entity.LimitRPM then return 0 end + + MaxPower = math.floor(Entity.PeakTorque * Entity.LimitRPM / 38195.2) --(4*9548.8) + else + if not Entity.PeakMaxRPM then return 0 end + + MaxPower = math.floor(Entity.PeakTorque * Entity.PeakMaxRPM / 9548.8) + end -local function reloadTime( ent ) - if ent.CurrentShot and ent.CurrentShot > 0 then return ent.ReloadTime end - return ent.MagReload + return MaxPower end -local propProtectionInstalled = FindMetaTable("Entity").CPPIGetOwner and true +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) + + 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 + end + + return Linked +end ---------------------------------------- -- ACF Library @@ -80,41 +127,83 @@ SF.RegisterLibrary("acf") -- Local to each starfall return function(instance) -- Called for library declarations - -local checktype = instance.CheckType +local CheckType = instance.CheckType local acf_library = instance.Libraries.acf local owrap, ounwrap = instance.WrapObject, instance.UnwrapObject -local ents_methods, ent_meta, wrap, unwrap = 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 +local ents_methods, wrap, unwrap = instance.Types.Entity.Methods, instance.Types.Entity.Wrap, instance.Types.Entity.Unwrap +local ang_meta, aunwrap = instance.Types.Angle, instance.Types.Angle.Unwrap +local vec_meta, vunwrap = instance.Types.Vector, instance.Types.Vector.Unwrap +local function RestrictInfo(Entity) + if not ACF.RestrictInfo then return false 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 + return not isOwner(instance, Entity) +end + +local function WrapTable(Table, Ignore, Checked) + local Result = {} + + if not Checked then Checked = {} end + if not Ignore then Ignore = {} end + + for K, V in pairs(Table) do + if istable(V) then + if not (Ignore[K] or Checked[V]) then + Result[K] = WrapTable(V, Ignore, Checked) + Checked[V] = true + end + elseif not Ignore[K] then + Result[K] = owrap(V) + end + end + + return Result +end + +local function UnwrapTable(Table, Checked) + local Result = {} + + if not Checked then Checked = {} end + + for K, V in pairs(Table) do + if istable(V) then + if not Checked[V] then + Result[K] = ounwrap(V) or UnwrapTable(V, Checked) + Checked[V] = true + end + else + Result[K] = ounwrap(V) + end end - return false + + return Result end -local function propOnDestroy(ent, instance) - local ply = instance.player - plyCount:free(ply, 1) - instance.data.props.props[ent] = nil +local function OnRemove(Entity, Player) + plyCount:free(Player, 1) + + instance.data.props.props[Entity] = nil end -local function register(ent, instance) - ent:CallOnRemove("starfall_prop_delete", propOnDestroy, instance) - plyCount:free(instance.player, -1) - instance.data.props.props[ent] = true +local function RegisterEntity(Entity) + local Player = instance.player + + Entity:CallOnRemove("starfall_prop_delete", OnRemove, Player) + + plyCount:free(Player, -1) + + instance.data.props.props[Entity] = true end +--===============================================================================================-- +-- General Functions +--===============================================================================================-- --- 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 + return ACF.RestrictInfo end --- Returns current ACF drag divisor @@ -128,2275 +217,2005 @@ end -- @server -- @return The effective armor function acf_library.effectiveArmor(armor, angle) - checkluatype(armor, TYPE_NUMBER) - checkluatype(angle, TYPE_NUMBER) - + 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 --- 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 +--- Creates an ACF ammo crate using the information from the data table argument +-- @server +function acf_library.createAmmo(pos, ang, data) + CheckPerms(instance, nil, "acf.createAmmo") + + local Player = instance.player + + if not hook.Run("CanTool", Player, { Hit = true, Entity = game.GetWorld() }, "acf_menu") then + SF.Throw("No permission to spawn ACF components", 2) end + + CheckType(pos, vec_meta) + CheckType(ang, ang_meta) + CheckLuaType(data, TYPE_TABLE) + + local Position = SF.clampPos(vunwrap(pos)) + local Angles = aunwrap(ang) + local Data = UnwrapTable(data) + local Undo = not instance.data.props.undo + + local Success, Entity = ACF.CreateEntity("acf_ammo", Player, Position, Angles, Data, Undo) + + if not Success then SF.Throw("Unable to create ACF Ammo Crate", 2) end + + plyBurst:use(Player, 1) + plyCount:checkuse(Player, 1) + + RegisterEntity(Entity) + + return owrap(Entity) end ---- 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 +--- Creates an ACF engine using the information from the data table argument +-- @server +function acf_library.createEngine(pos, ang, data) + CheckPerms(instance, nil, "acf.createEngine") + + local Player = instance.player + + if not hook.Run("CanTool", Player, { Hit = true, Entity = game.GetWorld() }, "acf_menu") then + SF.Throw("No permission to spawn ACF components", 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 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 + + CheckType(pos, vec_meta) + CheckType(ang, ang_meta) + CheckLuaType(data, TYPE_TABLE) + + local Position = SF.clampPos(vunwrap(pos)) + local Angles = aunwrap(ang) + local Data = UnwrapTable(data) + local Undo = not instance.data.props.undo + + local Success, Entity = ACF.CreateEntity("acf_engine", Player, Position, Angles, Data, Undo) + + if not Success then SF.Throw("Unable to create ACF Engine", 2) end + + plyBurst:use(Player, 1) + plyCount:checkuse(Player, 1) + + RegisterEntity(Entity) + + return owrap(Entity) +end + +--- Creates an ACF fuel tank using the information from the data table argument +-- @server +function acf_library.createFuelTank(pos, ang, data) + CheckPerms(instance, nil, "acf.createFuelTank") + + local Player = instance.player + + if not hook.Run("CanTool", Player, { Hit = true, Entity = game.GetWorld() }, "acf_menu") then + SF.Throw("No permission to spawn ACF components", 2) end - - local ent = dupe_class.Func(ply, unpack(args_table)) - ent:Activate() - - local phys = ent:GetPhysicsObject() - if phys:IsValid() then - phys:EnableMotion(not frozen) + + CheckType(pos, vec_meta) + CheckType(ang, ang_meta) + CheckLuaType(data, TYPE_TABLE) + + local Position = SF.clampPos(vunwrap(pos)) + local Angles = aunwrap(ang) + local Data = UnwrapTable(data) + local Undo = not instance.data.props.undo + + local Success, Entity = ACF.CreateEntity("acf_fueltank", Player, Position, Angles, Data, Undo) + + if not Success then SF.Throw("Unable to create ACF Fuel Tank", 2) end + + plyBurst:use(Player, 1) + plyCount:checkuse(Player, 1) + + RegisterEntity(Entity) + + return owrap(Entity) +end + +--- Creates an ACF gearbox using the information from the data table argument +-- @server +function acf_library.createGearbox(pos, ang, data) + CheckPerms(instance, nil, "acf.createGearbox") + + local Player = instance.player + + if not hook.Run("CanTool", Player, { Hit = true, Entity = game.GetWorld() }, "acf_menu") then + SF.Throw("No permission to spawn ACF components", 2) end - - if instance.data.props.undo then - undo.Create("ACF Mobility") - undo.SetPlayer(ply) - undo.AddEntity(ent) - undo.Finish("ACF Mobility (" .. tostring(id) .. ")") + + CheckType(pos, vec_meta) + CheckType(ang, ang_meta) + CheckLuaType(data, TYPE_TABLE) + + local Position = SF.clampPos(vunwrap(pos)) + local Angles = aunwrap(ang) + local Data = UnwrapTable(data) + local Undo = not instance.data.props.undo + + local Success, Entity = ACF.CreateEntity("acf_gearbox", Player, Position, Angles, Data, Undo) + + if not Success then SF.Throw("Unable to create ACF Gearbox", 2) end + + plyBurst:use(Player, 1) + plyCount:checkuse(Player, 1) + + RegisterEntity(Entity) + + return owrap(Entity) +end + +--- Creates an ACF weapon using the information from the data table argument +-- @server +function acf_library.createWeapon(pos, ang, data) + CheckPerms(instance, nil, "acf.createWeapon") + + local Player = instance.player + + if not hook.Run("CanTool", Player, { Hit = true, Entity = game.GetWorld() }, "acf_menu") then + SF.Throw("No permission to spawn ACF components", 2) end - - ply:AddCleanup("props", ent) - register(ent, instance) - - return owrap(ent) -end - ---- 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 + + CheckType(pos, vec_meta) + CheckType(ang, ang_meta) + CheckLuaType(data, TYPE_TABLE) + + local Position = SF.clampPos(vunwrap(pos)) + local Angles = aunwrap(ang) + local Data = UnwrapTable(data) + local Undo = not instance.data.props.undo + + local Success, Entity = ACF.CreateEntity("acf_gun", Player, Position, Angles, Data, Undo) + + if not Success then SF.Throw("Unable to create ACF Weapon", 2) end + + plyBurst:use(Player, 1) + plyCount:checkuse(Player, 1) + + RegisterEntity(Entity) + + return owrap(Entity) +end + +--- Returns a list of every registered ACF ammo type +-- @server +function acf_library.listAllAmmoTypes() + local Result = {} + local Count = 0 + + for ID in pairs(AmmoTypes) do + Count = Count + 1 + + Result[Count] = ID end - - local specs = table.Copy(list_entries[id]) - specs.BaseClass = nil - - return specs + + return Result end ---- Returns a list of all mobility components +--- Returns a list of every registered ACF engine class -- @server --- @return The mobility component list -function acf_library.getAllMobility() - local list = {} - - for id, _ in pairs(ACF.Weapons.Mobility) do - table.insert(list, id) +function acf_library.listAllEngineClasses() + local Result = {} + local Count = 0 + + for _, Class in pairs(Engines) do + Count = Count + 1 + + Result[Count] = Class.ID end - - return list + + return Result end ---- Returns a list of all engines +--- Returns a list of every registered ACF engine -- @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) +function acf_library.listAllEngines() + local Result = {} + local Count = 0 + + for _, Class in pairs(Engines) do + for _, Engine in ipairs(Class.Items) do + Count = Count + 1 + + Result[Count] = Engine.ID end end - - return list + + return Result end ---- Returns a list of all gearboxes +--- Returns a list of every registered ACF fuel tank class -- @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 +function acf_library.listAllFuelTankClasses() + local Result = {} + local Count = 0 + + for _, Class in pairs(FuelTanks) do + Count = Count + 1 + + Result[Count] = Class.ID end - - return list -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) + + return Result +end + +--- Returns a list of every registered ACF fuel tank +-- @server +function acf_library.listAllFuelTanks() + local Result = {} + local Count = 0 + + for _, Class in pairs(FuelTanks) do + for _, Tank in ipairs(Class.Items) do + Count = Count + 1 + + Result[Count] = Tank.ID + end 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) .. ")") + + return Result +end + +--- Returns a list of every registered ACF fuel type +-- @server +function acf_library.listAllFuelTypes() + local Result = {} + local Count = 0 + + for ID in pairs(FuelTypes) do + Count = Count + 1 + + Result[Count] = ID end - - ply:AddCleanup("props", ent) - register(ent, instance) - - return owrap(ent) + + return Result end ---- Returns the specs of the fuel tank --- @param id id of the engine or gearbox +--- Returns a list of every registered ACF gearbox class -- @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 - ---- 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) +function acf_library.listAllGearboxClasses() + local Result = {} + local Count = 0 + + for _, Class in pairs(Gearboxes) do + Count = Count + 1 + + Result[Count] = Class.ID end - - return list -end - ---- 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) + + return Result +end + +--- Returns a list of every registered ACF gearbox +-- @server +function acf_library.listAllGearboxes() + local Result = {} + local Count = 0 + + for _, Class in pairs(Gearboxes) do + for _, Gearbox in ipairs(Class.Items) do + Count = Count + 1 + + Result[Count] = Gearbox.ID 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) .. ")") + + return Result +end + +--- Returns a list of every registered ACF weapon class +-- @server +function acf_library.listAllWeaponClasses() + local Result = {} + local Count = 0 + + for _, Class in pairs(Weapons) do + Count = Count + 1 + + Result[Count] = Class.ID end - - ply:AddCleanup("props", ent) - register(ent, instance) - - return owrap(ent) -end - ---- 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) + + return Result +end + +--- Returns a list of every registered ACF weapon +-- @server +function acf_library.listAllWeapons() + local Result = {} + local Count = 0 + + for _, Class in pairs(Weapons) do + for _, Weapon in ipairs(Class.Items) do + Count = Count + 1 + + Result[Count] = Weapon.ID end end - - local specs = table.Copy(list_entries[id]) - specs.BaseClass = nil - - return specs + + return Result end ---- Returns a list of all guns +--- Returns the specifications of an ACF ammo type +-- @param id The ID of the engine class you want to get the information from -- @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 - --- 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 - --- 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 - } -} +function acf_library.getAmmoTypeSpecs(id) + CheckLuaType(id, TYPE_STRING) -ammo_properties.AP.create_data = { - propellantLength = ammo_property_data.propellantLength, - projectileLength = ammo_property_data.projectileLength, - tracer = ammo_property_data.tracer -} + local Ammo = AmmoTypes[id] -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 -} + if not Ammo then SF.Throw("Invalid ammo type ID, not found.", 2) end -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 -} + return WrapTable(Ammo, Ignored) +end -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 -} +--- Returns the specifications of an ACF engine class +-- @param id The ID of the engine class you want to get the information from +-- @server +function acf_library.getEngineClassSpecs(id) + CheckLuaType(id, TYPE_STRING) -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 -} + local Class = Engines[id] -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 -} + if not Class then SF.Throw("Invalid engine class ID, not found.", 2) end -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 -} + return WrapTable(Class, Ignored) +end -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 or size vector --- @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(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 size - - if type(id) == "string" then - local list_entries = ACF.Weapons.Ammo - local type_id = list_entries[id] - if not type_id then SF.Throw("Invalid id", 2) end - - pos = pos + LocalToWorld(type_id.Offset, Angle(), Vector(), ang) - size = type_id.Size - else - size = vunwrap(id) - 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 - - local dupe_class = duplicator.FindEntityClass("acf_ammo") - 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, - 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 - - args_table[13] = size.x - args_table[14] = size.y - args_table[15] = size.z - - 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 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 - ---- 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 +--- Returns the specifications of an ACF engine class +-- @param id The ID of the engine class you want to get the information from +-- @server +function acf_library.getEngineClassSpecs(id) + CheckLuaType(id, TYPE_STRING) + + local Class = Engines[id] + + if not Class then SF.Throw("Invalid engine class ID, not found.", 2) end + + return WrapTable(Class, Ignored) end ---- Returns a list of all ammo boxes +--- Returns the specifications of an ACF engine +-- @param id The ID of the engine you want to get the information from -- @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 +function acf_library.getEngineSpecs(id) + CheckLuaType(id, TYPE_STRING) + + local Class = ACF.GetClassGroup(Engines, id) + + if not Class then SF.Throw("Invalid engine ID, not found.", 2) end + + return WrapTable(Class.Lookup[id], Ignored) end ----------------------------------------- --- Entity Methods +--- Returns the specifications of an ACF fuel tank class +-- @param id The ID of the fuel tank class you want to get the information from +-- @server +function acf_library.getFuelTankClassSpecs(id) + CheckLuaType(id, TYPE_STRING) + + local Class = FuelTanks[id] + + if not Class then SF.Throw("Invalid fuel tank class ID, not found.", 2) end + + return WrapTable(Class, Ignored) +end + +--- Returns the specifications of an ACF fuel tank +-- @param id The ID of the fuel tank you want to get the information from +-- @server +function acf_library.getFuelTankSpecs(id) + CheckLuaType(id, TYPE_STRING) + + local Class = ACF.GetClassGroup(FuelTanks, id) + + if not Class then SF.Throw("Invalid fuel tank ID, not found.", 2) end + + return WrapTable(Class.Lookup[id], Ignored) +end --- [General Functions ] -- +--- Returns the specifications of an ACF fuel type +-- @param id The ID of the fuel type you want to get the information from +-- @server +function acf_library.getFuelTypeSpecs(id) + CheckLuaType(id, TYPE_STRING) + + local Type = FuelTypes[id] --- 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]] + if not Type then SF.Throw("Invalid fuel type ID, not found.", 2) end ---- Returns true if this entity contains sensitive info and is not accessable to us + return WrapTable(Type, Ignored) +end + +--- Returns the specifications of an ACF gearbox class +-- @param id The ID of the gearbox class you want to get the information from -- @server -function ents_methods:acfIsInfoRestricted () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function acf_library.getGearboxClassSpecs(id) + CheckLuaType(id, TYPE_STRING) + + local Class = Gearboxes[id] - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + if not Class then SF.Throw("Invalid gearbox class ID, not found.", 2) end - return restrictInfo( this ) + return WrapTable(Class, Ignored) +end + +--- Returns the specifications of an ACF gearbox +-- @param id The ID of the gearbox you want to get the information from +-- @server +function acf_library.getGearboxSpecs(id) + CheckLuaType(id, TYPE_STRING) + + local Class = ACF.GetClassGroup(Gearboxes, id) + + if not Class then SF.Throw("Invalid gearbox ID, not found.", 2) end + + return WrapTable(Class.Lookup[id], Ignored) +end + +--- Returns the specifications of an ACF weapon class +-- @param id The ID of the weapon class you want to get the information from +-- @server +function acf_library.getWeaponClassSpecs(id) + CheckLuaType(id, TYPE_STRING) + + local Class = Weapons[id] + + if not Class then SF.Throw("Invalid weapon class ID, not found.", 2) end + + return WrapTable(Class, Ignored) +end + +--- Returns the specifications of an ACF weapon +-- @param id The ID of the weapon you want to get the information from +-- @server +function acf_library.getWeaponSpecs(id) + CheckLuaType(id, TYPE_STRING) + + local Class = ACF.GetClassGroup(Weapons, id) + + if not Class then SF.Throw("Invalid weapon ID, not found.", 2) end + + return WrapTable(Class.Lookup[id], Ignored) +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) + + if not ACF.Check(This) then SF.Throw("Entity is not valid", 2) end + + return RestrictInfo(This) +end + +--- Returns the full name of an ACF entity +-- @server +function ents_methods:acfName() + CheckType(self, ents_metatable) + + local This = unwrap(self) + + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return "" end + + return This.Name or "" end --- Returns the short name of an ACF entity -- @server -function ents_methods:acfNameShort () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfNameShort() + CheckType(self, ents_metatable) + + local This = unwrap(self) + + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return "" end + + return This.ShortName or "" +end + +--- Returns the type of ACF entity +-- @server +function ents_methods:acfType() + CheckType(self, ents_metatable) + + local This = unwrap(self) + + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return "" end + + return This.EntType or "" +end + +--- Returns true if the entity is an ACF engine +-- @server +function ents_methods:acfIsEngine() + 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 IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return false 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 "" + return This.IsEngine or false +end + +--- 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 SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return false end + + return This.IsGearbox or false +end + +--- Returns true if the entity is an ACF gun +-- @server +function ents_methods:acfIsGun() + CheckType(self, ents_metatable) + + local This = unwrap(self) + + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return false end + + return This.IsWeapon or false +end + +--- Returns true if the entity is an ACF ammo crate +-- @server +function ents_methods:acfIsAmmo() + CheckType(self, ents_metatable) + + local This = unwrap(self) + + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return false end + + return This.IsAmmoCrate or false +end + +--- Returns true if the entity is an ACF fuel tank +-- @server +function ents_methods:acfIsFuel() + CheckType(self, ents_metatable) + + local This = unwrap(self) + + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return false end + + return This.IsFuelTank or false end --- Returns the capacity of an acf ammo crate or fuel tank -- @server -function ents_methods:acfCapacity () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfCapacity() + CheckType(self, ents_metatable) + + local This = unwrap(self) + + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + + return This.Capacity or 0 +end + +--- Returns the path of an ACF entity's sound +-- @server +function ents_methods:acfSoundPath() + 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 IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return "" end - if not ( isAmmo( this ) or isFuel( this ) ) then return 0 end - if restrictInfo( this ) then return 0 end - return this.Capacity or 1 + return This.SoundPath or "" end --- 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 ) +function ents_methods:acfGetActive() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - 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 + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return false end + if This.CanConsume then return This:CanConsume() end + + return (This.Active or This.Load) or false end --- Turns an ACF engine, ammo crate, or fuel tank on or off +-- @param on The new active state of the entity -- @server -function ents_methods:acfSetActive ( on ) - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfSetActive(on) + CheckType(self, ents_metatable) + CheckLuaType(on, TYPE_BOOL) + + local This = unwrap(self) + + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - checkpermission( instance, this, "entities.acf" ) + CheckPerms(instance, This, "entities.acf") - if not ( isEngine( this ) or isAmmo( this ) or isFuel( this ) ) then return end - this:TriggerInput( "Active", on and 1 or 0 ) + This:TriggerInput("Active", on) end ---- Returns true if hitpos is on a clipped part of prop +--- Returns the current health of an entity -- @server -function ents_methods:acfHitClip( hitpos ) - checktype( self, ents_metatable ) - checktype( hitpos, vec_meta ) - local this = unwrap( self ) - local hitpos = vunwrap( hitpos ) +function ents_methods:acfPropHealth() + CheckType(self, ents_metatable) + + local This = unwrap(self) - 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? + if not ACF.Check(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end - if ACF_CheckClips( nil, nil, this, hitpos ) then return true else return false end + local Health = This.ACF.Health + + return Health and math.Round(Health, 2) or 0 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 } -} +--- Returns the current armor of an entity +-- @server +function ents_methods:acfPropArmor() + CheckType(self, ents_metatable) -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 + local This = unwrap(self) - -- 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 + if not ACF.Check(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + + local Armor = This.ACF.Armour - return ret + return Armor and math.Round(Armor, 2) or 0 end -local function searchForGearboxLinks ( ent ) - local boxes = ents.FindByClass( "acf_gearbox" ) +--- Returns the max health of an entity +-- @server +function ents_methods:acfPropHealthMax() + CheckType(self, ents_metatable) - local ret = {} + local This = unwrap(self) - 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 + if not ACF.Check(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + + local MaxHealth = This.ACF.MaxHealth - return ret + return MaxHealth and math.Round(MaxHealth, 2) or 0 end ---- Returns the ACF links associated with the entity +--- Returns the max armor of an entity +-- @server +function ents_methods:acfPropArmorMax() + CheckType(self, ents_metatable) + + local This = unwrap(self) + + if not ACF.Check(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + + local MaxArmor = This.ACF.MaxArmour + + return MaxArmor and math.Round(MaxArmor, 2) or 0 +end + +--- Returns the ductility of an entity +-- @server +function ents_methods:acfPropDuctility() + CheckType(self, ents_metatable) + + local This = unwrap(self) + + if not ACF.Check(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + + local Ductility = This.ACF.Ductility + + return Ductility and math.Round(Ductility * 100, 2) or 0 +end + +--- Returns true if hitpos is on a clipped part of prop +-- @param hitpos The world hit position we want to check -- @server -function ents_methods:acfLinks () - checktype( self, ents_metatable ) - local this = unwrap( self ) +-- @return Returns true if hitpos is inside a visclipped part of the entity +function ents_methods:acfHitClip(hitpos) + CheckType(self, ents_metatable) + CheckType(hitpos, vec_meta) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - local enttype = this:GetClass() + if not ACF.Check(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return false end - if not linkTables[ enttype ] then - return searchForGearboxLinks( this ) - end + local Position = vunwrap(hitpos) - return getLinks( this, enttype ) + return ACF.CheckClips(This, Position) end ---- Returns the full name of an ACF entity +--- Returns the ACF links associated with the entity -- @server -function ents_methods:acfName () - checktype( self, ents_metatable ) - local this = unwrap( self ) - - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end +function ents_methods:acfLinks() + CheckType(self, ents_metatable) - if isAmmo( this ) then return ( this.RoundId .. " " .. this.RoundType) end - if isFuel( this ) then return this.FuelType .. " " .. this.SizeId end + local This = unwrap(self) - 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 + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return {} end ---- Returns the type of ACF entity --- @server -function ents_methods:acfType () - checktype( self, ents_metatable ) - local this = unwrap( self ) + local Result = {} + local Count = 0 - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + for Entity in pairs(ACF.GetLinkedEntities(This)) do + Count = Count + 1 - 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 "" + Result[Count] = wrap(Entity) end - if isAmmo( this ) then return this.RoundType or "" end - if isFuel( this ) then return this.FuelType or "" end - return "" + + return Result end --- Perform ACF links +-- @param target The entity to get linked to +-- @param notify If set, a notification will be sent to the player -- @server -function ents_methods:acfLinkTo ( target, notify ) - checktype( self, ents_metatable ) - checktype( target, ents_metatable ) +-- @return Returns the result of the operation, along with the result message as a second argument +function ents_methods:acfLinkTo(target, notify) + CheckType(self, ents_metatable) + CheckType(target, ents_metatable) - local this = unwrap( self ) - local tar = unwrap( target ) + local This = unwrap(self) + local Target = unwrap(target) - 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 + if not ACF.Check(This) then SF.Throw("Entity is not valid", 2) end + if not ACF.Check(Target) then SF.Throw("Invalid Link Entity", 2) end + if RestrictInfo(This) then SF.Throw("You don't have permission to link this entity to something", 2) end + if RestrictInfo(Target) then SF.Throw("You don't have permission to link something to this entity", 2) end + if not isfunction(This.Link) then SF.Throw("Entity does not support linking", 2) end - checkpermission( instance, this, "entities.acf" ) - checkpermission( instance, tar, "entities.acf" ) + CheckPerms(instance, This, "entities.acf") + CheckPerms(instance, Target, "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 + local Success, Message = This:Link(Target) - local success, msg = this:Link( tar ) if notify then - ACF_SendNotify( self.player, success, msg ) + ACF.SendNotify(instance.player, Success, Message) end - return success, msg + + return Success, Message end --- Perform ACF unlinks -- @server -function ents_methods:acfUnlinkFrom ( target, notify ) - checktype( self, ents_metatable ) - checktype( target, ents_metatable ) +function ents_methods:acfUnlinkFrom(target, notify) + CheckType(self, ents_metatable) + CheckType(target, ents_metatable) - local this = unwrap( self ) - local tar = unwrap( target ) + local This = unwrap(self) + local Target = unwrap(target) - 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 + if not ACF.Check(This) then SF.Throw("Entity is not valid", 2) end + if not ACF.Check(Target) then SF.Throw("Invalid Link Entity", 2) end + if RestrictInfo(This) then SF.Throw("You don't have permission to unlink this entity from something", 2) end + if RestrictInfo(Target) then SF.Throw("You don't have permission to unlink something from this entity", 2) end + if not isfunction(This.Unlink) then SF.Throw("Entity does not support unlinking", 2) end - checkpermission( instance, this, "entities.acf" ) - checkpermission( instance, tar, "entities.acf" ) + CheckPerms(instance, This, "entities.acf") + CheckPerms(instance, Target, "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 + local Success, Message = This:Unlink(Target) - local success, msg = this:Unlink( tar ) if notify then - ACF_SendNotify( self.player, success, msg ) - end - return success, msg -end - ---- 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 ( 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 - - local wheels = {} - for k, ent in pairs( ACF_GetLinkedWheels( this ) ) do - table.insert( wheels, wrap( ent ) ) + ACF.SendNotify(instance.player, Success, Message) end - return wheels + return Success, Message end --- [ Engine Functions ] -- - ---- Returns true if the entity is an ACF engine --- @server -function ents_methods:acfIsEngine () - checktype( self, ents_metatable ) - local this = unwrap( self ) - - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - - return isEngine( this ) -end +--===============================================================================================-- +-- Mobility Functions +--===============================================================================================-- --- Returns true if an ACF engine is electric -- @server -function ents_methods:acfIsElectric () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfIsElectric() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - return this.iselec == true + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return false end + + return This.IsElectric or false 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:acfMaxTorque() + 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 IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end - if not isEngine( this ) then return 0 end - return this.PeakTorque or 0 + return This.PeakTorque or 0 end ---- Returns the torque in N/m of an ACF engine with fuel +--- Returns the power in kW of an ACF engine -- @server -function ents_methods:acfMaxTorqueWithFuel () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfMaxPower() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - if not isEngine( this ) then return 0 end - return this.PeakTorque or 0 -end + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end -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 + return GetMaxPower(This) end ---- Returns the power in kW of an ACF engine +--- (DEPRECATED) Returns the torque in N/m of an ACF engine. Use Entity:acfMaxTorque() -- @server -function ents_methods:acfMaxPower () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfMaxTorqueWithFuel() + 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 IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end - return isEngine( this ) and getMaxPower( this ) or 0 + return This.PeakTorque or 0 end ---- Returns the power in kW of an ACF engine with fuel +--- (DEPRECATED) Returns the power in kW of an ACF engine. Use Entity:acfMaxPower() -- @server -function ents_methods:acfMaxPowerWithFuel () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfMaxPowerWithFuel() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - return isEngine( this ) and getMaxPower( this ) or 0 + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + + return GetMaxPower(This) end --- Returns the idle rpm of an ACF engine -- @server -function ents_methods:acfIdleRPM () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfIdleRPM() + 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 IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end - if not isEngine( this ) then return 0 end - return this.IdleRPM or 0 + return This.IdleRPM or 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 ) +function ents_methods:acfPowerband() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - if not isEngine( this ) then return 0, 0 end - return this.PeakMinRPM or 0, this.PeakMaxRPM or 0 + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0, 0 end + + return This.PeakMinRPM or 0, This.PeakMaxRPM or 0 end --- Returns the powerband min of an ACF engine -- @server -function ents_methods:acfPowerbandMin () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfPowerbandMin() + 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 IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end - if not isEngine( this ) then return 0 end - return this.PeakMinRPM or 0 + return This.PeakMinRPM or 0 end --- Returns the powerband max of an ACF engine -- @server -function ents_methods:acfPowerbandMax () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfPowerbandMax() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - if not isEngine( this ) then return 0 end - return this.PeakMaxRPM or 0 + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + + return This.PeakMaxRPM or 0 end --- Returns the redline rpm of an ACF engine -- @server -function ents_methods:acfRedline () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfRedline() + 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 IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end - if not isEngine( this ) then return 0 end - return this.LimitRPM or 0 + return This.LimitRPM or 0 end --- Returns the current rpm of an ACF engine -- @server -function ents_methods:acfRPM () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfRPM() + CheckType(self, ents_metatable) + + local This = unwrap(self) + + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local RPM = This.FlyRPM - if not isEngine( this ) then return 0 end - if restrictInfo( this ) then return 0 end - return math.floor( this.FlyRPM ) or 0 + return RPM and math.floor(RPM) or 0 end --- Returns the current torque of an ACF engine -- @server -function ents_methods:acfTorque () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfTorque() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + 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 ) + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + + local Torque = This.Torque + + return Torque and math.floor(Torque) or 0 end --- Returns the inertia of an ACF engine's flywheel -- @server -function ents_methods:acfFlyInertia () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfFlyInertia() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - if not isEngine( this ) then return nil end - if restrictInfo( this ) then return 0 end - return this.Inertia or 0 + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + + return This.Inertia or 0 end --- Returns the mass of an ACF engine's flywheel -- @server -function ents_methods:acfFlyMass () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfFlyMass() + 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 IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end - if not isEngine( this ) then return nil end - if restrictInfo( this ) then return 0 end - return this.Inertia / ( 3.1416 )^2 or 0 + return This.FlywheelMass or 0 end --- Returns the current power of an ACF engine -- @server -function ents_methods:acfPower () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfPower() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + 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 ) * ( this.FlyRPM or 0 ) / 9548.8 ) + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + if not This.Torque then return 0 end + if not This.FlyRPM then return 0 end + + return math.floor(This.Torque * This.FlyRPM / 9548.8) end --- 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 ) +function ents_methods:acfInPowerband() + CheckType(self, ents_metatable) + + local This = unwrap(self) + + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return false end + if not This.FlyRPM then return false end + + local PowerbandMin, 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 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 + 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 return true end --- Returns the throttle value -- @server -function ents_methods:acfGetThrottle () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfGetThrottle() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - - if not isEngine( this ) then return 0 end - if restrictInfo( this ) then return 0 end - return ( this.Throttle or 0 ) * 100 -end + local This = unwrap(self) ---- 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 ) + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - checkpermission( instance, this, "entities.acf" ) + local Throttle = This.Throttle - if not isEngine( this ) then return end - this:TriggerInput( "Throttle", throttle ) + return Throttle and Throttle * 100 or 0 end +--- Sets the throttle value for an ACF engine +-- @server +function ents_methods:acfSetThrottle(throttle) + CheckType(self, ents_metatable) + CheckLuaType(throttle, TYPE_NUMBER) --- [ Gearbox Functions ] -- + 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 SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return end - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + CheckPerms(instance, This, "entities.acf") - return isGearbox( this ) + This:TriggerInput("Throttle", throttle) end --- Returns the current gear for an ACF gearbox -- @server -function ents_methods:acfGear () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfGear() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - if not isGearbox( this ) then return 0 end - if restrictInfo( this ) then return 0 end - return this.Gear or 0 + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + + return This.Gear or 0 end --- Returns the number of gears for an ACF gearbox -- @server -function ents_methods:acfNumGears () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfNumGears() + 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 IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end - if not isGearbox( this ) then return 0 end - if restrictInfo( this ) then return 0 end - return this.Gears or 0 + return This.GearCount or 0 end --- Returns the final ratio for an ACF gearbox -- @server -function ents_methods:acfFinalRatio () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfFinalRatio() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - if not isGearbox( this ) then return 0 end - if restrictInfo( this ) then return 0 end - return this.GearTable[ "Final" ] or 0 + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + + return This.FinalDrive or 0 end --- Returns the total ratio (current gear * final) for an ACF gearbox -- @server -function ents_methods:acfTotalRatio () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfTotalRatio() + 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 IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end - if not isGearbox( this ) then return 0 end - if restrictInfo( this ) then return 0 end - return this.GearRatio or 0 + return This.GearRatio or 0 end --- Returns the max torque for an ACF gearbox -- @server -function ents_methods:acfTorqueRating () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfTorqueRating() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - if not isGearbox( this ) then return 0 end - return this.MaxTorque or 0 + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + + return This.MaxTorque or 0 end --- Returns whether an ACF gearbox is dual clutch -- @server -function ents_methods:acfIsDual () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfIsDual() + 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 IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return false end - if not isGearbox( this ) then return false end - if restrictInfo( this ) then return false end - - return this.Dual + return This.DualClutch or false 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 ) +function ents_methods:acfShiftTime() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - if not isGearbox( this ) then return 0 end - return ( this.SwitchTime or 0 ) * 1000 + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + + local Time = This.SwitchTime + + return Time and Time * 1000 or 0 end --- Returns true if an ACF gearbox is in gear -- @server -function ents_methods:acfInGear () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfInGear() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - if not isGearbox( this ) then return false end - if restrictInfo( this ) then return false end - - return this.InGear + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return false end + + return This.InGear or false end --- 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 ) +function ents_methods:acfGearRatio(gear) + CheckType(self, ents_metatable) + CheckLuaType(gear, TYPE_NUMBER) + + local This = unwrap(self) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + if not This.Gears then return 0 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 + return This.Gears[math.floor(gear)] or 0 end --- Returns the current torque output for an ACF gearbox -- @server -function ents_methods:acfTorqueOut () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfTorqueOut() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - if not isGearbox( this ) then return 0 end - return math.min( this.TotalReqTq or 0, this.MaxTorque or 0 ) / ( this.GearRatio or 1 ) + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + + return math.min(This.TotalReqTq or 0, This.MaxTorque or 0) / (This.GearRatio or 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 ) +function ents_methods:acfCVTRatio(ratio) + CheckType(self, ents_metatable) + CheckLuaType(ratio, TYPE_NUMBER) + + 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 SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return end + if not This.CVT 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 ) + CheckPerms(instance, This, "entities.acf") + + This:TriggerInput("CVT Ratio", math.Clamp(ratio, 0, 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 ) +function ents_methods:acfShift(gear) + CheckType(self, ents_metatable) + CheckLuaType(gear, TYPE_NUMBER) + + 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 SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return end - if not isGearbox( this ) then return end - if restrictInfo( this ) then return end - this:TriggerInput( "Gear", gear ) + CheckPerms(instance, This, "entities.acf") + + This:TriggerInput("Gear", gear) end --- Cause an ACF gearbox to shift up -- @server -function ents_methods:acfShiftUp () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfShiftUp() + CheckType(self, ents_metatable) + + 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 SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return end - 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 + CheckPerms(instance, This, "entities.acf") + + This:TriggerInput("Gear Up", true) --doesn't need to be toggled off end --- Cause an ACF gearbox to shift down -- @server -function ents_methods:acfShiftDown () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfShiftDown() + CheckType(self, ents_metatable) + + 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 SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 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 + CheckPerms(instance, This, "entities.acf") + + This:TriggerInput("Gear Down", true) --doesn't need to be toggled off end --- 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 ) +function ents_methods:acfBrake(brake) + CheckType(self, ents_metatable) + CheckLuaType(brake, TYPE_NUMBER) + + 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 SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return end - if not isGearbox( this ) then return end - if restrictInfo( this ) then return end - this:TriggerInput( "Brake", brake ) + CheckPerms(instance, This, "entities.acf") + + This:TriggerInput("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 ) +function ents_methods:acfBrakeLeft(brake) + CheckType(self, ents_metatable) + CheckLuaType(brake, TYPE_NUMBER) + + 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 SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) 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 Brake", brake ) + CheckPerms(instance, This, "entities.acf") + + This:TriggerInput("Left Brake", brake) 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 ) +function ents_methods:acfBrakeRight(brake) + CheckType(self, ents_metatable) + CheckLuaType(brake, TYPE_NUMBER) + + 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 SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) 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("Right Brake", brake ) + CheckPerms(instance, This, "entities.acf") + + This:TriggerInput("Right Brake", brake) end --- 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 ) +function ents_methods:acfClutch(clutch) + CheckType(self, ents_metatable) + CheckLuaType(clutch, TYPE_NUMBER) + + 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 SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return end - if not isGearbox( this ) then return end - if restrictInfo( this ) then return end - this:TriggerInput( "Clutch", clutch ) + CheckPerms(instance, This, "entities.acf") + + This:TriggerInput("Clutch", clutch) end --- 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 ) +function ents_methods:acfClutchLeft(clutch) + CheckType(self, ents_metatable) + CheckLuaType(clutch, TYPE_NUMBER) + + 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 SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) 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 ) + CheckPerms(instance, This, "entities.acf") + + This:TriggerInput("Left 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 ) +function ents_methods:acfClutchRight(clutch) + CheckType(self, ents_metatable) + CheckLuaType(clutch, TYPE_NUMBER) + + 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 SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) 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( "Right Clutch", clutch ) + CheckPerms(instance, This, "entities.acf") + + This:TriggerInput("Right Clutch", clutch) 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 ) +function ents_methods:acfSteerRate(rate) + CheckType(self, ents_metatable) + CheckLuaType(rate, TYPE_NUMBER) + + 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 SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return end - 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 ) + CheckPerms(instance, This, "entities.acf") + + This:TriggerInput("Steer Rate", rate) end --- 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 ) +function ents_methods:acfHoldGear(hold) + CheckType(self, ents_metatable) + CheckLuaType(hold, TYPE_BOOL) + + 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 SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) 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( "Hold Gear", hold ) + CheckPerms(instance, This, "entities.acf") + + This:TriggerInput("Hold Gear", hold) end --- 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 ) +function ents_methods:acfShiftPointScale(scale) + CheckType(self, ents_metatable) + CheckLuaType(scale, 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.Auto then return end - this:TriggerInput( "Shift Speed Scale", scale ) -end + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return end + CheckPerms(instance, This, "entities.acf") --- [ Gun Functions ] -- + This:TriggerInput("Shift Speed Scale", scale) +end ---- Returns true if the entity is an ACF gun +--- Sets the ACF fuel tank refuel duty status, which supplies fuel to other fuel tanks -- @server -function ents_methods:acfIsGun () - checktype( self, ents_metatable ) - local this = unwrap( self ) - - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end +function ents_methods:acfRefuelDuty(on) + CheckType(self, ents_metatable) + 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 SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return end - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + CheckPerms(instance, This, "entities.acf") - if not isGun( this ) then return false end - if restrictInfo( this ) then return false end - if ( this.Ready ) then return true end - return false + This:TriggerInput("Refuel Duty", on) end ---- Returns the magazine size for an ACF gun +--- Returns the remaining liters or kilowatt hours of fuel in an ACF fuel tank or engine -- @server -function ents_methods:acfMagSize () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfFuel() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - if not isGun( this ) then return 0 end - return this.MagSize or 1 -end + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + if This.Fuel then return math.Round(This.Fuel, 2) end ---- Returns the spread for an ACF gun or flechette ammo --- @server -function ents_methods:acfSpread () - checktype( self, ents_metatable ) - local this = unwrap( self ) + local Source = LinkSource(This:GetClass(), "FuelTanks") - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + if not Source then return 0 end - 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 ) + local Fuel = 0 + + for Tank in pairs(Source(This)) do + Fuel = Fuel + Tank.Fuel end - return Spread + + return math.Round(Fuel, 2) end ---- Returns true if an ACF gun is reloading +--- Returns the amount of fuel in an ACF fuel tank or linked to engine as a percentage of capacity -- @server -function ents_methods:acfIsReloading () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfFuelLevel() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - 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 IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + if This.Capacity then + return math.Round(This.Fuel or 0 / This.Capacity, 2) + end ---- Returns the rate of fire of an acf gun --- @server -function ents_methods:acfFireRate () - checktype( self, ents_metatable ) - local this = unwrap( self ) + local Source = LinkSource(This:GetClass(), "FuelTanks") + + if not Source then return 0 end - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local Capacity = 0 + local Fuel = 0 - if not isGun( this ) then return 0 end - return math.Round( this.RateOfFire or 0, 3 ) + for Tank in pairs(Source(This)) do + Capacity = Capacity + Tank.Capacity + Fuel = Fuel + Tank.Fuel + end + + return math.Round(Fuel / Capacity, 2) end ---- Returns the number of rounds left in a magazine for an ACF gun +--- Returns the current fuel consumption in liters per minute or kilowatts of an engine -- @server -function ents_methods:acfMagRounds () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfFuelUse() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - 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 + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(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 + + return This:GetConsumption(This.Throttle, This.FlyRPM) * 60 end ---- Sets the firing state of an ACF weapon +--- 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:acfFire ( fire ) - checktype( self, ents_metatable ) - checkluatype( fire, TYPE_NUMBER ) - local this = unwrap( self ) +function ents_methods:acfPeakFuelUse() + CheckType(self, ents_metatable) - 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 isGun( this ) then return end - - this:TriggerInput( "Fire", fire ) + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + if not This.GetConsumption then return 0 end + if not This.LimitRPM then return 0 end + + return This:GetConsumption(1, This.LimitRPM) * 60 end ---- Causes an ACF weapon to unload +--- returns any wheels linked to This engine/gearbox or child gearboxes -- @server -function ents_methods:acfUnload () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfGetLinkedWheels() + CheckType(self, ents_metatable) - 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 isGun( this ) then return end - - this:UnloadAmmo() -end + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return {} end ---- Causes an ACF weapon to reload --- @server -function ents_methods:acfReload () - checktype( self, ents_metatable ) - local this = unwrap( self ) + local Wheels = {} + local Count = 0 - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - checkpermission( instance, this, "entities.acf" ) + for Wheel in pairs(GetLinkedWheels(This)) do + Count = Count + 1 + Wheels[Count] = Wheel + end - if not isGun( this ) then return end - - this.Reloading = true + return Wheels end ---- Returns the number of rounds in active ammo crates linked to an ACF weapon +--- Returns true if the ACF gun is ready to fire -- @server -function ents_methods:acfAmmoCount () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfReady() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - if not isGun( this ) then return 0 end - if restrictInfo( this ) then return 0 end - local Ammo = 0 - for AmmoEnt in pairs( this.Crates ) do - if AmmoEnt and AmmoEnt:IsValid() and AmmoEnt[ "Load" ] then - Ammo = Ammo + ( AmmoEnt.Ammo or 0 ) - end - end - return Ammo + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return false end + + return This.State == "Loaded" end ---- Returns the number of rounds in all ammo crates linked to an ACF weapon +--- Returns a string with the current state of the entity -- @server -function ents_methods:acfTotalAmmoCount () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfState() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - if not isGun( this ) then return 0 end - if restrictInfo( this ) then return 0 end - local Ammo = 0 - for AmmoEnt in pairs( this.Crates ) do - if AmmoEnt and AmmoEnt:IsValid() then - Ammo = Ammo + ( AmmoEnt.Ammo or 0 ) - end - end - return Ammo + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return "" end + + return This.State or "" end --- Returns time to next shot of an ACF weapon -- @server -function ents_methods:acfReloadTime () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfReloadTime() + 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 IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + if This.State == "Loaded" then return 0 end - if restrictInfo( this ) or not isGun( this ) or this.Ready then return 0 end - return reloadTime( this ) + return GetReloadTime(This) 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 ) +function ents_methods:acfReloadProgress() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + 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 ) + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + if not This.NextFire then return This.State == "Loaded" and 1 or 0 end + + return math.Clamp(1 - (This.NextFire - ACF.CurTime) / GetReloadTime(This), 0, 1) 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 ) +function ents_methods:acfMagReloadTime() + 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 IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end - if restrictInfo( instance.player , this ) or not isGun( this ) or not this.MagReload then return 0 end - return this.MagReload + return This.MagReload or 0 end --- [ Ammo Functions ] -- +--- Returns the magazine size for an ACF gun +-- @server +function ents_methods:acfMagSize() + CheckType(self, ents_metatable) + + local This = unwrap(self) ---- Returns true if the entity is an ACF ammo crate + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + + return This.MagSize or 0 +end + +--- Returns the spread for an ACF gun or flechette ammo -- @server -function ents_methods:acfIsAmmo () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfSpread() + CheckType(self, ents_metatable) + + local This = unwrap(self) + + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local Spread = (This.GetSpread and This:GetSpread()) or This.Spread or 0 - return isAmmo( this ) and not restrictInfo( this ) + if This.BulletData and This.BulletData.Type == "FL" then -- TODO: Replace this hardcoded bit + return Spread + (This.BulletData.FlechetteSpread or 0) + end + + return Spread end ---- Returns the rounds left in an acf ammo crate +--- Returns true if an ACF gun is reloading -- @server -function ents_methods:acfRounds () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfIsReloading() + 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 IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return false end - if not isAmmo( this ) then return 0 end - if restrictInfo( this ) then return 0 end - return this.Ammo or 0 + return This.State == "Loading" end ---- Returns the type of weapon the ammo in an ACF ammo crate loads into +--- Returns the rate of fire of an acf gun -- @server -function ents_methods:acfRoundType () --cartridge? - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfFireRate() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - 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 + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + + local Time = This.ReloadTime + + return Time and math.Round(60 / Time, 2) or 0 end ---- Returns the type of ammo in a crate or gun +--- Returns the number of rounds left in a magazine for an ACF gun -- @server -function ents_methods:acfAmmoType () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfMagRounds() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - if not isAmmo( this ) or isGun( this ) then return "" end - if restrictInfo( this ) then return "" end - return this.BulletData[ "Type" ] or "" + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + + return This.CurrentShot or 0 end ---- Returns the caliber of an ammo or gun +--- Sets the firing state of an ACF weapon -- @server -function ents_methods:acfCaliber () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfFire(fire) + CheckType(self, ents_metatable) + CheckLuaType(fire, TYPE_BOOL) + + local This = unwrap(self) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return end - if not ( isAmmo( this ) or isGun( this ) or isRack( this )) then return 0 end - if restrictInfo( this ) then return 0 end + CheckPerms(instance, This, "entities.acf") - if not this.Caliber then -- If not a gun or ammo crate - if not this.BulletData then return 0 end -- If not a a rack - if not this.BulletData.Id then return 0 end + This:TriggerInput("Fire", fire) +end - local GunData = ACF.Weapons.Guns[this.BulletData.Id] +--- Causes an ACF weapon to unload +-- @server +function ents_methods:acfUnload() + CheckType(self, ents_metatable) - if not GunData then return 0 end + local This = unwrap(self) - return GunData.caliber * 10 or 0 - end + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return end - return this.Caliber * 10 or 0 + CheckPerms(instance, This, "entities.acf") + + This:TriggerInput("Unload", true) end ---- Returns the muzzle velocity of the ammo in a crate or gun +--- Causes an ACF weapon to reload -- @server -function ents_methods:acfMuzzleVel () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfReload() + 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 IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(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.Scale, 3 ) + CheckPerms(instance, This, "entities.acf") + + This:TriggerInput("Reload", true) end ---- Returns the mass of the projectile in a crate or gun +--- Returns the rounds left in an acf ammo crate -- @server -function ents_methods:acfProjectileMass () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfRounds() + 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 IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 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 ) + return This.Ammo or 0 end ---- Returns the number of projectiles in a flechette round +--- Returns the type of weapon the ammo in an ACF ammo crate loads into -- @server -function ents_methods:acfFLSpikes () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfRoundType() + CheckType(self, ents_metatable) + + local This = unwrap(self) + + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return "" end - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local BulletData = This.BulletData - 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 + return BulletData and BulletData.Id or "" end ---- Returns the mass of a single spike in a FL round in a crate or gun +--- Returns the type of ammo in a crate or gun -- @server -function ents_methods:acfFLSpikeMass () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfAmmoType() + CheckType(self, ents_metatable) + + local This = unwrap(self) + + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return "" end - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local BulletData = This.BulletData - 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) + return BulletData and BulletData.Type or "" end ---- Returns the radius of the spikes in a flechette round in mm +--- Returns the caliber of an ammo or gun -- @server -function ents_methods:acfFLSpikeRadius () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfCaliber() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - 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) + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + + return This.Caliber or 0 end ---- Returns the penetration of an AP, APHE, or HEAT round +--- Returns the muzzle velocity of the ammo in a crate or gun -- @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[ "PenArea" ] ) * 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[ "SlugPenArea" ] ) * 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["PenArea"])*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["SlugPenArea"])*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 +function ents_methods:acfMuzzleVel() + CheckType(self, ents_metatable) + + local This = unwrap(self) + + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + + local BulletData = This.BulletData + local MuzzleVel = BulletData and BulletData.MuzzleVel + + return MuzzleVel and MuzzleVel * ACF.Scale or 0 end ---- Returns the blast radius of an HE, APHE, or HEAT round +--- Returns the mass of the projectile in a crate or gun -- @server -function ents_methods:acfBlastRadius () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfProjectileMass() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - 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 + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end + + local BulletData = This.BulletData + + return BulletData and BulletData.ProjMass or 0 end --- 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 ) + 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 IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) 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 + local BulletData = This.BulletData + local DragCoef = BulletData and BulletData.DragCoef + + return DragCoef and DragCoef / ACF.DragDiv or 0 end --- Returns the fin multiplier of the ammo in a crate or launcher -- @server function ents_methods:acfFinMul() - checktype( self, ents_metatable ) - local this = unwrap( self ) - - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + CheckType(self, ents_metatable) - if not ( isAmmo( this ) or isRack( this ) ) then return 0 end - if restrictInfo( this ) then return 0 end - if not this.BulletData.Id then return 0 end + local This = unwrap(self) - local GunData = ACF.Weapons.Guns[this.BulletData.Id] + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end - if not GunData then return 0 end - if not GunData.round then return 0 end - - return ( GunData.round.finmul or 0 ) + return This.FinMultiplier or 0 end -- Returns the weight of the missile in a crate or rack -- @server function ents_methods:acfMissileWeight() - checktype( self, ents_metatable ) - local this = unwrap( self ) - - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + CheckType(self, ents_metatable) - if not ( isAmmo( this ) or isRack( this ) ) then return 0 end - if restrictInfo( this ) then return 0 end - if not this.BulletData.Id then return 0 end + local This = unwrap(self) - local GunData = ACF.Weapons.Guns[this.BulletData.Id] - if not GunData then return 0 end + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end - return ( GunData.weight or 0 ) + return This.ForcedMass or 0 end -- Returns the weight of the missile in a crate or rack -- @server function ents_methods:acfMissileLength() - checktype( self, ents_metatable ) - local this = unwrap( self ) + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - if not ( isAmmo( this ) or isRack( this ) ) then return 0 end - if restrictInfo( this ) then return 0 end - if not this.BulletData.Id then return 0 end + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end - local GunData = ACF.Weapons.Guns[this.BulletData.Id] - if not GunData then return 0 end - - return ( GunData.length or 0 ) + return This.Length or 0 end --- [ Armor Functions ] -- - ---- Returns the current health of an entity +--- Returns the number of projectiles in a flechette round -- @server -function ents_methods:acfPropHealth () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfFLSpikes() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - 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 current armor of an entity --- @server -function ents_methods:acfPropArmor () - checktype( self, ents_metatable ) - local this = unwrap( self ) + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local BulletData = This.BulletData - 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 ) + return BulletData and BulletData.Flechettes or 0 end ---- Returns the max health of an entity +--- Returns the mass of a single spike in a FL round in a crate or gun -- @server -function ents_methods:acfPropHealthMax () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfFLSpikeMass() + CheckType(self, ents_metatable) + + local This = unwrap(self) + + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local BulletData = This.BulletData - 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 ) + return BulletData and BulletData.FlechetteMass or 0 end ---- Returns the max armor of an entity +--- Returns the radius of the spikes in a flechette round in mm -- @server -function ents_methods:acfPropArmorMax () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfFLSpikeRadius() + CheckType(self, ents_metatable) + + local This = unwrap(self) + + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local BulletData = This.BulletData + local Radius = BulletData and BulletData.FlechetteRadius - 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 ) + return Radius and math.Round(Radius * 10, 2) or 0 end ---- Returns the ductility of an entity +--- Returns the penetration of an AP, APHE, or HEAT round -- @server -function ents_methods:acfPropDuctility () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfPenetration() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - 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 + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end --- [ Fuel Functions ] -- + local BulletData = This.BulletData + local AmmoType = BulletData and AmmoTypes[BulletData.Type] ---- Returns true if the entity is an ACF fuel tank --- @server -function ents_methods:acfIsFuel () - checktype( self, ents_metatable ) - local this = unwrap( self ) + if not AmmoType then return 0 end - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local DisplayData = AmmoType:GetDisplayData(BulletData) + local MaxPen = DisplayData and DisplayData.MaxPen - return isFuel( this ) and not restrictInfo( this ) + return MaxPen and Round(MaxPen, 2) or 0 end ---- Sets the ACF fuel tank refuel duty status, which supplies fuel to other fuel tanks +--- Returns the blast radius of an HE, APHE, or HEAT round -- @server -function ents_methods:acfRefuelDuty ( on ) - checktype( self, ents_metatable ) - local this = unwrap( self ) - - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end - checkpermission( instance, this, "entities.acf" ) +function ents_methods:acfBlastRadius() + CheckType(self, ents_metatable) - if not isFuel( this ) then return end - - this:TriggerInput( "Refuel Duty", on and true or false ) -end + 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 SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local BulletData = This.BulletData + local AmmoType = BulletData and AmmoTypes[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 AmmoType 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 = AmmoType:GetDisplayData(BulletData) + local Radius = DisplayData and DisplayData.BlastRadius - return math.Round( liters, 3 ) - end - return 0 + return Radius and Round(Radius, 2) or 0 end ---- Returns the amount of fuel in an ACF fuel tank or linked to engine as a percentage of capacity +--- Returns the number of rounds in active ammo crates linked to an ACF weapon -- @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 +function ents_methods:acfAmmoCount() + CheckType(self, ents_metatable) - return math.Round( liters / capacity, 3 ) - end - return 0 -end + local This = unwrap(self) ---- 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 ) + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 end - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local Source = LinkSource(This:GetClass(), "Crates") - 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 + if not Source then return 0 end - local tank - for _, fueltank in pairs( this.FuelLink ) do - if validPhysics( fueltank ) and fueltank.Fuel > 0 and fueltank.Active then - tank = fueltank - break + local Count = 0 + + for Crate in pairs(Source(This)) do + if Crate:CanConsume() then + Count = Count + Crate.Ammo end end - if not tank then return 0 end - 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 ) + return Count 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 +--- Returns the number of rounds in all ammo crates linked to an ACF weapon -- @server -function ents_methods:acfPeakFuelUse () - checktype( self, ents_metatable ) - local this = unwrap( self ) +function ents_methods:acfTotalAmmoCount() + CheckType(self, ents_metatable) - if not ( this and this:IsValid() ) then SF.Throw( "Entity is not valid", 2 ) end + local This = unwrap(self) - 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 + if not IsACFEntity(This) then SF.Throw("Entity is not valid", 2) end + if RestrictInfo(This) then return 0 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 + local Source = LinkSource(This:GetClass(), "Crates") - 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 ] + if not Source then return 0 end + + local Count = 0 + + for Crate in pairs(Source(This)) do + Count = Count + Crate.Ammo end - return math.Round( Consumption, 3 ) + + return Count end end diff --git a/lua/vgui/acf_panel.lua b/lua/vgui/acf_panel.lua new file mode 100644 index 000000000..472e3f1b9 --- /dev/null +++ b/lua/vgui/acf_panel.lua @@ -0,0 +1,250 @@ +local PANEL = {} + +DEFINE_BASECLASS("Panel") + +-- Panels don't have a CallOnRemove function +-- This roughly replicates the same behavior +local function AddOnRemove(Panel, Parent) + local OldRemove = Panel.Remove + + function Panel:Remove() + Parent:EndTemporal(self) + Parent:ClearTemporal(self) + + Parent.Items[self] = nil + + for TempParent in pairs(self.TempParents) do + TempParent.TempItems[self] = nil + end + + if self == Parent.LastItem then + Parent.LastItem = self.PrevItem + end + + if IsValid(self.PrevItem) then + self.PrevItem.NextItem = self.NextItem + end + + if IsValid(self.NextItem) then + self.NextItem.PrevItem = self.PrevItem + end + + OldRemove(self) + end +end + +function PANEL:Init() + self.Items = {} + self.TempItems = {} +end + +function PANEL:ClearAll() + for Item in pairs(self.Items) do + Item:Remove() + end + + self:Clear() +end + +function PANEL:ClearTemporal(Panel) + local Target = IsValid(Panel) and Panel or self + + if not Target.TempItems then return end + + for K in pairs(Target.TempItems) do + K:Remove() + end +end + +local TemporalPanels = {} + +function PANEL:StartTemporal(Panel) + local Target = IsValid(Panel) and Panel or self + + if not Target.TempItems then + Target.TempItems = {} + end + + TemporalPanels[Target] = true +end + +function PANEL:EndTemporal(Panel) + local Target = IsValid(Panel) and Panel or self + + TemporalPanels[Target] = nil +end + +function PANEL:ClearAllTemporal() + for Panel in pairs(TemporalPanels) do + self:EndTemporal(Panel) + self:ClearTemporal(Panel) + end +end + +function PANEL:AddPanel(Name) + if not Name then return end + + local Panel = vgui.Create(Name, self) + + if not IsValid(Panel) then return end + + Panel:Dock(TOP) + Panel:DockMargin(0, 0, 0, 10) + Panel:InvalidateParent() + Panel:InvalidateLayout() + Panel.TempParents = {} + + self:InvalidateLayout() + self.Items[Panel] = true + + local LastItem = self.LastItem + + if IsValid(LastItem) then + LastItem.NextItem = Panel + + Panel.PrevItem = LastItem + + for Temp in pairs(LastItem.TempParents) do + Panel.TempParents[Temp] = true + Temp.TempItems[Panel] = true + end + end + + self.LastItem = Panel + + for Temp in pairs(TemporalPanels) do + Panel.TempParents[Temp] = true + Temp.TempItems[Panel] = true + end + + AddOnRemove(Panel, self) + + return Panel +end + +function PANEL:AddButton(Text, Command, ...) + local Panel = self:AddPanel("DButton") + Panel:SetText(Text or "Button") + Panel:SetFont("ACF_Control") + + if Command then + Panel:SetConsoleCommand(Command, ...) + end + + return Panel +end + +function PANEL:AddCheckBox(Text) + local Panel = self:AddPanel("DCheckBoxLabel") + Panel:SetText(Text or "Checkbox") + Panel:SetFont("ACF_Control") + Panel:SetDark(true) + + return Panel +end + +function PANEL:AddTitle(Text) + local Panel = self:AddPanel("DLabel") + Panel:SetAutoStretchVertical(true) + Panel:SetText(Text or "Text") + Panel:SetFont("ACF_Title") + Panel:SetWrap(true) + Panel:SetDark(true) + + return Panel +end + +function PANEL:AddLabel(Text) + local Panel = self:AddTitle(Text) + Panel:SetFont("ACF_Label") + + return Panel +end + +function PANEL:AddHelp(Text) + local TextColor = self:GetSkin().Colours.Tree.Hover + local Panel = self:AddLabel(Text) + Panel:DockMargin(10, 0, 10, 10) + Panel:SetTextColor(TextColor) + Panel:InvalidateLayout() + + return Panel +end + +function PANEL:AddComboBox() + local Panel = self:AddPanel("DComboBox") + Panel:SetFont("ACF_Control") + Panel:SetSortItems(false) + Panel:SetDark(true) + Panel:SetWrap(true) + + return Panel +end + +function PANEL:AddSlider(Title, Min, Max, Decimals) + local Panel = self:AddPanel("DNumSlider") + Panel:DockMargin(0, 0, 0, 5) + Panel:SetDecimals(Decimals or 0) + Panel:SetText(Title or "") + Panel:SetMinMax(Min, Max) + Panel:SetValue(Min) + Panel:SetDark(true) + + Panel.Label:SetFont("ACF_Control") + + return Panel +end + +function PANEL:AddNumberWang(Label, Min, Max, Decimals) + local Base = self:AddPanel("ACF_Panel") + + local Wang = Base:Add("DNumberWang") + Wang:SetDecimals(Decimals or 0) + Wang:SetMinMax(Min, Max) + Wang:SetTall(20) + Wang:Dock(RIGHT) + + local Text = Base:Add("DLabel") + Text:SetText(Label or "Text") + Text:SetFont("ACF_Control") + Text:SetDark(true) + Text:Dock(TOP) + + return Wang, Text +end + +function PANEL:AddCollapsible(Text, State) + if State == nil then State = true end + + local Base = vgui.Create("ACF_Panel") + Base:DockMargin(5, 5, 5, 10) + + local Category = self:AddPanel("DCollapsibleCategory") + Category:SetLabel(Text or "Title") + Category:DoExpansion(State) + Category:SetContents(Base) + + return Base, Category +end + +function PANEL:AddModelPreview(Model) + local Panel = self:AddPanel("DModelPanel") + Panel:SetModel(Model or "models/props_junk/PopCan01a.mdl") + Panel:SetLookAt(Vector()) + Panel:SetCamPos(Vector(45, 60, 45)) + Panel:SetHeight(80) + Panel:SetFOV(75) + + Panel.LayoutEntity = function() end + + return Panel +end + +function PANEL:PerformLayout() + self:SizeToChildren(true, true) +end + +function PANEL:GenerateExample() +end + +derma.DefineControl("ACF_Panel", "", PANEL, "Panel") \ No newline at end of file diff --git a/lua/weapons/acf_base/cl_init.lua b/lua/weapons/acf_base/cl_init.lua index 6ff0c9414..e2031cd31 100644 --- a/lua/weapons/acf_base/cl_init.lua +++ b/lua/weapons/acf_base/cl_init.lua @@ -4,11 +4,15 @@ SWEP.Slot = 1 SWEP.SlotPos = 1 SWEP.DrawAmmo = false SWEP.DrawCrosshair = false -SWEP.ScreenFactor = {} -SWEP.ScreenFactor.w = surface.ScreenWidth() -SWEP.ScreenFactor.h = surface.ScreenHeight() -SWEP.FloatingAim = {} -SWEP.FloatingAim.bounds = 0.3 + +SWEP.ScreenFactor = { + w = ScrW(), + h = ScrH(), +} + +SWEP.FloatingAim = { + bounds = 0.3, +} function SWEP:Initialize() self:StartUp() @@ -65,18 +69,18 @@ end function SWEP:ApplyRecoil(Recoil) local RecoilAng = Angle(Recoil * math.Rand(-1, -0.25), Recoil * math.Rand(-0.25, 0.25), 0) - self.Owner:ViewPunch(RecoilAng) + self:GetOwner():ViewPunch(RecoilAng) end --Clientside effect, for Viewmodel stuff function SWEP:MuzzleEffect() self:SendWeaponAnim(ACT_VM_PRIMARYATTACK) - self.Owner:MuzzleFlash() + self:GetOwner():MuzzleFlash() end function SWEP:StartUp() print("Starting Client") - self.FOV = self.Owner:GetFOV() + self.FOV = self:GetOwner():GetFOV() self.ViewModelFOV = self.FOV self.LastIrons = 0 --hook.Add("InputMouseApply","ACF_SWEPFloatingCrosshair",ACF_SWEPFloatingCrosshair) diff --git a/lua/weapons/acf_base/init.lua b/lua/weapons/acf_base/init.lua index c37f94c37..ffd7ae12a 100644 --- a/lua/weapons/acf_base/init.lua +++ b/lua/weapons/acf_base/init.lua @@ -1,25 +1,36 @@ AddCSLuaFile("cl_init.lua") AddCSLuaFile("shared.lua") + include("shared.lua") + +local AmmoTypes = ACF.Classes.AmmoTypes + SWEP.AutoSwitchTo = false SWEP.AutoSwitchFrom = false function SWEP:Initialize() - self.Primary.BulletData = {} - self.ConvertData = ACF.RoundTypes[self.Primary.UserData["Type"]]["convert"] --Call the correct function for this round type to convert user input data into ballistics data - self.Primary.BulletData = self:ConvertData(self.Primary.UserData) --Put the results into the BulletData table - self.NetworkData = ACF.RoundTypes[self.Primary.UserData["Type"]]["network"] - self:NetworkData(self.Primary.BulletData) + local UserData = self.Primary.UserData + local AmmoType = AmmoTypes[UserData.Type] + local BulletData + + if SERVER then + BulletData = AmmoType:ServerConvert(UserData) + + AmmoType:Network(self, BulletData) - if (SERVER) then self:SetWeaponHoldType("ar2") - --self.Owner:GiveAmmo( self.Primary.DefaultClip, self.Primary.Ammo ) + --self.Owner:GiveAmmo( self.Primary.DefaultClip, self.Primary.Ammo ) + else + BulletData = AmmoType:ClientConvert(UserData) end + + self.Primary.BulletData = BulletData + self.Primary.RoundData = AmmoType end function SWEP:Reload() - if (self:Clip1() < self.Primary.ClipSize and self.Owner:GetAmmoCount(self.Primary.Ammo) > 0) then - self:EmitSound("weapons/AMR/sniper_reload.wav", 70, 110, ACF.SoundVolume) + if (self:Clip1() < self.Primary.ClipSize and self:GetOwner():GetAmmoCount(self.Primary.Ammo) > 0) then + self:EmitSound("weapons/AMR/sniper_reload.wav", 70, 110, ACF.Volume) self:DefaultReload(ACT_VM_RELOAD) end end @@ -27,7 +38,7 @@ end function SWEP:Think() if self.OwnerIsNPC then return end - if self.Owner:KeyDown(IN_USE) then + if self:GetOwner():KeyDown(IN_USE) then self:CrateReload() end @@ -36,29 +47,40 @@ end --Server side effect, for external stuff function SWEP:MuzzleEffect() - self:EmitSound("weapons/AMR/sniper_fire.wav", nil, nil, ACF.SoundVolume) - self.Owner:MuzzleFlash() - self.Owner:SetAnimation(PLAYER_ATTACK1) + local Owner = self:GetOwner() + + self:EmitSound("weapons/AMR/sniper_fire.wav", nil, nil, ACF.Volume) + + Owner:MuzzleFlash() + Owner:SetAnimation(PLAYER_ATTACK1) end function SWEP:CrateReload() - local ViewTr = {} - ViewTr.start = self.Owner:GetShootPos() - ViewTr.endpos = self.Owner:GetShootPos() + self.Owner:GetAimVector() * 128 - ViewTr.filter = {self.Owner, self} + local Owner = self:GetOwner() + local ViewTr = { + start = Owner:GetShootPos(), + endpos = Owner:GetShootPos() + Owner:GetAimVector() * 128, + filter = { Owner, self }, + } + local ViewRes = util.TraceLine(ViewTr) --Trace to see if it will hit anything if SERVER then local AmmoEnt = ViewRes.Entity - if AmmoEnt and AmmoEnt:IsValid() and AmmoEnt.Ammo > 0 and AmmoEnt.RoundId == self.Primary.UserData["Id"] then - local CurAmmo = self.Owner:GetAmmoCount(self.Primary.Ammo) + if IsValid(AmmoEnt) and AmmoEnt.Ammo > 0 and AmmoEnt.RoundId == self.Primary.UserData["Id"] then + local CurAmmo = Owner:GetAmmoCount(self.Primary.Ammo) local Transfert = math.min(AmmoEnt.Ammo, self.Primary.DefaultClip - CurAmmo) + local AmmoType = AmmoTypes[AmmoEnt.AmmoType] + AmmoEnt.Ammo = AmmoEnt.Ammo - Transfert - self.Owner:GiveAmmo(Transfert, self.Primary.Ammo) + + Owner:GiveAmmo(Transfert, self.Primary.Ammo) + self.Primary.BulletData = AmmoEnt.BulletData - self.NetworkData = ACF.RoundTypes[AmmoEnt.RoundType]["network"] - self:NetworkData(self.Primary.BulletData) + self.Primary.RoundData = AmmoType + + AmmoType:Network(self, self.Primary.BulletData) return true end @@ -66,11 +88,13 @@ function SWEP:CrateReload() end function SWEP:StartUp() + local Owner = self:GetOwner() + self:SetDTBool(0, false) self.LastIrons = 0 - if self.Owner then - self.OwnerIsNPC = self.Owner:IsNPC() -- This ought to be better than getting it every time we fire + if Owner then + self.OwnerIsNPC = Owner:IsNPC() -- This ought to be better than getting it every time we fire end end diff --git a/lua/weapons/acf_base/shared.lua b/lua/weapons/acf_base/shared.lua index 6faec873b..ba92c42da 100644 --- a/lua/weapons/acf_base/shared.lua +++ b/lua/weapons/acf_base/shared.lua @@ -61,8 +61,9 @@ function SWEP:PrimaryAttack() self:ApplyRecoil(math.min(Recoil,50)) self:MuzzleEffect() else - local MuzzlePos = self.Owner:GetShootPos() - local MuzzleVec = self.Owner:GetAimVector() + local Owner = self:GetOwner() + local MuzzlePos = Owner:GetShootPos() + local MuzzleVec = Owner:GetAimVector() local Speed = self.Primary.BulletData["MuzzleVel"] local Modifiers = self:CalculateModifiers() --local Recoil = (self.Primary.BulletData["ProjMass"] * self.Primary.BulletData["MuzzleVel"] + self.Primary.BulletData["PropMass"] * 3000)/self.Weight @@ -74,11 +75,11 @@ function SWEP:PrimaryAttack() self.Primary.BulletData["Pos"] = MuzzlePos self.Primary.BulletData["Flight"] = (MuzzleVec + Inaccuracy):GetNormalized() * Speed * 39.37 + self:GetVelocity() - self.Primary.BulletData["Owner"] = self.Owner - self.Primary.BulletData["Gun"] = self.Owner + self.Primary.BulletData["Owner"] = Owner + self.Primary.BulletData["Gun"] = Owner self.Primary.BulletData["Crate"] = self:EntIndex() - self.CreateShell = ACF.RoundTypes[self.Primary.BulletData["Type"]]["create"] - self:CreateShell( self.Primary.BulletData ) + + self.Primary.RoundData:Create(self, self.Primary.BulletData) self:TakePrimaryAmmo(1) @@ -103,21 +104,20 @@ end -- Acuracy/recoil modifiers function SWEP:CalculateModifiers() - + local Owner = self:GetOwner() local modifier = 1 - if self.Owner:KeyDown(IN_FORWARD or IN_BACK or IN_MOVELEFT or IN_MOVERIGHT) then + if Owner:KeyDown(IN_FORWARD or IN_BACK or IN_MOVELEFT or IN_MOVERIGHT) then modifier = modifier * 2 end - if not self.Owner:IsOnGround() then + if not Owner:IsOnGround() then modifier = modifier * 2 --You can't be jumping and crouching at the same time, so return here return modifier end - if self.Owner:Crouching() then + if Owner:Crouching() then modifier = modifier * 0.5 end return modifier - end diff --git a/lua/weapons/gmod_tool/stools/acf_menu.lua b/lua/weapons/gmod_tool/stools/acf_menu.lua new file mode 100644 index 000000000..2072ec5b3 --- /dev/null +++ b/lua/weapons/gmod_tool/stools/acf_menu.lua @@ -0,0 +1,49 @@ +ACF.LoadToolFunctions(TOOL) + +TOOL.Name = "ACF Menu" + +if CLIENT then + local DrawBoxes = GetConVar("acf_drawboxes") + + -- "Hitbox" colors + local Sensitive = Color(255, 0, 0, 50) + local NotSoSensitive = Color(255, 255, 0, 50) + + language.Add("Tool.acf_menu.name", "Armored Combat Framework") + language.Add("Tool.acf_menu.desc", "Main menu tool for the ACF addon") + + function TOOL:DrawHUD() + local Trace = LocalPlayer():GetEyeTrace() + local Distance = Trace.StartPos:DistToSqr(Trace.HitPos) + local Entity = Trace.Entity + + cam.Start3D() + render.SetColorMaterial() + + if DrawBoxes:GetBool() and IsValid(Entity) and Distance <= 65536 then + hook.Run("ACF_DrawBoxes", Entity, Trace) + end + + cam.End3D() + end + + TOOL.BuildCPanel = ACF.CreateSpawnMenu + + concommand.Add("acf_reload_spawn_menu", function() + if not IsValid(ACF.SpawnMenu) then return end + + ACF.CreateSpawnMenu(ACF.SpawnMenu.Panel) + end) + + hook.Add("ACF_DrawBoxes", "ACF Draw Hitboxes", function(Entity) + if not Entity.HitBoxes then return end + if not next(Entity.HitBoxes) then return end + + for _, Tab in pairs(Entity.HitBoxes) do + local Pos = Entity:LocalToWorld(Tab.Pos) + local Ang = Entity:LocalToWorldAngles(Tab.Angle) + + render.DrawWireframeBox(Pos, Ang, Tab.Scale * -0.5, Tab.Scale * 0.5, Tab.Sensitive and Sensitive or NotSoSensitive) + end + end) +end diff --git a/lua/weapons/gmod_tool/stools/acfarmorprop.lua b/lua/weapons/gmod_tool/stools/acfarmorprop.lua index 4ec7e012a..917d69893 100644 --- a/lua/weapons/gmod_tool/stools/acfarmorprop.lua +++ b/lua/weapons/gmod_tool/stools/acfarmorprop.lua @@ -22,10 +22,10 @@ end local function ApplySettings(_, Entity, Data) if CLIENT then return end if not Data then return end - if not ACF_Check(Entity) then return end + if not ACF.Check(Entity) then return end if Data.Mass then - local PhysObj = Entity.ACF.PhysObj -- If it passed ACF_Check, then the PhysObj will always be valid + local PhysObj = Entity.ACF.PhysObj -- If it passed ACF.Check, then the PhysObj will always be valid local Mass = math.Clamp(Data.Mass, 0.1, 50000) PhysObj:SetMass(Mass) @@ -41,13 +41,13 @@ local function ApplySettings(_, Entity, Data) duplicator.StoreEntityModifier(Entity, "acfsettings", { Ductility = Ductility }) end - ACF_Check(Entity, true) -- Forcing the entity to update its information + ACF.Check(Entity, true) -- Forcing the entity to update its information end if CLIENT then language.Add("tool.acfarmorprop.name", "ACF Armor Properties") language.Add("tool.acfarmorprop.desc", "Sets the weight of a prop by desired armor thickness and ductility.") - language.Add("tool.acfarmorprop.0", "Left click to apply settings. Right click to copy settings. Reload to get the total mass of an object and all constrained objects.") + language.Add("tool.acfarmorprop.0", "Left click to apply settings. Right click to copy settings. Reload to get the total mass of an object and all constrained objects.") surface.CreateFont("Torchfont", { size = 40, weight = 1000, font = "arial" }) @@ -69,7 +69,7 @@ if CLIENT then Panel:ControlHelp("Set the desired armor thickness (in mm) and the mass will be adjusted accordingly.") Panel:NumSlider("Ductility", "acfarmorprop_ductility", -80, 80) - Panel:ControlHelp("Set the desired armor ductility (thickness-vs-health bias). A ductile prop can survive more damage but is penetrated more easily (slider > 0). A non-ductile prop is brittle - hardened against penetration, but more easily shattered by bullets and explosions (slider < 0).") + Panel:ControlHelp("Set the desired armor ductility (thickness-vs-health bias). A ductile prop can survive more damage but is penetrated more easily (slider > 0). A non-ductile prop is brittle - hardened against penetration, but more easily shattered by bullets and explosions (slider < 0).") local SphereCheck = Panel:CheckBox("Use sphere search for armor readout", "acfarmorprop_sphere_search") Panel:ControlHelp("If checked, the tool will find all the props in a sphere around the hit position instead of getting all the entities connected to a prop.") @@ -228,7 +228,7 @@ else -- Serverside-only stuff local Ductility = Entity.ACF.Ductility local Thickness = Entity.ACF.MaxArmour - ACF_Check(Entity) -- We need to update again to get the Area + ACF.Check(Entity) -- We need to update again to get the Area local Area = Entity.ACF.Area local Mass = CalcArmor(Area, Ductility, Thickness) @@ -247,7 +247,7 @@ else -- Serverside-only stuff local Weapon = self.Weapon - if ACF_Check(Ent) then + if ACF.Check(Ent) then Player:ConCommand("acfarmorprop_area " .. Ent.ACF.Area) Player:ConCommand("acfarmorprop_thickness " .. self:GetClientNumber("thickness")) -- Force sliders to update themselves @@ -274,7 +274,7 @@ else -- Serverside-only stuff hook.Add("ProperClippingPhysicsClipped", "ACF Physclip Armor", UpdateMass) hook.Add("ProperClippingPhysicsReset", "ACF Physclip Armor", UpdateMass) hook.Add("ProperClippingCanPhysicsClip", "ACF PhysClip Armor", function(Entity) - ACF_Check(Entity, true) -- Just creating the ACF table on the entity + ACF.Check(Entity, true) -- Just creating the ACF table on the entity end) duplicator.RegisterEntityModifier("acfsettings", ApplySettings) @@ -300,7 +300,7 @@ function TOOL:LeftClick(Trace) if not IsValid(Ent) then return false end if Ent:IsPlayer() or Ent:IsNPC() then return false end if CLIENT then return true end - if not ACF_Check(Ent) then return false end + if not ACF.Check(Ent) then return false end local Player = self:GetOwner() @@ -323,7 +323,7 @@ function TOOL:RightClick(Trace) if not IsValid(Ent) then return false end if Ent:IsPlayer() or Ent:IsNPC() then return false end if CLIENT then return true end - if not ACF_Check(Ent) then return false end + if not ACF.Check(Ent) then return false end local Player = self:GetOwner() @@ -359,7 +359,7 @@ do -- Armor readout local PhysTotal = 0 for _, Ent in ipairs(Entities) do - if not ACF_Check(Ent) then + if not ACF.Check(Ent) then if not Ent:IsWeapon() then -- We don't want to count weapon entities OtherNum = OtherNum + 1 end diff --git a/lua/weapons/gmod_tool/stools/acfcopy.lua b/lua/weapons/gmod_tool/stools/acfcopy.lua index dba5566bd..08783aba7 100644 --- a/lua/weapons/gmod_tool/stools/acfcopy.lua +++ b/lua/weapons/gmod_tool/stools/acfcopy.lua @@ -1,92 +1,16 @@ -local cat = ((ACF.CustomToolCategory and ACF.CustomToolCategory:GetBool()) and "ACF" or "Construction") -TOOL.Category = cat -TOOL.Name = "#Tool.acfcopy.listname" -TOOL.Author = "looter" -TOOL.Command = nil -TOOL.ConfigName = "" -TOOL.GearboxCopyData = {} -TOOL.AmmoCopyData = {} +ACF.LoadToolFunctions(TOOL) + +TOOL.Name = "ACF Copy Tool" if CLIENT then - language.Add("Tool.acfcopy.listname", "ACF Copy Tool") language.Add("Tool.acfcopy.name", "Armored Combat Framework") - language.Add("Tool.acfcopy.desc", "Copy ammo or gearbox data from one object to another") - language.Add("Tool.acfcopy.0", "Left click to paste data, Right click to copy data") - - function TOOL.BuildCPanel() - end -end + language.Add("Tool.acfcopy.desc", "Copy information from one ACF entity to another") --- Update -function TOOL:LeftClick(trace) - if CLIENT then return end - local ent = trace.Entity - if not IsValid(ent) then return false end - local pl = self:GetOwner() + TOOL.BuildCPanel = ACF.CreateCopyMenu - if (ent:GetClass() == "acf_gearbox" and #self.GearboxCopyData > 1 and ent.CanUpdate) then - local success, msg = ent:Update(self.GearboxCopyData) - ACF_SendNotify(pl, success, msg) - end + concommand.Add("acf_reload_copy_menu", function() + if not IsValid(ACF.CopyMenu) then return end - if (ent:GetClass() == "acf_ammo" and #self.AmmoCopyData > 1 and ent.CanUpdate) then - local success, msg = ent:Update(self.AmmoCopyData) - ACF_SendNotify(pl, success, msg) - end - - return true + ACF.CreateCopyMenu(ACF.CopyMenu.Panel) + end) end - --- Copy -function TOOL:RightClick(trace) - if CLIENT then return end - local ent = trace.Entity - if not IsValid(ent) then return false end - local pl = self:GetOwner() - - if (ent:GetClass() == "acf_gearbox") then - local ArgsTable = {} - -- zero out the un-needed tool trace information - ArgsTable[1] = pl - ArgsTable[2] = 0 - ArgsTable[3] = 0 - ArgsTable[4] = ent.Id - -- build gear data - ArgsTable[5] = ent.GearTable[1] - ArgsTable[6] = ent.GearTable[2] - ArgsTable[7] = ent.GearTable[3] - ArgsTable[8] = ent.GearTable[4] - ArgsTable[9] = ent.GearTable[5] - ArgsTable[10] = ent.GearTable[6] - ArgsTable[11] = ent.GearTable[7] - ArgsTable[12] = ent.GearTable[8] - ArgsTable[13] = ent.GearTable[9] - ArgsTable[14] = ent.GearTable.Final - self.GearboxCopyData = ArgsTable - ACF_SendNotify(pl, true, "Gearbox copied successfully!") - end - - if (ent:GetClass() == "acf_ammo") then - -- zero out the un-needed tool trace information - self.AmmoCopyData = { - pl, - 0, - 0, - -- build ammo data - ent.RoundId, - ent.RoundType, - ent.RoundPropellant, - ent.RoundProjectile, - ent.RoundData5, - ent.RoundData6, - ent.RoundData7, - ent.RoundData8, - ent.RoundData9, - ent.RoundData10, - } - - ACF_SendNotify(pl, true, "Ammo copied successfully!") - end - - return true -end \ No newline at end of file diff --git a/lua/weapons/gmod_tool/stools/acfmenu.lua b/lua/weapons/gmod_tool/stools/acfmenu.lua deleted file mode 100644 index b01c3cb61..000000000 --- a/lua/weapons/gmod_tool/stools/acfmenu.lua +++ /dev/null @@ -1,239 +0,0 @@ - -local cat = ((ACF.CustomToolCategory and ACF.CustomToolCategory:GetBool()) and "ACF" or "Construction"); - -TOOL.Category = cat -TOOL.Name = "#Tool.acfmenu.listname" -TOOL.Command = nil -TOOL.ConfigName = "" - -TOOL.ClientConVar[ "type" ] = "gun" -TOOL.ClientConVar[ "id" ] = "12.7mmMG" - -TOOL.ClientConVar[ "data1" ] = "12.7mmMG" -TOOL.ClientConVar[ "data2" ] = "AP" -TOOL.ClientConVar[ "data3" ] = 0 -TOOL.ClientConVar[ "data4" ] = 0 -TOOL.ClientConVar[ "data5" ] = 0 -TOOL.ClientConVar[ "data6" ] = 0 -TOOL.ClientConVar[ "data7" ] = 0 -TOOL.ClientConVar[ "data8" ] = 0 -TOOL.ClientConVar[ "data9" ] = 0 -TOOL.ClientConVar[ "data10" ] = 0 -TOOL.ClientConVar[ "data11" ] = 24 -TOOL.ClientConVar[ "data12" ] = 24 -TOOL.ClientConVar[ "data13" ] = 24 - -cleanup.Register( "acfmenu" ) - -if CLIENT then - language.Add( "Tool.acfmenu.listname", "ACF Menu" ) - language.Add( "Tool.acfmenu.name", "Armored Combat Framework" ) - language.Add( "Tool.acfmenu.desc", "Spawn the Armored Combat Framework weapons and ammo" ) - language.Add( "Tool.acfmenu.0", "Left click to spawn the entity of your choice, Right click to link an entity to another (+Use to unlink)" ) - language.Add( "Tool.acfmenu.1", "Right click to link the selected sensor to a pod" ) - - language.Add( "Undone_ACF Entity", "Undone ACF Entity" ) - language.Add( "Undone_acf_engine", "Undone ACF Engine" ) - language.Add( "Undone_acf_gearbox", "Undone ACF Gearbox" ) - language.Add( "Undone_acf_ammo", "Undone ACF Ammo" ) - language.Add( "Undone_acf_gun", "Undone ACF Gun" ) - language.Add( "SBoxLimit_acf_gun", "You've reached the ACF Guns limit!" ) - language.Add( "SBoxLimit_acf_rack", "You've reached the ACF Launchers limit!" ) - language.Add( "SBoxLimit_acf_ammo", "You've reached the ACF Explosives limit!" ) - language.Add( "SBoxLimit_acf_sensor", "You've reached the ACF Sensors limit!" ) - - local DrawBoxes = CreateConVar("acf_drawboxes", 1, FCVAR_ARCHIVE, "Whether or not to draw hitboxes on ACF entities", 0, 1) - - function TOOL.BuildCPanel( CPanel ) - - local pnldef_ACFmenu = vgui.RegisterFile( "acf/client/sk_menu.lua" ) - - -- create - local DPanel = vgui.CreateFromTable( pnldef_ACFmenu ) - CPanel:AddPanel( DPanel ) - - end - - -- "Hitbox" colors - local Sensitive = Color(255, 0, 0, 50) - local NotSoSensitive = Color(255, 255, 0, 50) - - -- Ammo overlay colors - local Blue = Color(0, 127, 255, 65) - local Orange = Color(255, 127, 0, 65) - local Green = Color(0, 255, 0, 65) - local Red = Color(255,0,0,65) - - function TOOL:DrawHUD() - if not DrawBoxes:GetBool() then return end - - local Trace = LocalPlayer():GetEyeTrace() - local Distance = Trace.StartPos:DistToSqr(Trace.HitPos) - local Ent = Trace.Entity - - if not IsValid(Ent) then return end - if Distance > 65536 then return end - - cam.Start3D() - render.SetColorMaterial() - - if Ent.HitBoxes then -- Draw "hitboxes" - for _, Tab in pairs(Ent.HitBoxes) do - render.DrawWireframeBox(Ent:LocalToWorld(Tab.Pos), Ent:LocalToWorldAngles(Tab.Angle), Tab.Scale * -0.5, Tab.Scale * 0.5, Tab.Sensitive and Sensitive or NotSoSensitive) - end - end - - if not Ent.IsScalable then cam.End3D() return end - if not Ent.HasData then - if Ent.HasData == nil and Ent.RequestAmmoData then - Ent:RequestAmmoData() - end - - cam.End3D() - return - end - - local FinalAmmo = Ent.HasBoxedAmmo and math.floor(Ent.Ammo / Ent.MagSize) or Ent.Ammo - - if FinalAmmo > 0 and Ent.FitPerAxis then - local RoundsDisplay = 0 - local RoundAngle = Ent:LocalToWorldAngles(Ent.LocalAng) - local StartPos = ((Ent.FitPerAxis.x - 1) * (Ent.RoundSize.x + Ent.Spacing) * RoundAngle:Forward()) + - ((Ent.FitPerAxis.y - 1) * (Ent.RoundSize.y + Ent.Spacing) * RoundAngle:Right()) + - ((Ent.FitPerAxis.z - 1) * (Ent.RoundSize.z + Ent.Spacing) * RoundAngle:Up()) - - if not Ent.BulkDisplay then - for RX = 1, Ent.FitPerAxis.x do - for RY = 1, Ent.FitPerAxis.y do - for RZ = 1, Ent.FitPerAxis.z do - local LocalPos = ((RX - 1) * (Ent.RoundSize.x + Ent.Spacing) * -RoundAngle:Forward()) + - ((RY - 1) * (Ent.RoundSize.y + Ent.Spacing) * -RoundAngle:Right()) + - ((RZ - 1) * (Ent.RoundSize.Z + Ent.Spacing) * -RoundAngle:Up()) - - if RoundsDisplay < FinalAmmo then - local C = Ent.IsRound and Blue or Ent.HasBoxedAmmo and Green or Orange - - RoundsDisplay = RoundsDisplay + 1 - - render.DrawWireframeBox(Ent:LocalToWorld(Ent:OBBCenter()) + (StartPos / 2) + LocalPos, RoundAngle, -Ent.RoundSize / 2, Ent.RoundSize / 2, C) - end - - if RoundsDisplay == FinalAmmo then break end - end - end - end - else -- Basic bitch box that scales according to ammo, only for bulk display - local AmmoPerc = Ent.Ammo / Ent.Capacity - local SizeAdd = Vector(Ent.Spacing, Ent.Spacing, Ent.Spacing) * Ent.FitPerAxis - local BulkSize = (Ent.FitPerAxis * Ent.RoundSize * Vector(1, AmmoPerc, 1)) + SizeAdd - - render.DrawWireframeBox(Ent:LocalToWorld(Ent:OBBCenter()) + (RoundAngle:Right() * (Ent.FitPerAxis.y * Ent.RoundSize.y) * 0.5 * (1 - AmmoPerc)),RoundAngle,-BulkSize / 2, BulkSize / 2, Red) - end - end - - cam.End3D() - end -end - --- Spawn/update functions -function TOOL:LeftClick(Trace) - if CLIENT then return true end - if not IsValid(Trace.Entity) and not Trace.Entity:IsWorld() then return false end - - local Player = self:GetOwner() - local Type = self:GetClientInfo("type") - local Id = self:GetClientInfo("id") - local TypeId = ACF.Weapons[Type][Id] - - if not TypeId then return false end - - local DupeClass = duplicator.FindEntityClass(TypeId.ent) - - if not DupeClass then - print("Didn't find entity duplicator records") - return false - end - - local Class = TypeId.ent - local ArgList = list.Get("ACFCvars") - local ArgTable = { - Player, - Trace.HitPos + Trace.HitNormal * 96, - Trace.HitNormal:Angle():Up():Angle(), - } - - -- Reading the list packaged with the ent to see what client CVar it needs - for K, V in ipairs(ArgList[Class]) do - local Info = self:GetClientInfo(V) - - ArgTable[K + 3] = Info ~= "" and Info or false - end - - if Trace.Entity:GetClass() == Class and Trace.Entity.CanUpdate then - local Success, Message = Trace.Entity:Update(ArgTable) - - ACF_SendNotify(Player, Success, Message) - else - -- Using the Duplicator entity register to find the right factory function - local Ent = DupeClass.Func(unpack(ArgTable)) - - if not IsValid(Ent) then - ACF_SendNotify(Player, false, "Couldn't create entity.") - return false - end - - Ent:Activate() - Ent:DropToFloor() - - if CPPI then - Ent:CPPISetOwner(Player) - end - - local PhysObj = Ent:GetPhysicsObject() - - if IsValid(PhysObj) then - PhysObj:EnableMotion(false) - PhysObj:Sleep() - end - - undo.Create(Class) - undo.AddEntity(Ent) - undo.SetPlayer(Player) - undo.Finish() - end - - return true -end - --- Link/unlink functions -function TOOL:RightClick(Trace) - if not IsValid(Trace.Entity) then return false end - if CLIENT then return true end - - local Player = self:GetOwner() - local Entity = Trace.Entity - - if self:GetStage() == 0 and Entity.Link then - self.Master = Entity - self:SetStage(1) - - return true - elseif self:GetStage() == 1 then - local Success, Message - - if Player:KeyDown(IN_USE) or Player:KeyDown(IN_SPEED) then - Success, Message = self.Master:Unlink(Entity) - else - Success, Message = self.Master:Link(Entity) - end - - ACF_SendNotify(Player, Success, Message) - - self:SetStage(0) - self.Master = nil - - return true - end - - return false -end \ No newline at end of file diff --git a/lua/weapons/gmod_tool/stools/acfsound.lua b/lua/weapons/gmod_tool/stools/acfsound.lua index c1104fc74..99b427dc5 100644 --- a/lua/weapons/gmod_tool/stools/acfsound.lua +++ b/lua/weapons/gmod_tool/stools/acfsound.lua @@ -11,54 +11,67 @@ if CLIENT then language.Add("Tool.acfsound.0", "Left click to apply sound. Right click to copy sound. Reload to set default sound. Use an empty sound path to disable sound.") end -ACF.SoundToolSupport = { - acf_gun = { - GetSound = function(ent) - return { - Sound = ent.Sound - } - end, - SetSound = function(ent, soundData) - ent.Sound = soundData.Sound - ent:SetNWString("Sound", soundData.Sound) - end, - ResetSound = function(ent) - local Class = ent.Class - local Classes = ACF.Classes - - local soundData = { - Sound = Classes["GunClass"][Class]["sound"] - } - - local setSound = ACF.SoundToolSupport["acf_gun"].SetSound - setSound(ent, soundData) - end - }, - acf_engine = { - GetSound = function(ent) - return { - Sound = ent.SoundPath, - Pitch = ent.SoundPitch - } - end, - SetSound = function(ent, soundData) - ent.SoundPath = soundData.Sound - ent.SoundPitch = soundData.Pitch - end, - ResetSound = function(ent) - local Id = ent.Id - local List = ACF.Weapons - local pitch = List["Mobility"][Id]["pitch"] or 1 - - local soundData = { - Sound = List["Mobility"][Id]["sound"], - Pitch = pitch - } - - local setSound = ACF.SoundToolSupport["acf_engine"].SetSound - setSound(ent, soundData) - end - } +ACF.SoundToolSupport = ACF.SoundToolSupport or {} + +local Sounds = ACF.SoundToolSupport + +Sounds.acf_gun = { + GetSound = function(ent) + return { + Sound = ent.SoundPath + } + end, + SetSound = function(ent, soundData) + ent.SoundPath = soundData.Sound + ent:SetNWString("Sound", soundData.Sound) + end, + ResetSound = function(ent) + local setSound = Sounds.acf_gun.SetSound + + setSound(ent, { Sound = ent.DefaultSound }) + end +} + +Sounds.acf_engine = { + GetSound = function(ent) + return { + Sound = ent.SoundPath, + Pitch = ent.SoundPitch + } + end, + SetSound = function(ent, soundData) + ent.SoundPath = soundData.Sound + ent.SoundPitch = soundData.Pitch + end, + ResetSound = function(ent) + local setSound = Sounds.acf_engine.SetSound + + setSound(ent, { Sound = ent.DefaultSound }) + end +} + +Sounds.acf_gearbox = { + GetSound = function(ent) + return { Sound = ent.SoundPath } + end, + SetSound = function(ent, soundData) + ent.SoundPath = soundData.Sound + end, + ResetSound = function(ent) + ent.SoundPath = ent.DefaultSound + end +} + +Sounds.acf_piledriver = { + GetSound = function(ent) + return { Sound = ent.SoundPath or "" } + end, + SetSound = function(ent, soundData) + ent.SoundPath = soundData.Sound + end, + ResetSound = function(ent) + ent.SoundPath = nil + end } local function ReplaceSound(_, Entity, data) @@ -90,9 +103,9 @@ local function IsReallyValid(trace, ply) if not ACF.SoundToolSupport[class] then if string.StartWith(class, "acf_") then - ACF_SendNotify(ply, false, class .. " is not supported by the sound tool!") + ACF.SendNotify(ply, false, class .. " is not supported by the sound tool!") else - ACF_SendNotify(ply, false, "Only ACF entities are supported by the ACF sound tool!") + ACF.SendNotify(ply, false, "Only ACF entities are supported by the ACF sound tool!") end return false diff --git a/lua/weapons/torch/shared.lua b/lua/weapons/torch/shared.lua index 5f2785b6a..9c9a6a9c2 100644 --- a/lua/weapons/torch/shared.lua +++ b/lua/weapons/torch/shared.lua @@ -30,6 +30,9 @@ SWEP.DrawAmmo = false SWEP.DrawCrosshair = true SWEP.MaxDistance = 64 * 64 -- Squared distance +local Spark = "ambient/energy/NewSpark0%s.wav" +local Zap = "weapons/physcannon/superphys_small_zap%s.wav" + local function TeslaSpark(pos, magnitude) zap = ents.Create("point_tesla") zap:SetKeyValue("targetname", "teslab") @@ -72,13 +75,13 @@ function SWEP:Think() if CLIENT then return end local Health, MaxHealth, Armor, MaxArmor = 0, 0, 0, 0 - local Trace = self.Owner:GetEyeTrace() + local Trace = self:GetOwner():GetEyeTrace() local Entity = Trace.Entity self.LastDistance = Trace.StartPos:DistToSqr(Trace.HitPos) self.LastTrace = Trace - if ACF_Check(Entity) and self.LastDistance <= self.MaxDistance then + if ACF.Check(Entity) and self.LastDistance <= self.MaxDistance then if Entity:IsPlayer() or Entity:IsNPC() then Health = Entity:Health() MaxHealth = Entity:GetMaxHealth() @@ -118,9 +121,9 @@ function SWEP:PrimaryAttack() local Entity = self.LastEntity local Trace = self.LastTrace - local Owner = self.Owner + local Owner = self:GetOwner() - if ACF_Check(Entity) then + if ACF.Check(Entity) then if Entity:IsPlayer() or Entity:IsNPC() then local Health = Entity:Health() local MaxHealth = Entity:GetMaxHealth() @@ -139,25 +142,29 @@ function SWEP:PrimaryAttack() Effect:SetEntity(self) util.Effect("thruster_ring", Effect, true, true) - Entity:EmitSound("items/medshot4.wav", nil, nil, ACF.SoundVolume) + Entity:EmitSound("items/medshot4.wav", nil, nil, ACF.Volume) else if CPPI and not Entity:CPPICanTool(Owner, "torch") then return end - local Health = Entity.ACF.Health + local OldHealth = Entity.ACF.Health local MaxHealth = Entity.ACF.MaxHealth - if Health >= MaxHealth then return end + if OldHealth >= MaxHealth then return end - local Armor = Entity.ACF.Armour + local OldArmor = Entity.ACF.Armour local MaxArmor = Entity.ACF.MaxArmour - Health = math.min(Health + (30 / MaxArmor), MaxHealth) - Armor = MaxArmor * (0.5 + Health / MaxHealth * 0.5) + local Health = math.min(OldHealth + (30 / MaxArmor), MaxHealth) + local Armor = MaxArmor * (0.5 + Health / MaxHealth * 0.5) Entity.ACF.Health = Health Entity.ACF.Armour = Armor - Entity:EmitSound("ambient/energy/NewSpark0" .. math.random(3, 5) .. ".wav", nil, nil, ACF.SoundVolume) + if Entity.ACF_OnRepaired then + Entity:ACF_OnRepaired(OldArmor, OldHealth, Armor, Health) + end + + Entity:EmitSound(Spark:format(math.random(3, 5)), nil, nil, ACF.Volume) TeslaSpark(Trace.HitPos, 1) end end @@ -174,9 +181,9 @@ function SWEP:SecondaryAttack() local Entity = self.LastEntity local Trace = self.LastTrace - local Owner = self.Owner + local Owner = self:GetOwner() - if ACF_Check(Entity) then + if ACF.Check(Entity) then local HitRes = {} if Entity:IsPlayer() or Entity:IsNPC() then @@ -200,7 +207,7 @@ function SWEP:SecondaryAttack() Effect:SetOrigin(Trace.HitPos) util.Effect("Sparks", Effect, true, true) - Entity:EmitSound("weapons/physcannon/superphys_small_zap" .. math.random(1, 4) .. ".wav", nil, nil, ACF.SoundVolume) + Entity:EmitSound(Zap:format(math.random(1, 4)), nil, nil, ACF.Volume) end end end