Files
wnsrc/lua/pac3/core/client/parts/event.lua
lifestorm 94063e4369 Upload
2024-08-04 22:55:00 +03:00

2221 lines
53 KiB
Lua

--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local FrameTime = FrameTime
local CurTime = CurTime
local NULL = NULL
local Vector = Vector
local util = util
local SysTime = SysTime
local BUILDER, PART = pac.PartTemplate("base")
PART.ClassName = "event"
PART.ThinkTime = 0
PART.AlwaysThink = true
PART.Icon = 'icon16/clock.png'
BUILDER:StartStorableVars()
BUILDER:GetSet("Event", "", {enums = function(part)
local output = {}
for i, event in pairs(part.Events) do
if not event.IsAvailable or event:IsAvailable(part) then
output[i] = event
end
end
return output
end})
BUILDER:GetSet("Operator", "find simple", {enums = function(part) local tbl = {} for i,v in ipairs(part.Operators) do tbl[v] = v end return tbl end})
BUILDER:GetSet("Arguments", "", {hidden = true})
BUILDER:GetSet("Invert", true)
BUILDER:GetSet("RootOwner", true)
BUILDER:GetSet("AffectChildrenOnly", false)
BUILDER:GetSet("ZeroEyePitch", false)
BUILDER:GetSetPart("TargetPart")
BUILDER:EndStorableVars()
function PART:SetEvent(event)
local reset = (self.Arguments == "") or
(self.Arguments ~= "" and self.Event ~= "" and self.Event ~= event)
self.Event = event
self:SetWarning()
self:GetDynamicProperties(reset)
end
local function get_default(typ)
if typ == "string" then
return ""
elseif typ == "number" then
return 0
elseif typ == "boolean" then
return false
end
end
local function string_to_type(typ, val)
if typ == "number" then
return tonumber(val) or 0
elseif typ == "boolean" then
return tobool(val) or false
end
return val
end
local function cast(typ, val)
if val == nil then
val = get_default(typ)
end
return string_to_type(typ, val)
end
function PART:GetDynamicProperties(reset_to_default)
local data = self.Events[self.Event]
if not data then return end
local tbl = {}
for pos, arg in ipairs(data:GetArguments()) do
local key, typ, udata = unpack(arg)
udata = udata or {}
udata.group = udata.group or "arguments"
tbl[key] = {
key = key,
get = function()
local args = {self:GetParsedArguments(data)}
return cast(typ, args[pos])
end,
set = function(val)
local args = {self:GetParsedArguments(data)}
args[pos] = val
self:ParseArguments(unpack(args))
end,
udata = udata,
}
local arg = tbl[key]
if arg.get() == nil or reset_to_default then
if udata.default then
arg.set(udata.default)
else
arg.set(nil)
end
end
end
return tbl
end
local function convert_angles(self, ang)
if self.ZeroEyePitch then
ang.p = 0
end
return ang
end
local function calc_velocity(part)
if not part.GetWorldPosition then
return vector_origin
end
local diff = part:GetWorldPosition() - (part.last_pos or Vector(0, 0, 0))
part.last_pos = part:GetWorldPosition()
part.last_vel_smooth = part.last_vel_smooth or Vector(0, 0, 0)
part.last_vel_smooth = part.last_vel_smooth + (part:GetWorldPosition() - part.last_vel_smooth) * FrameTime() * 4
return part.last_vel_smooth
end
local function try_viewmodel(ent)
return ent == pac.LocalViewModel and pac.LocalPlayer or ent
end
local function get_owner(self)
if self.RootOwner then
return try_viewmodel(self:GetRootPart():GetOwner())
else
return try_viewmodel(self:GetOwner())
end
end
local movetypes = {}
for k,v in pairs(_G) do
if isstring(k) and isnumber(v) and k:sub(0,9) == "MOVETYPE_" then
movetypes[v] = k:sub(10):lower()
end
end
PART.Events = {}
PART.OldEvents = {
random = {
arguments = {{compare = "number"}},
callback = function(self, ent, compare)
return self:NumberOperator(math.random(), compare)
end,
},
randint = {
arguments = {{compare = "number"}, {min = "number"}, {max = "number"}},
callback = function(self, ent, compare, min, max)
min = min or 0
max = max or 1
if min > max then return 0 end
return self:NumberOperator(math.random(min,max), compare)
end,
},
random_timer = {
arguments = {{min = "number"}, {max = "number"}, {holdtime = "number"}},
callback = function(self, ent, min, max, holdtime)
holdtime = holdtime or 0.1
min = min or 0
max = max or 1
if min > max then return false end
if self.RndTime == nil then
self.RndTime = 0
end
if not self.SetRandom then
self.RndTime = CurTime() + math.random(min,max)
self.SetRandom = true
elseif self.SetRandom then
if CurTime() > self.RndTime then
if CurTime() < self.RndTime + holdtime then
return true
end
self.SetRandom = false
return false
end
end
return false
end,
},
timerx = {
arguments = {{seconds = "number"}, {reset_on_hide = "boolean"}, {synced_time = "boolean"}},
nice = function(self, ent, seconds)
return "timerx: " .. ("%.2f"):format(self.number or 0, 2) .. " " .. self:GetOperator() .. " " .. seconds .. " seconds?"
end,
callback = function(self, ent, seconds, reset_on_hide, synced_time)
local time = synced_time and CurTime() or RealTime()
self.time = self.time or time
self.timerx_reset = reset_on_hide
if self.AffectChildrenOnly and self:IsHiddenBySomethingElse() then
return false
end
self.number = time - self.time
return self:NumberOperator(self.number, seconds)
end,
},
timersys = {
arguments = {{seconds = "number"}, {reset_on_hide = "boolean"}},
callback = function(self, ent, seconds, reset_on_hide)
local time = SysTime()
self.time = self.time or time
self.timerx_reset = reset_on_hide
if self.AffectChildrenOnly and self:IsHiddenBySomethingElse() then
return false
end
return self:NumberOperator(time - self.time, seconds)
end,
},
map_name = {
arguments = {{find = "string"}},
callback = function(self, ent, find)
return self:StringOperator(game.GetMap(), find)
end,
},
fov = {
arguments = {{fov = "number"}},
callback = function(self, ent, fov)
ent = try_viewmodel(ent)
if ent.GetFOV then
return self:NumberOperator(ent:GetFOV(), fov)
end
return 0
end,
},
health_lost = {
arguments = {{amount = "number"}},
callback = function(self, ent, amount)
ent = try_viewmodel(ent)
if ent.Health then
local dmg = self.pac_lastdamage or 0
if self.dmgCD == nil then
self.dmgCD = 0
end
if not self.pac_wasdmg then
local dmgDone = dmg - ent:Health()
self.pac_lastdamage = ent:Health()
if dmgDone == 0 then return false end
if self:NumberOperator(dmgDone,amount) then
self.pac_wasdmg = true
self.dmgCD = pac.RealTime + 0.2
end
else
if self.pac_wasdmg and pac.RealTime > self.dmgCD then
self.pac_wasdmg = false
end
end
return self.pac_wasdmg
end
return false
end,
},
holdtype = {
arguments = {{find = "string"}},
callback = function(self, ent, find)
ent = try_viewmodel(ent)
local wep = ent.GetActiveWeapon and ent:GetActiveWeapon() or NULL
if wep:IsValid() and self:StringOperator(wep:GetHoldType(), find) then
return true
end
end,
},
is_crouching = {
callback = function(self, ent)
ent = try_viewmodel(ent)
return ent.Crouching and ent:Crouching()
end,
},
is_typing = {
callback = function(self, ent)
ent = self:GetPlayerOwner()
return ent.IsTyping and ent:IsTyping()
end,
},
using_physgun = {
callback = function(self, ent)
ent = self:GetPlayerOwner()
local pac_drawphysgun_event_part = ent.pac_drawphysgun_event_part
if not pac_drawphysgun_event_part then
pac_drawphysgun_event_part = {}
ent.pac_drawphysgun_event_part = pac_drawphysgun_event_part
end
pac_drawphysgun_event_part[self] = true
return ent.pac_drawphysgun_event ~= nil
end,
},
eyetrace_entity_class = {
arguments = {{class = "string"}},
callback = function(self, ent, find)
if ent.GetEyeTrace then
ent = ent:GetEyeTrace().Entity
if self:StringOperator(ent:GetClass(), find) then
return true
end
end
end,
},
owner_health = {
arguments = {{health = "number"}},
callback = function(self, ent, num)
ent = try_viewmodel(ent)
if ent.Health then
return self:NumberOperator(ent:Health(), num)
end
return 0
end,
},
owner_max_health = {
arguments = {{health = "number"}},
callback = function(self, ent, num)
ent = try_viewmodel(ent)
if ent.GetMaxHealth then
return self:NumberOperator(ent:GetMaxHealth(), num)
end
return 0
end,
},
owner_alive = {
callback = function(self, ent)
ent = try_viewmodel(ent)
if ent.Alive then
return ent:Alive()
end
return 0
end,
},
owner_armor = {
arguments = {{armor = "number"}},
callback = function(self, ent, num)
ent = try_viewmodel(ent)
if ent.Armor then
return self:NumberOperator(ent:Armor(), num)
end
return 0
end,
},
owner_scale_x = {
arguments = {{scale = "number"}},
callback = function(self, ent, num)
ent = try_viewmodel(ent)
return self:NumberOperator(ent.pac_model_scale and ent.pac_model_scale.x or (ent.GetModelScale and ent:GetModelScale()) or 1, num)
end,
},
owner_scale_y = {
arguments = {{scale = "number"}},
callback = function(self, ent, num)
ent = try_viewmodel(ent)
return self:NumberOperator(ent.pac_model_scale and ent.pac_model_scale.y or (ent.GetModelScale and ent:GetModelScale()) or 1, num)
end,
},
owner_scale_z = {
arguments = {{scale = "number"}},
callback = function(self, ent, num)
ent = try_viewmodel(ent)
return self:NumberOperator(ent.pac_model_scale and ent.pac_model_scale.z or (ent.GetModelScale and ent:GetModelScale()) or 1, num)
end,
},
pose_parameter = {
arguments = {{name = "string"}, {num = "number"}},
callback = function(self, ent, name, num)
ent = try_viewmodel(ent)
return self:NumberOperator(ent:GetPoseParameter(name), num)
end,
},
speed = {
arguments = {{speed = "number"}},
callback = function(self, ent, num)
ent = try_viewmodel(ent)
return self:NumberOperator(ent:GetVelocity():Length(), num)
end,
},
is_under_water = {
arguments = {{level = "number"}},
callback = function(self, ent, num)
ent = try_viewmodel(ent)
return self:NumberOperator(ent:WaterLevel(), num)
end,
},
is_on_fire = {
callback = function(self, ent)
ent = try_viewmodel(ent)
return ent:IsOnFire()
end,
},
client_spawned = {
arguments = {{time = "number"}},
callback = function(self, ent, time)
time = time or 0.1
ent = try_viewmodel(ent)
if ent.pac_playerspawn and ent.pac_playerspawn + time > pac.RealTime then
return true
end
end,
},
is_client = {
callback = function(self, ent)
ent = try_viewmodel(ent)
return self:GetPlayerOwner() == ent
end,
},
is_flashlight_on = {
callback = function(self, ent)
ent = try_viewmodel(ent)
return ent.FlashlightIsOn and ent:FlashlightIsOn()
end,
},
collide = {
callback = function(self, ent)
ent.pac_event_collide_callback = ent.pac_event_collide_callback or ent:AddCallback("PhysicsCollide", function(ent, data)
ent.pac_event_collision_data = data
end)
if ent.pac_event_collision_data then
local data = ent.pac_event_collision_data
ent.pac_event_collision_data = nil
return true
end
return false
end,
},
ranger = {
arguments = {{distance = "number"}, {compare = "number"}, {npcs_and_players_only = "boolean"}},
userdata = {{editor_panel = "ranger", ranger_property = "distance"}, {editor_panel = "ranger", ranger_property = "compare"}},
callback = function(self, ent, distance, compare, npcs_and_players_only)
local parent = self:GetParentEx()
if parent:IsValid() and parent.GetWorldPosition then
self:SetWarning()
distance = distance or 1
compare = compare or 0
local res = util.TraceLine({
start = parent:GetWorldPosition(),
endpos = parent:GetWorldPosition() + parent:GetWorldAngles():Forward() * distance,
filter = ent,
})
if npcs_and_players_only and (not res.Entity:IsPlayer() and not res.Entity:IsNPC()) then
return false
end
return self:NumberOperator(res.Fraction * distance, compare)
else
local classname = parent:GetNiceName()
local name = parent:GetName()
self:SetWarning(("ranger doesn't work on [%s] %s"):format(classname, classname ~= name and "(" .. name .. ")" or ""))
end
end,
},
is_on_ground = {
arguments = {{exclude_noclip = "boolean"}},
callback = function(self, ent, exclude_noclip)
ent = try_viewmodel(ent)
if exclude_noclip and ent:GetMoveType() == MOVETYPE_NOCLIP then return false end
--return ent.IsOnGround and ent:IsOnGround()
local rad = ent:BoundingRadius() / 2
local times = 2
for x = -times, times do
for y = -times, times do
local xy = Vector(x/times,y/times,0) * rad
local res = util.TraceLine({
start = ent:GetPos() + xy + Vector(0,0,2.5),
endpos = ent:GetPos() + xy/1.25 + Vector(0,0,-10),
--mins = ent:OBBMins(),
--maxs = ent:OBBMaxs(),
filter = ent,
--mask = MASK_SOLID_BRUSHONLY,
})
if res.Hit and math.abs(res.HitNormal.z) > 0.70 then return true end
end
end
return false
end,
},
is_touching = {
arguments = {{extra_radius = "number"}},
userdata = {{editor_panel = "is_touching", is_touching_property = "extra_radius"}},
callback = function(self, ent, extra_radius)
extra_radius = extra_radius or 0
local radius = ent:BoundingRadius()
if radius == 0 and IsValid(ent.pac_projectile) then
radius = ent.pac_projectile:GetRadius()
end
radius = math.max(radius + extra_radius + 1, 1)
local mins = Vector(-1,-1,-1)
local maxs = Vector(1,1,1)
local startpos = ent:WorldSpaceCenter()
mins = mins * radius
maxs = maxs * radius
local tr = util.TraceHull( {
start = startpos,
endpos = startpos,
maxs = maxs,
mins = mins,
filter = ent
} )
return tr.Hit
end,
},
is_in_noclip = {
callback = function(self, ent)
ent = try_viewmodel(ent)
return ent:GetMoveType() == MOVETYPE_NOCLIP and (not ent.GetVehicle or not ent:GetVehicle():IsValid())
end,
},
is_voice_chatting = {
callback = function(self, ent)
ent = try_viewmodel(ent)
return ent.IsSpeaking and ent:IsSpeaking()
end,
},
ammo = {
arguments = {{primary = "boolean"}, {amount = "number"}},
userdata = {{editor_onchange = function(part, num) return math.Round(num) end}},
callback = function(self, ent, primary, amount)
ent = try_viewmodel(ent)
ent = ent.GetActiveWeapon and ent:GetActiveWeapon() or ent
if ent:IsValid() then
return self:NumberOperator(ent.Clip1 and (primary and ent:Clip1() or ent:Clip2()) or 0, amount)
end
end,
},
total_ammo = {
arguments = {{ammo_id = "string"}, {amount = "number"}},
callback = function(self, ent, ammo_id, amount)
if ent.GetAmmoCount then
ammo_id = tonumber(ammo_id) or ammo_id:lower()
if ammo_id == "primary" then
local wep = ent.GetActiveWeapon and ent:GetActiveWeapon() or NULL
return self:NumberOperator(wep:IsValid() and ent:GetAmmoCount(wep:GetPrimaryAmmoType()) or 0, amount)
elseif ammo_id == "secondary" then
local wep = ent.GetActiveWeapon and ent:GetActiveWeapon() or NULL
return self:NumberOperator(wep:IsValid() and ent:GetAmmoCount(wep:GetSecondaryAmmoType()) or 0, amount)
else
return self:NumberOperator(ent:GetAmmoCount(ammo_id), amount)
end
end
end,
},
clipsize = {
arguments = {{primary = "boolean"}, {amount = "number"}},
callback = function(self, ent, primary, amount)
ent = try_viewmodel(ent)
ent = ent.GetActiveWeapon and ent:GetActiveWeapon() or ent
if ent:IsValid() then
return self:NumberOperator(ent.GetMaxClip1 and (primary and ent:GetMaxClip1() or ent:GetMaxClip2()) or 0, amount)
end
end,
},
vehicle_class = {
arguments = {{find = "string"}},
callback = function(self, ent, find)
ent = try_viewmodel(ent)
ent = ent.GetVehicle and ent:GetVehicle() or NULL
if ent:IsValid() then
return self:StringOperator(ent:GetClass(), find)
end
end,
},
vehicle_model = {
arguments = {{find = "string"}},
callback = function(self, ent, find)
ent = try_viewmodel(ent)
ent = ent.GetVehicle and ent:GetVehicle() or NULL
if ent:IsValid() and ent:GetModel() then
return self:StringOperator(ent:GetModel():lower(), find)
end
end,
},
driver_name = {
arguments = {{find = "string"}},
callback = function(self, ent, find)
ent = ent.GetDriver and ent:GetDriver() or NULL
if ent:IsValid() then
return self:StringOperator(ent:GetName(), find)
end
end,
},
entity_class = {
arguments = {{find = "string"}},
callback = function(self, ent, find)
return self:StringOperator(ent:GetClass(), find)
end,
},
weapon_class = {
arguments = {{find = "string"}, {hide = "boolean"}},
callback = function(self, ent, find, hide)
ent = try_viewmodel(ent)
local wep = ent.GetActiveWeapon and ent:GetActiveWeapon() or NULL
if wep:IsValid() then
local class_name = wep:GetClass()
local found = self:StringOperator(class_name, find)
if class_name == "hands" and not found then
found = self:StringOperator("none", find)
end
if found then
wep:SetNoDraw(hide)
wep.pac_weapon_class = true
return true
end
end
end,
},
has_weapon = {
arguments = {{find = "string"}},
callback = function(self, ent, find)
ent = try_viewmodel(ent)
local tbl = ent.GetWeapons and ent:GetWeapons()
if tbl then
for _, val in pairs(tbl) do
val = val:GetClass()
if self:StringOperator(val, find) then
return true
end
end
end
end,
},
model_name = {
arguments = {{find = "string"}},
callback = function(self, ent, find)
return self:StringOperator(ent:GetModel(), find)
end,
},
sequence_name = {
arguments = {{find = "string"}},
nice = function(self, ent)
return self.sequence_name or "invalid sequence"
end,
callback = function(self, ent, find)
ent = get_owner(self)
self.sequence_name = ent:GetSequenceName(ent:GetSequence())
return self:StringOperator(self.sequence_name, find)
end,
},
timer = {
arguments = {{interval = "number"}, {offset = "number"}},
callback = function(self, ent, interval, offset)
interval = interval or 1
offset = offset or 0
if interval == 0 or interval < FrameTime() then
self.timer_hack = not self.timer_hack
return self.timer_hack
end
return (CurTime() + offset) % interval > (interval / 2)
end,
},
animation_event = {
arguments = {{find = "string"}, {time = "number"}},
nice = function(self)
return self.anim_name or ""
end,
callback = function(self, ent, find, time)
time = time or 0.1
ent = get_owner(self)
local data = ent.pac_anim_event
local b = false
if data and (self:StringOperator(data.name, find) and (time == 0 or data.time + time > pac.RealTime)) then
data.reset = false
b = true
end
if b then
self.anim_name = data.name
else
self.anim_name = nil
end
return b
end,
},
fire_bullets = {
arguments = {{find_ammo = "string"}, {time = "number"}},
callback = function(self, ent, find, time)
time = time or 0.1
ent = try_viewmodel(ent)
local data = ent.pac_fire_bullets
local b = false
if data and (self:StringOperator(data.name, find) and (time == 0 or data.time + time > pac.RealTime)) then
data.reset = false
b = true
end
return b
end,
},
emit_sound = {
arguments = {{find_sound = "string"}, {time = "number"}, {mute = "boolean"}},
callback = function(self, ent, find, time, mute)
time = time or 0.1
ent = try_viewmodel(ent)
local data = ent.pac_emit_sound
local b = false
if data and (self:StringOperator(data.name, find) and (time == 0 or data.time + time > pac.RealTime)) then
data.reset = false
b = true
if mute then
data.mute_me = true
end
end
return b
end,
},
command = {
arguments = {{find = "string"}, {time = "number"}, {hide_in_eventwheel = "boolean"}},
userdata = {
{default = "change_me", editor_friendly = "CommandName"},
{default = 0.1, editor_friendly = "EventDuration"},
{default = false, group = "event wheel", editor_friendly = "HideInEventWheel"}
},
nice = function(self, ent, find, time)
find = find or "?"
time = time or "?"
return "command: " .. find .. " | " .. "duration: " .. time
end,
callback = function(self, ent, find, time)
time = time or 0.1
local ply = self:GetPlayerOwner()
local events = ply.pac_command_events
if events then
local found = nil
for _, data in pairs(events) do
if self:StringOperator(data.name, find) then
if data.on > 0 then
found = data.on == 1
elseif data.time + time > pac.RealTime then
found = true
end
end
end
return found
end
end,
},
say = {
arguments = {{find = "string"}, {time = "number"}, {all_players = "boolean"}},
callback = function(self, ent, find, time, all_players)
time = time or 0.1
ent = try_viewmodel(ent)
if all_players then
for _, ply in ipairs(player.GetAll()) do
local data = ply.pac_say_event
if data and self:StringOperator(data.str, find) and data.time + time > pac.RealTime then
return true
end
end
else
local owner = self:GetRootPart():GetOwner()
if owner:IsValid() then
local data = owner.pac_say_event
if data and self:StringOperator(data.str, find) and data.time + time > pac.RealTime then
return true
end
end
end
end,
},
-- outfit owner
owner_velocity_length = {
arguments = {{speed = "number"}},
callback = function(self, ent, speed)
local parent = self:GetParentEx()
ent = try_viewmodel(ent)
if parent:IsValid() and ent:IsValid() then
return self:NumberOperator(get_owner(parent):GetVelocity():Length(), speed)
end
return 0
end,
},
owner_velocity_forward = {
arguments = {{speed = "number"}},
callback = function(self, ent, speed)
ent = try_viewmodel(ent)
if ent:IsValid() then
return self:NumberOperator(convert_angles(self, ent:EyeAngles()):Forward():Dot(ent:GetVelocity()), speed)
end
return 0
end,
},
owner_velocity_right = {
arguments = {{speed = "number"}},
callback = function(self, ent, speed)
ent = try_viewmodel(ent)
if ent:IsValid() then
return self:NumberOperator(convert_angles(self, ent:EyeAngles()):Right():Dot(ent:GetVelocity()), speed)
end
return 0
end,
},
owner_velocity_up = {
arguments = {{speed = "number"}},
callback = function(self, ent, speed)
ent = try_viewmodel(ent)
if ent:IsValid() then
return self:NumberOperator(convert_angles(self, ent:EyeAngles()):Up():Dot(ent:GetVelocity()), speed)
end
return 0
end,
},
owner_velocity_world_forward = {
arguments = {{speed = "number"}},
callback = function(self, ent, speed)
ent = try_viewmodel(ent)
if owner:IsValid() then
return self:NumberOperator(ent:GetVelocity()[1], speed)
end
return 0
end,
},
owner_velocity_world_right = {
arguments = {{speed = "number"}},
callback = function(self, ent, speed)
ent = try_viewmodel(ent)
if ent:IsValid() then
return self:NumberOperator(ent:GetVelocity()[2], speed)
end
return 0
end,
},
owner_velocity_world_up = {
arguments = {{speed = "number"}},
callback = function(self, ent, speed)
ent = try_viewmodel(ent)
if ent:IsValid() then
return self:NumberOperator(ent:GetVelocity()[3], speed)
end
return 0
end,
},
-- parent part
parent_velocity_length = {
arguments = {{speed = "number"}},
callback = function(self, ent, speed)
local parent = self:GetParentEx()
if not self.TargetPart:IsValid() and parent:HasParent() then
parent = parent:GetParent()
end
if parent:IsValid() then
return self:NumberOperator(calc_velocity(parent):Length(), speed)
end
return 0
end,
},
parent_velocity_forward = {
arguments = {{speed = "number"}},
callback = function(self, ent, speed)
local parent = self:GetParentEx()
if not self.TargetPart:IsValid() and parent:HasParent() then
parent = parent:GetParent()
end
if parent:IsValid() and parent.GetWorldAngles then
return self:NumberOperator(parent:GetWorldAngles():Forward():Dot(calc_velocity(parent)), speed)
end
return 0
end,
},
parent_velocity_right = {
arguments = {{speed = "number"}},
callback = function(self, ent, speed)
local parent = self:GetParentEx()
if not self.TargetPart:IsValid() and parent:HasParent() then
parent = parent:GetParent()
end
if parent:IsValid() and parent.GetWorldAngles then
return self:NumberOperator(parent:GetWorldAngles():Right():Dot(calc_velocity(parent)), speed)
end
return 0
end,
},
parent_velocity_up = {
arguments = {{speed = "number"}},
callback = function(self, ent, speed)
local parent = self:GetParentEx()
if not self.TargetPart:IsValid() and parent:HasParent() then
parent = parent:GetParent()
end
if parent:IsValid() and parent.GetWorldAngles then
return self:NumberOperator(parent:GetWorldAngles():Up():Dot(calc_velocity(parent)), speed)
end
return 0
end,
},
parent_scale_x = {
arguments = {{scale = "number"}},
callback = function(self, ent, num)
local parent = self:GetParentEx()
if not self.TargetPart:IsValid() and parent:HasParent() then
parent = parent:GetParent()
end
if parent:IsValid() then
return self:NumberOperator((parent.Type == "part" and parent.Scale and parent.Scale.x * parent.Size) or (parent.pac_model_scale and parent.pac_model_scale.x) or (parent.GetModelScale and parent:GetModelScale()) or 1, num)
end
return 1
end,
},
parent_scale_y = {
arguments = {{scale = "number"}},
callback = function(self, ent, num)
local parent = self:GetParentEx()
if not self.TargetPart:IsValid() and parent:HasParent() then
parent = parent:GetParent()
end
if parent:IsValid() then
return self:NumberOperator((parent.Type == "part" and parent.Scale and parent.Scale.y * parent.Size) or (parent.pac_model_scale and parent.pac_model_scale.y) or (parent.GetModelScale and parent:GetModelScale()) or 1, num)
end
return 1
end,
},
parent_scale_z = {
arguments = {{scale = "number"}},
callback = function(self, ent, num)
local parent = self:GetParentEx()
if not self.TargetPart:IsValid() and parent:HasParent() then
parent = parent:GetParent()
end
if parent:IsValid() then
return self:NumberOperator((parent.Type == "part" and parent.Scale and parent.Scale.z * parent.Size) or (parent.pac_model_scale and parent.pac_model_scale.z) or (parent.GetModelScale and parent:GetModelScale()) or 1, num)
end
return 1
end,
},
gravitygun_punt = {
arguments = {{time = "number"}},
callback = function(self, ent, time)
time = time or 0.1
ent = try_viewmodel(ent)
local punted = ent.pac_gravgun_punt
if punted and punted + time > pac.RealTime then
return true
end
end,
},
movetype = {
arguments = {{find = "string"}},
callback = function(self, ent, find)
local mt = ent:GetMoveType()
if movetypes[mt] then
return self:StringOperator(movetypes[mt], find)
end
end,
},
dot_forward = {
arguments = {{normal = "number"}},
callback = function(self, ent, normal)
local owner = self:GetRootPart():GetOwner()
if owner:IsValid() then
local ang = owner:EyeAngles()
ang.p = 0
return self:NumberOperator(pac.EyeAng:Forward():Dot(ang:Forward()), normal)
end
return 0
end,
},
dot_right = {
arguments = {{normal = "number"}},
callback = function(self, ent, normal)
local owner = self:GetRootPart():GetOwner()
if owner:IsValid() then
local ang = owner:EyeAngles()
ang.p = 0
return self:NumberOperator(pac.EyeAng:Right():Dot(ang:Forward()), normal)
end
return 0
end,
},
flat_dot_forward = {
arguments = {{normal = "number"}},
callback = function(self, ent, normal)
local owner = self:GetRootPart():GetOwner()
if owner:IsValid() then
local ang = owner:EyeAngles()
ang.p = 0
ang.r = 0
local dir = pac.EyePos - owner:EyePos()
dir[3] = 0
dir:Normalize()
return self:NumberOperator(dir:Dot(ang:Forward()), normal)
end
return 0
end
},
flat_dot_right = {
arguments = {{normal = "number"}},
callback = function(self, ent, normal)
local owner = self:GetRootPart():GetOwner()
if owner:IsValid() then
local ang = owner:EyeAngles()
ang.p = 0
ang.r = 0
local dir = pac.EyePos - owner:EyePos()
dir[3] = 0
dir:Normalize()
return self:NumberOperator(dir:Dot(ang:Right()), normal)
end
return 0
end
}
}
do
local enums = {}
local enums2 = {}
for key, val in pairs(_G) do
if isstring(key) and isnumber(val) then
if key:sub(0,4) == "KEY_" and not key:find("_LAST$") and not key:find("_FIRST$") and not key:find("_COUNT$") then
enums[val] = key:sub(5):lower()
enums2[enums[val]] = val
elseif (key:sub(0,6) == "MOUSE_" or key:sub(0,9) == "JOYSTICK_") and not key:find("_LAST$") and not key:find("_FIRST$") and not key:find("_COUNT$") then
enums[val] = key:lower()
enums2[enums[val]] = val
end
end
end
pac.key_enums = enums
--TODO: Rate limit!!!
net.Receive("pac.BroadcastPlayerButton", function()
local ply = net.ReadEntity()
if not ply:IsValid() then return end
if ply == pac.LocalPlayer and (pace and pace.IsFocused() or gui.IsConsoleVisible()) then return end
local key = net.ReadUInt(8)
local down = net.ReadBool()
key = pac.key_enums[key] or key
ply.pac_buttons = ply.pac_buttons or {}
ply.pac_buttons[key] = down
end)
PART.OldEvents.button = {
arguments = {{button = "string"}},
userdata = {{enums = function()
return enums
end}},
nice = function(self, ent, button)
local ply = self:GetPlayerOwner()
local active = {}
if ply.pac_buttons then
for k,v in pairs(ply.pac_buttons) do
if v then
table.insert(active, "\"" .. tostring(k) .. "\"")
end
end
end
active = table.concat(active, " or ")
if active == "" then
active = "-"
end
return self:GetOperator() .. " \"" .. button .. "\"" .. " in (" .. active .. ")"
end,
callback = function(self, ent, button)
local ply = self:GetPlayerOwner()
if ply == pac.LocalPlayer then
ply.pac_broadcast_buttons = ply.pac_broadcast_buttons or {}
if not ply.pac_broadcast_buttons[button] then
local val = enums2[button:lower()]
if val then
net.Start("pac.AllowPlayerButtons")
net.WriteUInt(val, 8)
net.SendToServer()
end
ply.pac_broadcast_buttons[button] = true
end
end
local buttons = ply.pac_buttons
if buttons then
return buttons[button]
end
end,
}
end
do
local eventMeta = {}
eventMeta.__name = 'undefined'
AccessorFunc(eventMeta, '__name', 'Name')
AccessorFunc(eventMeta, '__name', 'EventName')
AccessorFunc(eventMeta, '__name', 'Nick')
function eventMeta:IsValid(event)
return true
end
function eventMeta:IsAvailable(eventPart)
return true
end
function eventMeta:GetArguments()
self.__registeredArguments = self.__registeredArguments or {}
return self.__registeredArguments
end
function eventMeta:AppendArgument(keyName, keyType, userdata)
self.__registeredArguments = self.__registeredArguments or {}
if not keyType then
error('No Type of argument was specified!')
end
if keyType ~= 'number' and keyType ~= 'string' and keyType ~= 'boolean' then
error('Invalid Type of argument was passed. Valids are number, string or boolean')
end
for i, data in ipairs(self.__registeredArguments) do
if data[1] == keyName then
error('Argument with key ' .. keyName .. ' already exists!')
end
end
self.__registeredArguments = self.__registeredArguments or {}
table.insert(self.__registeredArguments, {keyName, keyType, userdata})
end
function eventMeta:PopArgument(keyName)
for i, data in ipairs(self.__registeredArguments) do
if data[1] == keyName then
return true, i, table.remove(self.__registeredArguments, i)
end
end
return false
end
eventMeta.RemoveArgument = eventMeta.PopArgument
eventMeta.SpliceArgument = eventMeta.PopArgument
function eventMeta:GetClass()
return self.__classname
end
function eventMeta:Think(event, ent, ...)
return false
end
function eventMeta:GetNiceName(part, ent)
if self.extra_nice_name then
return self.extra_nice_name(part, ent, part:GetParsedArgumentsForObject(self))
end
local str = part:GetEvent()
if part:GetArguments() ~= "" then
local args = part:GetArguments():gsub(";", " or ")
if not tonumber(args) then
args = [["]] .. args .. [["]]
end
str = str .. " " .. part:GetOperator() .. " " .. args
end
return pac.PrettifyName(str)
end
local eventMetaTable = {
__index = function(self, key)
if key == '__class' or key == '__classname' then
return rawget(getmetatable(self), '__classname')
end
if rawget(self, key) ~= nil then
return rawget(self, key)
end
return eventMeta[key]
end,
__call = function(self)
local newObj = pac.CreateEvent(self:GetClass())
for k, v in pairs(self) do
if not istable(v) then
newObj[k] = v
else
newObj[k] = table.Copy(v)
end
end
return newObj
end,
-- __newindex = function(self, key, val)
-- rawset(self, key, val)
-- end
}
function pac.GetEventMetatable()
return eventMeta
end
function pac.CreateEvent(nClassName, defArguments)
if not nClassName then error('No classname was specified!') end
local newObj = setmetatable({}, {
__index = eventMetaTable.__index,
__call = eventMetaTable.__call,
__classname = nClassName
})
newObj.__registeredArguments = {}
newObj:SetName(nClassName)
if defArguments then
for i, data in pairs(defArguments) do
newObj:AppendArgument(data[1], data[2], data[3])
end
end
return newObj
end
function pac.RegisterEvent(nRegister)
local classname = nRegister:GetClass()
if PART.Events[classname] then
pac.Message('WARN: Registering event with already existing classname!: '.. classname)
end
PART.Events[classname] = nRegister
end
for classname, data in pairs(PART.OldEvents) do
local arguments = data.arguments
local think = data.callback
local eventObject = pac.CreateEvent(classname)
if arguments then
for i, data2 in ipairs(arguments) do
local key, Type = next(data2)
eventObject:AppendArgument(key, Type, data.userdata and data.userdata[i] or nil)
end
end
eventObject.extra_nice_name = data.nice
function eventObject:Think(event, ent, ...)
return think(event, ent, ...)
end
pac.RegisterEvent(eventObject)
end
timer.Simple(0, function() -- After all addons has loaded
hook.Call('PAC3RegisterEvents', nil, pac.CreateEvent, pac.RegisterEvent)
end)
end
-- custom animation event
do
local animations = pac.animations
local event = {
name = "custom_animation_frame",
nice = function(self, ent, animation)
if animation == "" then self:SetWarning("no animation selected") return "no animation" end
local part = pac.GetLocalPart(animation)
if not IsValid(part) then self:SetError("invalid animation selected") return "invalid animation" end
self:SetWarning()
return part:GetName()
end,
args = {
{"animation", "string", {
enums = function(part)
local output = {}
local parts = pac.GetLocalParts()
for i, part in pairs(parts) do
if part.ClassName == "custom_animation" then
output[i] = part
end
end
return output
end
}},
{"frame_start", "number", {
editor_onchange = function(self, num)
local anim = pace.current_part:GetProperty("animation")
if anim ~= "" then
local part = pac.GetLocalPart(anim)
-- GetAnimationDuration only works while editor is active for some reason
local data = util.JSONToTable(part:GetData())
return math.Clamp(math.ceil(num), 1, #data.FrameData)
end
end
}},
{"frame_end", "number", {
editor_onchange = function(self, num)
local anim = pace.current_part:GetProperty("animation")
local start = pace.current_part:GetProperty("frame_start")
if anim ~= "" then
local part = pac.GetLocalPart(anim)
-- GetAnimationDuration only works while editor is active for some reason
local data = util.JSONToTable(part:GetData())
return math.Clamp(math.ceil(num), start, #data.FrameData)
end
end
}},
--{"framedelta", "number", {editor_clamp = {0,1}, editor_sensitivity = 0.15}}
},
available = function(self, eventPart)
return next(animations.registered) and true or false
end,
func = function (self, eventPart, ent, animation, frame_start, frame_end)
local frame_start = frame_start or 1
local frame_end = frame_end or 1
if not animation or animation == "" then return end
if not IsValid(ent) then return end
if not next(animations.playing) then return end
for i,v in ipairs(animations.playing) do
if v == ent then
local part = pac.GetPartFromUniqueID(pac.Hash(ent), animation)
if not IsValid(part) then return end
local frame, delta = animations.GetEntityAnimationFrame(ent, part:GetAnimID())
if not frame or not delta then return end -- different animation part is playing
return frame >= frame_start and frame <= frame_end
end
end
end
}
local eventObject = pac.CreateEvent(event.name, event.args)
eventObject.Think = event.func
eventObject.IsAvailable = event.available
eventObject.extra_nice_name = event.nice
pac.RegisterEvent(eventObject)
end
-- DarkRP default events
do
local plyMeta = FindMetaTable('Player')
local gamemode = engine.ActiveGamemode
local isDarkRP = function() return gamemode() == 'darkrp' end
local events = {
{
name = 'is_arrested',
args = {},
available = function() return plyMeta.isArrested ~= nil end,
func = function(self, eventPart, ent)
ent = try_viewmodel(ent)
return ent.isArrested and ent:isArrested() or false
end
},
{
name = 'is_wanted',
args = {},
available = function() return plyMeta.isWanted ~= nil end,
func = function(self, eventPart, ent)
ent = try_viewmodel(ent)
return ent.isWanted and ent:isWanted() or false
end
},
{
name = 'is_police',
args = {},
available = function() return plyMeta.isCP ~= nil end,
func = function(self, eventPart, ent)
ent = try_viewmodel(ent)
return ent.isCP and ent:isCP() or false
end
},
{
name = 'wanted_reason',
args = {{'find', 'string'}},
available = function() return plyMeta.getWantedReason ~= nil and plyMeta.isWanted ~= nil end,
func = function(self, eventPart, ent, find)
ent = try_viewmodel(ent)
return eventPart:StringOperator(ent.isWanted and ent.getWantedReason and ent:isWanted() and ent:getWantedReason() or '', find)
end
},
{
name = 'is_cook',
args = {},
available = function() return plyMeta.isCook ~= nil end,
func = function(self, eventPart, ent)
ent = try_viewmodel(ent)
return ent.isCook and ent:isCook() or false
end
},
{
name = 'is_hitman',
args = {},
available = function() return plyMeta.isHitman ~= nil end,
func = function(self, eventPart, ent)
ent = try_viewmodel(ent)
return ent.isHitman and ent:isHitman() or false
end
},
{
name = 'has_hit',
args = {},
available = function() return plyMeta.hasHit ~= nil end,
func = function(self, eventPart, ent)
ent = try_viewmodel(ent)
return ent.hasHit and ent:hasHit() or false
end
},
{
name = 'hit_price',
args = {{'amount', 'number'}},
available = function() return plyMeta.getHitPrice ~= nil end,
func = function(self, eventPart, ent, amount)
ent = try_viewmodel(ent)
return eventPart:NumberOperator(ent.getHitPrice and ent:getHitPrice() or 0, amount)
end
},
}
for k, v in ipairs(events) do
local available = v.available
local eventObject = pac.CreateEvent(v.name, v.args)
eventObject.Think = v.func
function eventObject:IsAvailable()
return isDarkRP() and available()
end
pac.RegisterEvent(eventObject)
end
end
function PART:GetParentEx()
local parent = self:GetTargetPart()
if parent:IsValid() then
return parent
end
return self:GetParent()
end
function PART:GetNiceName()
local event_name = self:GetEvent()
if not PART.Events[event_name] then return "unknown event" end
return PART.Events[event_name]:GetNiceName(self, get_owner(self))
end
local function is_hidden_by_something_else(part, ignored_part)
if part.active_events_ref_count > 0 and not part.active_events[ignored_part] then
return true
end
return part.Hide
end
function PART:IsHiddenBySomethingElse(only_self)
if is_hidden_by_something_else(self, self) then
return true
end
if only_self then return false end
for _, parent in ipairs(self:GetParentList()) do
if is_hidden_by_something_else(parent, self) then
return true
end
end
return false
end
local function should_trigger(self, ent, eventObject)
if not eventObject:IsAvailable(self) then
return true
end
local b = false
if eventObject.ParseArguments then
b = eventObject:Think(self, ent, eventObject:ParseArguments(self)) or false
else
b = eventObject:Think(self, ent, self:GetParsedArgumentsForObject(eventObject)) or false
end
if self.Invert then
b = not b
end
if is_hidden_by_something_else(self, self) then
b = self.Invert
end
self.is_active = b
return b
end
PART.last_event_triggered = false
function PART:OnThink()
local ent = get_owner(self)
if not ent:IsValid() then return end
local data = PART.Events[self.Event]
if not data then return end
self:TriggerEvent(should_trigger(self, ent, data))
if pace and pace.IsActive() and self.Name == "" then
if self.pace_properties and self.pace_properties["Name"] and self.pace_properties["Name"]:IsValid() then
self.pace_properties["Name"]:SetText(self:GetNiceName())
end
end
end
function PART:TriggerEvent(b)
self.event_triggered = b -- event_triggered is just used for the editor
if self.AffectChildrenOnly then
for _, child in ipairs(self:GetChildren()) do
child:SetEventTrigger(self, b)
end
else
local parent = self:GetParent()
if parent:IsValid() then
parent:SetEventTrigger(self, b)
end
end
end
PART.Operators = {
"equal",
"not equal",
"above",
"equal or above",
"below",
"equal or below",
"find",
"find simple",
"maybe",
}
pac.EventArgumentCache = {}
function PART:ParseArguments(...)
local str = ""
local args = {...}
for key, val in pairs(args) do
local T = type(val)
if T == "boolean" then
val = val and "1" or "0"
elseif T == "string" then
val = tostring(val) or ""
elseif T == "number" then
val = tostring(val) or "0"
end
if key == #args then
str = str .. val
else
str = str .. val .. "@@"
end
end
self.Arguments = str
end
function PART:GetParsedArguments(eventObject)
if not eventObject then return end
if eventObject.ParseArguments then
return eventObject:ParseArguments(self)
end
return self:GetParsedArgumentsForObject(eventObject)
end
function PART:GetParsedArgumentsForObject(eventObject)
if not eventObject then return end
local line = self.Arguments
local hash = line .. tostring(eventObject)
if pac.EventArgumentCache[hash] then
return unpack(pac.EventArgumentCache[hash])
end
local args = line:Split("@@")
for i, argData in pairs(eventObject:GetArguments()) do
local typ = argData[2]
if args[i] ~= nil then
if typ == "boolean" then
args[i] = tonumber(args[i]) ~= 0
elseif typ == "number" then
args[i] = tonumber(args[i]) or 0
elseif typ == "string" then
args[i] = tostring(args[i]) or ""
end
end
end
pac.EventArgumentCache[hash] = args
return unpack(args)
end
local cache = {}
local function CompareBTable(a, btbl, func, ...)
for _, b in pairs(btbl) do
if func(a, b, ...) then
return true
end
end
return false
end
function PART:StringOperator(a, b)
local args = cache[b]
if not args then
args = b:Split(";")
cache[b] = args
end
if not self.Operator or not a or not b then
return false
elseif self.Operator == "equal" then
return CompareBTable(a, args, function(x, y) return x == y end)
elseif self.Operator == "not equal" then
return CompareBTable(a, args, function(x, y) return x ~= y end)
elseif self.Operator == "find" then
return CompareBTable(a, args, pac.StringFind)
elseif self.Operator == "find simple" then
return CompareBTable(a, args, pac.StringFind, true)
elseif self.Operator == "changed" then
if a ~= self.changed_last_a then
self.changed_last_a = a
return true
end
elseif self.Operator == "maybe" then
return math.random() > 0.5
end
end
function PART:NumberOperator(a, b)
if not self.Operator or not a or not b then
return false
elseif self.Operator == "equal" then
return a == b
elseif self.Operator == "not equal" then
return a ~= b
elseif self.Operator == "above" then
return a > b
elseif self.Operator == "equal or above" then
return a >= b
elseif self.Operator == "below" then
return a < b
elseif self.Operator == "equal or below" then
return a <= b
elseif self.Operator == "maybe" then
return math.random() > 0.5
end
end
function PART:OnHide()
if self.timerx_reset then
self.time = nil
self.number = 0
end
if self.Event == "weapon_class" then
local ent = self:GetOwner()
if ent:IsValid() then
ent = ent.GetActiveWeapon and ent:GetActiveWeapon() or NULL
if ent:IsValid() then
ent.pac_weapon_class = nil
ent:SetNoDraw(false)
end
end
end
end
function PART:OnShow()
if self.timerx_reset then
self.time = nil
self.number = 0
end
end
function PART:OnAnimationEvent(ent)
if self.Event == "animation_event" then
self:GetParent():CallRecursive("Think")
end
end
function PART:OnFireBullets()
if self.Event == "fire_bullets" then
self:GetParent():CallRecursive("Think")
end
end
function PART:OnEmitSound(ent)
if self.Event == "emit_sound" then
self:GetParent():CallRecursive("Think")
if ent.pac_emit_sound.mute_me then
return false
end
end
end
BUILDER:Register()
do
local enums = {}
for key, val in pairs(_G) do
if isstring(key) and key:find("PLAYERANIMEVENT_", nil, true) then
enums[val] = key:gsub("PLAYERANIMEVENT_", ""):gsub("_", " "):lower()
end
end
pac.AddHook("DoAnimationEvent", "animation_events", function(ply, event, data)
-- update all parts once so OnShow and OnHide are updated properly for animation events
if ply.pac_has_parts then
ply.pac_anim_event = {name = enums[event], time = pac.RealTime, reset = true}
pac.CallRecursiveOnAllParts("OnAnimationEvent")
end
end)
end
pac.AddHook("EntityEmitSound", "emit_sound", function(data)
if pac.playing_sound then return end
local ent = data.Entity
if not ent:IsValid() or not ent.pac_has_parts then return end
ent.pac_emit_sound = {name = data.SoundName, time = pac.RealTime, reset = true, mute_me = ent.pac_emit_sound and ent.pac_emit_sound.mute_me or false}
if pac.CallRecursiveOnAllParts("OnEmitSound", ent) == false then
return false
end
if ent.pac_mute_sounds then
return false
end
end)
pac.AddHook("EntityFireBullets", "firebullets", function(ent, data)
if not ent:IsValid() or not ent.pac_has_parts then return end
ent.pac_fire_bullets = {name = data.AmmoType, time = pac.RealTime, reset = true}
pac.CallRecursiveOnAllParts("OnFireBullets")
if ent.pac_hide_bullets then
return false
end
end)
net.Receive("pac_event", function(umr)
local ply = net.ReadEntity()
local str = net.ReadString()
local on = net.ReadInt(8)
-- ^ resets all other events
if str:find("^", 0, true) then
ply.pac_command_events = {}
end
if ply:IsValid() then
ply.pac_command_events = ply.pac_command_events or {}
ply.pac_command_events[str] = {name = str, time = pac.RealTime, on = on}
end
end)
pac.AddHook("OnPlayerChat", "say_event", function(ply, str)
if ply:IsValid() then
ply.pac_say_event = {str = str, time = pac.RealTime}
end
end)
pac.AddHook("GravGunOnPickedUp", "gravgun_event", function(ply, ent)
if ply:IsValid() then
ply.pac_gravgun_ent = ent
end
end)
pac.AddHook("GravGunOnDropped", "gravgun_event", function(ply, ent)
if ply:IsValid() then
ply.pac_gravgun_ent = ent
end
end)
-- ####
pac.AddHook("GravGunPunt", "gravgun_event", function(ply, ent)
if ply:IsValid() then
ply.pac_gravgun_ent = ent
ply.pac_gravgun_punt = pac.RealTime
end
end)
--[[
attack primary
swim
flinch rightleg
flinch leftarm
flinch head
cancel
attack secondary
flinch rightarm
jump
snap yaw
attack grenade
custom
cancel reload
reload loop
custom gesture sequence
custom sequence
spawn
doublejump
flinch leftleg
flinch chest
die
reload end
reload
custom gesture
--]]
-- Custom event selector wheel
do
local function get_events()
local available = {}
for k,v in pairs(pac.GetLocalParts()) do
if v.ClassName == "event" then
local e = v:GetEvent()
if e == "command" then
local cmd, time, hide = v:GetParsedArgumentsForObject(v.Events.command)
if hide then continue end
available[cmd] = {type = e, time = time}
end
end
end
local list = {}
for k,v in pairs(available) do
v.trigger = k
table.insert(list, v)
end
table.sort(list, function(a, b) return a.trigger > b.trigger end)
return list
end
local selectorBg = Material("sgm/playercircle")
local selected
function pac.openEventSelectionWheel()
gui.EnableScreenClicker(true)
local scrw, scrh = ScrW(), ScrH()
local scrw2, scrh2 = scrw*0.5, scrh*0.5
local color_red = Color(255,0,0)
local R = 48
local events = get_events()
local nevents = #events
-- Theta size of each wedge
local thetadiff = math.pi*2 / nevents
-- Used to compare the dot product
local coslimit = math.cos(thetadiff * 0.5)
-- Keeps the circles R units from each others' center
local radius
if nevents < 3 then
radius = R
else
radius = R/math.cos((nevents - 2)*math.pi*0.5/nevents)
end
-- Scale down to keep from going out of the screen
local gScale
if radius+R > scrh2 then
gScale = scrh2 / (radius+R)
else
gScale = 1
end
local selections = {}
for k, v in ipairs(events) do
local theta = (k-1)*thetadiff
selections[k] = {
grow = 0,
name = v.trigger,
event = v,
x = math.sin(theta),
y = -math.cos(theta),
}
end
local function draw_circle(self, x, y)
local dot = self.x*x + self.y*y
local grow
if dot > coslimit then
selected = self
grow = 0.1
else
grow = 0
end
self.grow = self.grow*0.9 + grow -- Framerate will affect this effect's speed but oh well
local scale = gScale*(1 + self.grow*0.2)
local m = Matrix()
m:SetTranslation(Vector(scrw2, scrh2, 0))
m:Scale(Vector(scale, scale, scale))
cam.PushModelMatrix(m)
local x, y = self.x*radius, self.y*radius
local ply = pac.LocalPlayer
local data = ply.pac_command_events and ply.pac_command_events[self.event.trigger] and ply.pac_command_events[self.event.trigger]
if data then
local is_oneshot = self.event.time and self.event.time > 0
if is_oneshot then
local f = (pac.RealTime - data.time) / self.event.time
local s = Lerp(math.Clamp(f,0,1), 1, 0)
local v = Lerp(math.Clamp(f,0,1), 0.55, 0.15)
surface.SetDrawColor(HSVToColor(210,s,v))
else
if data.on == 1 then
surface.SetDrawColor(HSVToColor(210,1,0.55))
else
surface.SetDrawColor(HSVToColor(210,0,0.15))
end
end
else
surface.SetDrawColor(HSVToColor(210,0,0.15))
end
surface.DrawTexturedRect(x-48, y-48, 96, 96)
draw.SimpleText(self.name, "DermaDefault", x, y, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
cam.PopModelMatrix()
end
pac.AddHook("HUDPaint","custom_event_selector",function()
-- Right clicking cancels
if input.IsButtonDown(MOUSE_RIGHT) then pac.closeEventSelectionWheel(true) return end
-- Normalize mouse vector from center of screen
local x, y = input.GetCursorPos()
x = x - scrw2
y = y - scrh2
if x==0 and y==0 then x = 1 y = 0 else
local l = math.sqrt(x^2+y^2)
x = x/l
y = y/l
end
DisableClipping(true)
render.PushFilterMag(TEXFILTER.ANISOTROPIC)
render.PushFilterMin(TEXFILTER.ANISOTROPIC)
surface.SetMaterial(selectorBg)
draw.SimpleText("Right click to cancel", "DermaDefault", scrw2, scrh2+radius+R, color_red, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP)
for _, v in ipairs(selections) do draw_circle(v, x, y) end
render.PopFilterMag()
render.PopFilterMin()
DisableClipping(false)
end)
end
function pac.closeEventSelectionWheel(cancel)
gui.EnableScreenClicker(false)
pac.RemoveHook("HUDPaint","custom_event_selector")
if selected and cancel ~= true then
if not selected.event.time then
RunConsoleCommand("pac_event", selected.event.trigger, "toggle")
elseif selected.event.time > 0 then
RunConsoleCommand("pac_event", selected.event.trigger)
else
local ply = pac.LocalPlayer
if ply.pac_command_events and ply.pac_command_events[selected.event.trigger] and ply.pac_command_events[selected.event.trigger].on == 1 then
RunConsoleCommand("pac_event", selected.event.trigger, "0")
else
RunConsoleCommand("pac_event", selected.event.trigger, "1")
end
end
end
selected = nil
end
concommand.Add("+pac_events", pac.openEventSelectionWheel)
concommand.Add("-pac_events", pac.closeEventSelectionWheel)
end