diff --git a/lua/entities/gmod_wire_expression2/core/unitconv.lua b/lua/entities/gmod_wire_expression2/core/unitconv.lua index e002ba47e2..26324e6aa6 100644 --- a/lua/entities/gmod_wire_expression2/core/unitconv.lua +++ b/lua/entities/gmod_wire_expression2/core/unitconv.lua @@ -2,132 +2,30 @@ Unit conversion \******************************************************************************/ -/* - u - source unit - A Source Unit is 0.75 Inch long, more info here: - http://developer.valvesoftware.com/wiki/Dimensions#Map_Grid_Units:_quick_reference - - mm - millimeter - cm - centimeter - dm - decimeter - m - meter - km - kilometer - in - inch - ft - foot - yd - yard - mi - mile - nmi - nautical mile - - g - gram - kg - kilogram - t - tonne - oz - ounce - lb - pound -*/ - -local speed = { - ["u/s"] = 1 / 0.75, - ["u/m"] = 60 * (1 / 0.75), - ["u/h"] = 3600 * (1 / 0.75), - - ["mm/s"] = 25.4, - ["cm/s"] = 2.54, - ["dm/s"] = 0.254, - ["m/s"] = 0.0254, - ["km/s"] = 0.0000254, - ["in/s"] = 1, - ["ft/s"] = 1 / 12, - ["yd/s"] = 1 / 36, - ["mi/s"] = 1 / 63360, - ["nmi/s"] = 127 / 9260000, - - ["mm/m"] = 60 * 25.4, - ["cm/m"] = 60 * 2.54, - ["dm/m"] = 60 * 0.254, - ["m/m"] = 60 * 0.0254, - ["km/m"] = 60 * 0.0000254, - ["in/m"] = 60, - ["ft/m"] = 60 / 12, - ["yd/m"] = 60 / 36, - ["mi/m"] = 60 / 63360, - ["nmi/m"] = 60 * 127 / 9260000, - - ["mm/h"] = 3600 * 25.4, - ["cm/h"] = 3600 * 2.54, - ["dm/h"] = 3600 * 0.254, - ["m/h"] = 3600 * 0.0254, - ["km/h"] = 3600 * 0.0000254, - ["in/h"] = 3600, - ["ft/h"] = 3600 / 12, - ["yd/h"] = 3600 / 36, - ["mi/h"] = 3600 / 63360, - ["nmi/h"] = 3600 * 127 / 9260000, - - ["mph"] = 3600 / 63360, - ["knots"] = 3600 * 127 / 9260000, - ["mach"] = 0.0254 / 295, -} - -local length = { - ["u"] = 1 / 0.75, - - ["mm"] = 25.4, - ["cm"] = 2.54, - ["dm"] = 0.254, - ["m"] = 0.0254, - ["km"] = 0.0000254, - ["in"] = 1, - ["ft"] = 1 / 12, - ["yd"] = 1 / 36, - ["mi"] = 1 / 63360, - ["nmi"] = 127 / 9260000, -} - -local weight = { - ["g"] = 1000, - ["kg"] = 1, - ["t"] = 0.001, - ["oz"] = 1 / 0.028349523125, - ["lb"] = 1 / 0.45359237, -} - __e2setcost(2) -- approximated -e2function number toUnit(string rv1, rv2) +local unitconv = WireLib.UnitConv - if speed[rv1] then - return (rv2 * 0.75) * speed[rv1] - elseif length[rv1] then - return (rv2 * 0.75) * length[rv1] - elseif weight[rv1] then - return rv2 * weight[rv1] - end - - return -1 +e2function number toUnit(string rv1, rv2) + for _, tbl in pairs(unitconv) do + if tbl[rv1] then + return rv2 * tbl[rv1] + end + end + return -1 end e2function number fromUnit(string rv1, rv2) - - if speed[rv1] then - return (rv2 / 0.75) / speed[rv1] - elseif length[rv1] then - return (rv2 / 0.75) / length[rv1] - elseif weight[rv1] then - return rv2 / weight[rv1] - end - - return -1 + for _, tbl in pairs(unitconv) do + if tbl[rv1] then + return rv2 / tbl[rv1] + end + end + return -1 end e2function number convertUnit(string rv1, string rv2, rv3) - - if speed[rv1] and speed[rv2] then - return rv3 * (speed[rv2] / speed[rv1]) - elseif length[rv1] and length[rv2] then - return rv3 * (length[rv2] / length[rv1]) - elseif weight[rv1] and weight[rv2] then - return rv3 * (weight[rv2] / weight[rv1]) - end - - return -1 + local factor = WireLib.ConvertUnit(rv1, rv2) + if factor then return rv3 * factor end + return -1 end diff --git a/lua/entities/gmod_wire_interactiveprop.lua b/lua/entities/gmod_wire_interactiveprop.lua index e3acce6dd4..3bc846dfbe 100644 --- a/lua/entities/gmod_wire_interactiveprop.lua +++ b/lua/entities/gmod_wire_interactiveprop.lua @@ -134,6 +134,19 @@ InteractiveModels = { {type="DNumberScratch", x = 85, y = 55, name="Knob6"}, } }, + ["models/props_lab/reciever01d.mdl"] = { + width = 112, + height = 80, + title="reciever01d", + widgets={ + {type="DNumberScratch", x = 10, y = 30, name="Knob1", color=Color(128,64,64)}, + {type="DNumberScratch", x = 35, y = 30, name="Knob2", color=Color(128,64,64)}, + {type="DNumberScratch", x = 10, y = 55, name="Knob3"}, + {type="DNumberScratch", x = 35, y = 55, name="Knob4"}, + {type="DNumberScratch", x = 60, y = 55, name="Knob5"}, + {type="DNumberScratch", x = 85, y = 55, name="Knob6"}, + } + }, ["models/props_interiors/vendingmachinesoda01a.mdl"] = { width = 60, height = 200, diff --git a/lua/wire/gates/entity.lua b/lua/wire/gates/entity.lua index c634fc80f3..b5c086d603 100644 --- a/lua/wire/gates/entity.lua +++ b/lua/wire/gates/entity.lua @@ -1027,4 +1027,213 @@ GateActions["entity_heading"] = { end } +GateActions["entity_getattachments"] = { + name = "Attachments", + description = "Returns an array of all attachment IDs for the entity's model.", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputs = { "IDs", "Names" }, + outputtypes = { "ARRAY", "ARRAY" }, + output = function(gate, Ent) + if not Ent:IsValid() then return {}, {} end + local attachments = Ent:GetAttachments() + if not attachments or #attachments == 0 then return {}, {} end + local ids = {} + local names = {} + for _, att in ipairs(attachments) do + ids[#ids + 1] = att.id + names[#names + 1] = att.name + end + return ids, names + end, + label = function(Out) + return string.format("Attachments: %d found", #Out.IDs) + end +} + +GateActions["entity_getattachment"] = { + name = "Attachment", + description = "Gets the position and angle of a specific attachment by ID or Name. Return Position/Angles must be > 0 to output their respective values.", + inputs = { "Ent", "ID", "Name", "Return Position", "Return Angles" }, + inputtypes = { "ENTITY", "NORMAL", "STRING", "NORMAL", "NORMAL" }, + outputs = { "Position", "Angles" }, + outputtypes = { "VECTOR", "ANGLE" }, + timed = true, + output = function(gate, Ent, ID, Name, ReturnPos, ReturnAng) + if not Ent:IsValid() then return vec0, ang0 end + + local attachID = ID + if Name and Name ~= "" then + local found = Ent:LookupAttachment(Name) + if found and found > 0 then + attachID = found + end + end + + if not attachID or attachID <= 0 then return vec0, ang0 end + + local attData = Ent:GetAttachment(attachID) + if not attData then return vec0, ang0 end + + local pos = vec0 + local ang = ang0 + + if ReturnPos and ReturnPos > 0 then + pos = attData.Pos + end + if ReturnAng and ReturnAng > 0 then + ang = attData.Ang + end + + return pos, ang + end, + label = function(Out, Ent, ID, Name) + return string.format("attachment(%s, id=%s, name=%q) Pos=(%d,%d,%d) Ang=(%d,%d,%d)", + tostring(Ent), tostring(ID), tostring(Name or ""), + Out.Position.x, Out.Position.y, Out.Position.z, + Out.Angles.p, Out.Angles.y, Out.Angles.r) + end +} + +GateActions["entity_isfrozen"] = { + name = "Is Frozen", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "NORMAL" }, + timed = true, + output = function(gate, Ent) + if not Ent:IsValid() then return 0 end + local phys = Ent:GetPhysicsObject() + if not IsValid(phys) then return 0 end + return phys:IsMoveable() and 0 or 1 + end, + label = function(Out) + return string.format("Is Frozen = %d", Out) + end +} + +GateActions["entity_getchildren"] = { + name = "Children", + description = "Returns an array of all children entities parented to the given entity.", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputs = { "Children", "Count" }, + outputtypes = { "ARRAY", "NORMAL" }, + output = function(gate, Ent) + if not Ent:IsValid() then return {}, 0 end + local keytable = Ent:GetChildren() + local result = {} + local i = 1 + for _, child in pairs(keytable) do + if IsValid(child) then + result[i] = child + i = i + 1 + end + end + return result, #result + end, + label = function(Out) + return string.format("Children: %d found", Out.Count) + end +} + +GateActions["entity_vehicle"] = { + name = "Vehicle", + description = "Returns the vehicle that the player is currently sitting in. Returns NULL if the player is not in a vehicle.", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "ENTITY" }, + timed = true, + output = function(gate, Ent) + if not IsValid(Ent) then return NULL end + if not Ent:IsPlayer() then return NULL end + return Ent:GetVehicle() + end, + label = function(Out, Ent) + return string.format("vehicle(%s) = %s", tostring(Ent), tostring(Out)) + end +} + +local moveTypes = { + [0] = "NONE", + [1] = "ISOMETRIC", + [2] = "WALK", + [3] = "STEP", + [4] = "FLY", + [5] = "FLYGRAVITY", + [6] = "VPHYSICS", + [7] = "PUSH", + [8] = "NOCLIP", + [9] = "LADDER", + [10] = "OBSERVER", + [11] = "CUSTOM", +} + +GateActions["entity_getmovetype"] = { + name = "Move Type", + description = "Returns the move type of an entity.", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "STRING" }, + timed = true, + output = function(gate, Ent) + if not IsValid(Ent) then return moveTypes[0] end + return moveTypes[Ent:GetMoveType()] + end, + label = function(Out, Ent) + return string.format("getMoveType(%s) = %s", tostring(Ent), Out or "") + end +} + +GateActions["entity_activeweapon"] = { + name = "Weapon", + description = "Returns the weapon the player is currently holding.", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "ENTITY" }, + timed = true, + output = function(gate, Ent) + if not IsValid(Ent) then return NULL end + if not Ent:IsPlayer() then return NULL end + return Ent:GetActiveWeapon() + end, + label = function(Out, Ent) + local name = IsValid(Out) and Out:GetClass() or "none" + return string.format("activeWeapon(%s) = %s", tostring(Ent), name) + end +} + +GateActions["entity_weapons"] = { + name = "Weapons", + description = "Returns an array of all weapons in the player's inventory.", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "ARRAY" }, + timed = true, + output = function(gate, Ent) + if not IsValid(Ent) then return {} end + if not Ent:IsPlayer() then return {} end + return Ent:GetWeapons() + end, + label = function(Out, Ent) + return string.format("Weapons(%s) = %d items", tostring(Ent), #Out) + end +} + +GateActions["entity_eyepos"] = { + name = "Eye Position", + description = "Returns the position of the player's eyes.", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "VECTOR" }, + timed = true, + output = function(gate, Ent) + if not IsValid(Ent) or not Ent:IsPlayer() then return vec0 end + return Ent:EyePos() + end, + label = function(Out, Ent) + return string.format("eyePos(%s) = (%d,%d,%d)", tostring(Ent), Out.x, Out.y, Out.z) + end +} + GateActions() diff --git a/lua/wire/gates/find.lua b/lua/wire/gates/find.lua new file mode 100644 index 0000000000..025f7df2be --- /dev/null +++ b/lua/wire/gates/find.lua @@ -0,0 +1,145 @@ +--[[ + Find gates +]] + +GateActions("Find") + +local forbidden_classes = { + ["info_player_allies"] = true, + ["info_player_axis"] = true, + ["info_player_combine"] = true, + ["info_player_counterterrorist"]= true, + ["info_player_deathmatch"] = true, + ["info_player_logo"] = true, + ["info_player_rebel"] = true, + ["info_player_start"] = true, + ["info_player_terrorist"] = true, + ["info_player_blu"] = true, + ["info_player_red"] = true, + ["prop_dynamic"] = true, + ["physgun_beam"] = true, + ["player_manager"] = true, + ["predicted_viewmodel"] = true, + ["gmod_ghost"] = true, +} + +local FIND_RATE = 1 -- seconds between allowed searches + +local function safeClassMatch(class, filter) + if filter == "" then return true end + local ok, result = pcall(string.match, string.lower(class), string.lower(filter)) + return ok and result ~= nil +end + +GateActions["find_incone"] = { + name = "Find In Cone", + description = "Finds all entities within a spherical cone. Returns an array of entities. Filter accepts a Lua pattern for class name. Ignore is an array of entities to exclude. Updates at most once per second.", + inputs = { "Clk", "Position", "Direction", "Length", "Degrees", "Filter", "Ignore" }, + inputtypes = { "NORMAL", "VECTOR", "VECTOR", "NORMAL", "NORMAL", "STRING", "ARRAY" }, + outputs = { "Entities", "Count" }, + outputtypes = { "ARRAY", "NORMAL" }, + timed = true, + output = function(gate, Clk, Position, Direction, Length, Degrees, Filter, Ignore) + if Clk == 0 then + return gate.Outputs.Entities.Value, gate.Outputs.Count.Value + end + + -- rate limit: no more than once per second + local now = CurTime() + if gate._findNextTime and now < gate._findNextTime then + return gate.Outputs.Entities.Value, gate.Outputs.Count.Value + end + gate._findNextTime = now + FIND_RATE + + if not isvector(Position) then Position = Vector(0, 0, 0) end + if not isvector(Direction) then Direction = Vector(0, 0, 1) end + if not Length or Length <= 0 then Length = 1024 end + if not Degrees or Degrees <= 0 then Degrees = 45 end + if not isstring(Filter) then Filter = "" end + + Direction = Direction:GetNormalized() + + local ignoreLookup = {} + if istable(Ignore) then + for _, v in ipairs(Ignore) do + if IsValid(v) then ignoreLookup[v] = true end + end + end + + local cosDegrees = math.cos(math.rad(Degrees)) + local findlist = ents.FindInSphere(Position, Length) + local result = {} + + for _, ent in ipairs(findlist) do + if IsValid(ent) and not ignoreLookup[ent] and not forbidden_classes[ent:GetClass()] then + local dot = Direction:Dot((ent:GetPos() - Position):GetNormalized()) + if dot > cosDegrees then + if safeClassMatch(ent:GetClass(), Filter) then + result[#result + 1] = ent + end + end + end + end + + return result, #result + end, + label = function(Out, Clk, Position, Direction, Length, Degrees, Filter) + return string.format("findInCone(%s, len=%s, deg=%s, filter=%q) = %d", + tostring(Position), tostring(Length), tostring(Degrees), tostring(Filter), Out.Count) + end +} + +GateActions["find_inbox"] = { + name = "Find In Box", + description = "Finds all entities within an axis-aligned box. Returns an array of entities. Filter accepts a Lua pattern for class name. Ignore is an array of entities to exclude. Updates at most once per second.", + inputs = { "Clk", "Min", "Max", "Filter", "Ignore" }, + inputtypes = { "NORMAL", "VECTOR", "VECTOR", "STRING", "ARRAY" }, + outputs = { "Entities", "Count" }, + outputtypes = { "ARRAY", "NORMAL" }, + timed = true, + output = function(gate, Clk, Min, Max, Filter, Ignore) + if Clk == 0 then + return gate.Outputs.Entities.Value, gate.Outputs.Count.Value + end + + -- rate limit: no more than once per second + local now = CurTime() + if gate._findNextTime and now < gate._findNextTime then + return gate.Outputs.Entities.Value, gate.Outputs.Count.Value + end + gate._findNextTime = now + FIND_RATE + + if not isvector(Min) then Min = Vector(0, 0, 0) end + if not isvector(Max) then Max = Vector(0, 0, 0) end + if not isstring(Filter) then Filter = "" end + + local rMin = Vector(math.min(Min.x, Max.x), math.min(Min.y, Max.y), math.min(Min.z, Max.z)) + local rMax = Vector(math.max(Min.x, Max.x), math.max(Min.y, Max.y), math.max(Min.z, Max.z)) + + local ignoreLookup = {} + if istable(Ignore) then + for _, v in ipairs(Ignore) do + if IsValid(v) then ignoreLookup[v] = true end + end + end + + local findlist = ents.FindInBox(rMin, rMax) + local result = {} + + for _, ent in ipairs(findlist) do + if IsValid(ent) and not ignoreLookup[ent] and not forbidden_classes[ent:GetClass()] then + if safeClassMatch(ent:GetClass(), Filter) then + result[#result + 1] = ent + end + end + end + + return result, #result + end, + label = function(Out, Clk, Min, Max, Filter) + return string.format("findInBox(%s -> %s, filter=%q) = %d", + tostring(Min), tostring(Max), tostring(Filter), Out.Count) + end +} + +GateActions() \ No newline at end of file diff --git a/lua/wire/gates/rangerdata.lua b/lua/wire/gates/rangerdata.lua index 65be5ad295..80722a6fd9 100644 --- a/lua/wire/gates/rangerdata.lua +++ b/lua/wire/gates/rangerdata.lua @@ -101,7 +101,7 @@ GateActions["rd_hit"] = { return A.Hit and 1 or 0 end, label = function(Out, A) - return string.format ("hit(%s) = %d", A, Out and 1 or 0) + return string.format ("hit(%s) = %d", A, Out) end } @@ -122,4 +122,94 @@ GateActions["rd_distance"] = { end } +GateActions["rd_hull_pos"] = { + name = "Hull Trace (By Position)", + description = "Performs a box-shaped (hull) trace between two positions and outputs ranger data. If Parent is provided, StartPos and EndPos are treated as local offsets relative to that entity.", + inputs = { "StartPos", "EndPos", "Size", "Filter", "Parent" }, + inputtypes = { "VECTOR", "VECTOR", "VECTOR", "ARRAY", "ENTITY" }, + outputtypes = { "RANGER" }, + timed = true, + output = function(gate, StartPos, EndPos, Size, Filter, Entity) + if not isvector(StartPos) then StartPos = vec0 end + if not isvector(EndPos) then EndPos = vec0 end + if not isvector(Size) then Size = vec1 end + + if IsValid(Entity) then + StartPos = Entity:LocalToWorld(StartPos) + EndPos = Entity:LocalToWorld(EndPos) + end + + local half = Size * 0.5 + local filter = {} + if istable(Filter) then + for _, v in ipairs(Filter) do + if IsValid(v) then filter[#filter + 1] = v end + end + end + + if IsValid(Entity) then + filter[#filter + 1] = Entity + end + + local tracedata = { + start = StartPos, + endpos = EndPos, + mins = -half, + maxs = half, + filter = (#filter > 0) and filter or nil, + } + return util.TraceHull(tracedata) + end, + label = function(Out, StartPos, EndPos, Size, Filter, Entity) + return string.format("hullTrace(%s → %s)", tostring(StartPos), tostring(EndPos)) + end +} + +GateActions["rd_hull_ang"] = { + name = "Hull Trace (By Angle)", + description = "Performs a box-shaped (hull) trace from a start position along an angle for a set distance, and outputs ranger data. If Parent is provided, StartPos and Angle are treated as local offset and local angle relative to that entity.", + inputs = { "StartPos", "Angle", "Distance", "Size", "Filter", "Parent" }, + inputtypes = { "VECTOR", "ANGLE", "NORMAL", "VECTOR", "ARRAY", "ENTITY" }, + outputtypes = { "RANGER" }, + timed = true, + output = function(gate, StartPos, Angle, Distance, Size, Filter, Entity) + if not isvector(StartPos) then StartPos = vec0 end + if not isangle(Angle) then Angle = ang0 end + if not isvector(Size) then Size = vec1 end + if not Distance or Distance == 0 then Distance = 4096 end + + if IsValid(Entity) then + StartPos = Entity:LocalToWorld(StartPos) + Angle = Entity:LocalToWorldAngles(Angle) + end + + local dir = Angle:Forward() + local endpos = StartPos + dir * Distance + local half = Size * 0.5 + + local filter = {} + if istable(Filter) then + for _, v in ipairs(Filter) do + if IsValid(v) then filter[#filter + 1] = v end + end + end + + if IsValid(Entity) then + filter[#filter + 1] = Entity + end + + local tracedata = { + start = StartPos, + endpos = endpos, + mins = -half, + maxs = half, + filter = (#filter > 0) and filter or nil, + } + return util.TraceHull(tracedata) + end, + label = function(Out, StartPos, Angle, Distance, Size, Filter, Entity) + return string.format("hullTrace(%s, %s, dist=%s)", tostring(StartPos), tostring(Angle), tostring(Distance)) + end +} + GateActions() diff --git a/lua/wire/gates/serverinfo.lua b/lua/wire/gates/serverinfo.lua new file mode 100644 index 0000000000..ff44f52161 --- /dev/null +++ b/lua/wire/gates/serverinfo.lua @@ -0,0 +1,117 @@ +--[[ + Server Info +]] + +local sv_map = game.GetMap() +local sv_maxplayers = game.MaxPlayers() + +GateActions("Server") + +GateActions["sv_hostname"] = { + name = "Hostname", + description = "Returns the server's hostname.", + inputs = {}, + outputtypes = { "STRING" }, + output = function(gate) + return GetHostName() + end, + label = function(Out) + return string.format("hostname = %q", Out) + end +} + +GateActions["sv_hostip"] = { + name = "Host IP", + description = "Returns the server's IP address.", + inputs = {}, + outputtypes = { "STRING" }, + output = function(gate) + return game.GetIPAddress() + end, + label = function(Out) + return string.format("hostip = %q", Out) + end +} + +GateActions["sv_map"] = { + name = "Map", + description = "Returns the current map name.", + inputs = {}, + outputtypes = { "STRING" }, + output = function(gate) + return sv_map + end, + label = function(Out) + return string.format("map = %q", Out) + end +} + +GateActions["sv_maxplayers"] = { + name = "Max Players", + description = "Returns the maximum number of players allowed on the server.", + inputs = {}, + outputtypes = { "NORMAL" }, + output = function(gate) + return sv_maxplayers + end, + label = function(Out) + return string.format("maxPlayers = %d", Out) + end +} + +GateActions["sv_numplayers"] = { + name = "Num Players", + description = "Returns the current number of players on the server.", + inputs = {}, + outputtypes = { "NORMAL" }, + timed = true, + output = function(gate) + return player.GetCount() + end, + label = function(Out) + return string.format("numPlayers = %d", Out) + end +} + +GateActions["sv_airdensity"] = { + name = "Air Density", + description = "Returns the air density of the physics environment.", + inputs = {}, + outputtypes = { "NORMAL" }, + output = function(gate) + return physenv.GetAirDensity() + end, + label = function(Out) + return string.format("airDensity = %f", Out) + end +} + + +GateActions["sv_propgravity"] = { + name = "Prop Gravity", + description = "Returns the gravity vector of the physics environment.", + inputs = {}, + outputtypes = { "VECTOR" }, + output = function(gate) + return physenv.GetGravity() + end, + label = function(Out) + return string.format("propGravity = (%d,%d,%d)", Out.x, Out.y, Out.z) + end +} + +GateActions["sv_tickinterval"] = { + name = "Tick Interval", + description = "Returns the server's tick interval in seconds.", + inputs = {}, + outputtypes = { "NORMAL" }, + timed = true, + output = function(gate) + return engine.TickInterval() + end, + label = function(Out) + return string.format("tickInterval = %f", Out) + end +} + +GateActions() \ No newline at end of file diff --git a/lua/wire/gates/time.lua b/lua/wire/gates/time.lua index 7267631e41..ad0d12dc34 100644 --- a/lua/wire/gates/time.lua +++ b/lua/wire/gates/time.lua @@ -341,5 +341,4 @@ GateActions["servertime"] = { end } - GateActions() diff --git a/lua/wire/gates/unitconv.lua b/lua/wire/gates/unitconv.lua new file mode 100644 index 0000000000..7724b88c58 --- /dev/null +++ b/lua/wire/gates/unitconv.lua @@ -0,0 +1,60 @@ +--[[ + Unit Conversion +]] + +GateActions("Unit Conversion") + +GateActions["unit_tounit"] = { + name = "To Unit", + description = "Converts a value from Source units (inches) to the specified unit. Supports speed (m/s, km/h, mph...), length (m, cm, ft...) and weight (kg, lb, oz...).", + inputs = { "Value", "Unit" }, + inputtypes = { "NORMAL", "STRING" }, + outputtypes = { "NORMAL" }, + output = function(gate, Value, Unit) + for _, tbl in pairs(WireLib.UnitConv) do + if tbl[Unit] then + return Value * tbl[Unit] + end + end + return 0 + end, + label = function(Out, Value, Unit) + return string.format("toUnit(%s, %q) = %f", tostring(Value), tostring(Unit), Out) + end +} + +GateActions["unit_fromunit"] = { + name = "From Unit", + description = "Converts a value from the specified unit back to Source units (inches). Supports speed, length and weight.", + inputs = { "Value", "Unit" }, + inputtypes = { "NORMAL", "STRING" }, + outputtypes = { "NORMAL" }, + output = function(gate, Value, Unit) + for _, tbl in pairs(WireLib.UnitConv) do + if tbl[Unit] then + return Value / tbl[Unit] + end + end + return 0 + end, + label = function(Out, Value, Unit) + return string.format("fromUnit(%s, %q) = %f", tostring(Value), tostring(Unit), Out) + end +} + +GateActions["unit_convertunit"] = { + name = "Convert Unit", + description = "Returns the conversion factor between two units. Both units must be of the same type (speed, length or weight).", + inputs = { "From", "To" }, + inputtypes = { "STRING", "STRING" }, + outputtypes = { "NORMAL" }, + output = function(gate, From, To) + local factor = WireLib.ConvertUnit(From, To) + return factor or 0 + end, + label = function(Out, From, To) + return string.format("convertUnit(%q → %q) = %f", tostring(From), tostring(To), Out) + end +} + +GateActions() \ No newline at end of file diff --git a/lua/wire/server/wirelib.lua b/lua/wire/server/wirelib.lua index 4362676a8f..0e7575fd90 100644 --- a/lua/wire/server/wirelib.lua +++ b/lua/wire/server/wirelib.lua @@ -204,6 +204,107 @@ WireLib.DT = { }, } +--- Conversion factors for unit conversion gates and E2 functions. +--- Each value represents the factor to convert from natural units to this unit. +--- Natural units: inches for length/speed, kilograms for weight. +--- +--- Unit abbreviations: +--- u - Source unit (1 Source Unit = 0.75 inches) +--- See: http://developer.valvesoftware.com/wiki/Dimensions#Map_Grid_Units:_quick_reference +--- mm - millimeter +--- cm - centimeter +--- dm - decimeter +--- m - meter +--- km - kilometer +--- in - inch +--- ft - foot +--- yd - yard +--- mi - mile +--- nmi - nautical mile +--- g - gram +--- kg - kilogram +--- t - tonne +--- oz - ounce +--- lb - pound +---@type table> +WireLib.UnitConv = { + --- Speed units (natural unit: in/s) + speed = { + ["u/s"] = 1 / 0.75, + ["u/m"] = 60 * (1 / 0.75), + ["u/h"] = 3600 * (1 / 0.75), + ["mm/s"] = 25.4, + ["cm/s"] = 2.54, + ["dm/s"] = 0.254, + ["m/s"] = 0.0254, + ["km/s"] = 0.0000254, + ["in/s"] = 1, + ["ft/s"] = 1 / 12, + ["yd/s"] = 1 / 36, + ["mi/s"] = 1 / 63360, + ["nmi/s"] = 127 / 9260000, + ["mm/m"] = 60 * 25.4, + ["cm/m"] = 60 * 2.54, + ["dm/m"] = 60 * 0.254, + ["m/m"] = 60 * 0.0254, + ["km/m"] = 60 * 0.0000254, + ["in/m"] = 60, + ["ft/m"] = 60 / 12, + ["yd/m"] = 60 / 36, + ["mi/m"] = 60 / 63360, + ["nmi/m"] = 60 * 127 / 9260000, + ["mm/h"] = 3600 * 25.4, + ["cm/h"] = 3600 * 2.54, + ["dm/h"] = 3600 * 0.254, + ["m/h"] = 3600 * 0.0254, + ["km/h"] = 3600 * 0.0000254, + ["in/h"] = 3600, + ["ft/h"] = 3600 / 12, + ["yd/h"] = 3600 / 36, + ["mi/h"] = 3600 / 63360, + ["nmi/h"] = 3600 * 127 / 9260000, + ["mph"] = 3600 / 63360, + ["knots"] = 3600 * 127 / 9260000, + ["mach"] = 0.0254 / 295, + }, + --- Length units (natural unit: inches) + length = { + ["u"] = 1 / 0.75, + ["mm"] = 25.4, + ["cm"] = 2.54, + ["dm"] = 0.254, + ["m"] = 0.0254, + ["km"] = 0.0000254, + ["in"] = 1, + ["ft"] = 1 / 12, + ["yd"] = 1 / 36, + ["mi"] = 1 / 63360, + ["nmi"] = 127 / 9260000, + }, + --- Weight units (natural unit: kilograms) + weight = { + ["g"] = 1000, + ["kg"] = 1, + ["t"] = 0.001, + ["oz"] = 1 / 0.028349523125, + ["lb"] = 1 / 0.45359237, + }, +} + +--- Returns the conversion factor between two units. +--- Returns nil if units are unknown or of different types. +---@param from string +---@param to string +---@return number|nil +function WireLib.ConvertUnit(from, to) + for _, tbl in pairs(WireLib.UnitConv) do + if tbl[from] and tbl[to] then + return tbl[to] / tbl[from] + end + end + return nil +end + --- Gets default value of a WireLib type. --- Assumes `type` is a valid string type in the WireLib.DT table. --- For example `VECTOR` / `NORMAL` / `ARRAY`