Skip to content

Commit b2211e5

Browse files
committed
event update
add AND gate fix OR gate, add ignore_inverts add ratio_timer (allowing reset on hide and biased ratios e.g. different from 50/50) timer default interval is now 1 (0 caused spam issues, kinda annoying) add timerx2 (start and end times instead of needing to stack two timerx) timerx2 can be quickbuilt with a two-number event input e.g. "1 3" will automatically set start time to 1, end time to 3 bidirectional link between arguments and dynamic properties to reflect edits in real time
1 parent ad58c59 commit b2211e5

File tree

2 files changed

+260
-12
lines changed

2 files changed

+260
-12
lines changed

lua/pac3/core/client/parts/event.lua

Lines changed: 201 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,16 @@ function PART:SetEvent(event)
170170
if (owner == pac.LocalPlayer) and (not pace.processing) then
171171
if event == "command" then owner.pac_command_events = owner.pac_command_events or {} end
172172
if not self.Events[event] then --invalid event? try another event
173+
if #string.Split(event, " ") == 2 then --timerx2
174+
local strs = string.Split(event, " ")
175+
timer.Simple(0.2, function()
176+
if not self.pace_properties or self ~= pace.current_part then return end
177+
self:SetEvent("timerx2")
178+
self:SetArguments(strs[1] .. "@@" .. strs[2] .. "@@1@@0")
179+
pace.PopulateProperties(self)
180+
end)
181+
return
182+
end
173183
if isnumber(tonumber(event)) then --timerx
174184
timer.Simple(0.2, function()
175185
if not self.pace_properties or self ~= pace.current_part then return end
@@ -220,7 +230,36 @@ function PART:SetEvent(event)
220230
end
221231
end
222232

233+
function PART:SetProperty(key, val)
234+
if self["Set" .. key] ~= nil then
235+
if self["Get" .. key](self) ~= val then
236+
self["Set" .. key](self, val)
237+
end
238+
elseif self.GetDynamicProperties then
239+
local info = self:GetDynamicProperties()[key]
240+
if info and info then
241+
if isnumber(val) then
242+
val = math.Round(val, 7)
243+
end
244+
info.set(val)
245+
if self:GetPlayerOwner() ~= pac.LocalPlayer then return end
246+
if pace.IsActive() then
247+
self.pace_properties["Arguments"]:SetText(" " .. self.Arguments)
248+
self.pace_properties["Arguments"].original_str = self.Arguments
249+
end
250+
end
251+
end
252+
end
253+
254+
function PART:SetArguments(str)
255+
self.Arguments = str
256+
if pace.IsActive() and pac.LocalPlayer == self:GetPlayerOwner() then
257+
pace.PopulateProperties(self)
258+
end
259+
end
260+
223261
function PART:Initialize()
262+
self.found_cached_parts = {}
224263
self.specialtrackedparts = {}
225264
self.ExtraHermites = {}
226265
if self:GetPlayerOwner() == LocalPlayer() then
@@ -234,13 +273,21 @@ function PART:Initialize()
234273
end
235274
end)
236275
end
276+
--force refresh
277+
timer.Simple(10, function()
278+
self.found_cached_parts = {}
279+
end)
237280

238281
end
239282

240283
function PART:GetOrFindCachedPart(uid_or_name)
241284
local part = nil
242-
self.found_cached_parts = self.found_cached_parts or {}
243-
if self.found_cached_parts[uid_or_name] then return self.found_cached_parts[uid_or_name] end
285+
local existing_part = self.found_cached_parts[uid_or_name]
286+
if existing_part then
287+
if existing_part ~= NULL and existing_part:IsValid() then
288+
return existing_part
289+
end
290+
end
244291

245292
local owner = self:GetPlayerOwner()
246293
part = pac.GetPartFromUniqueID(pac.Hash(owner), uid_or_name) or pac.FindPartByPartialUniqueID(pac.Hash(owner), uid_or_name)
@@ -577,6 +624,36 @@ PART.OldEvents = {
577624
end,
578625
},
579626

627+
timerx2 = {
628+
operator_type = "none",
629+
tutorial_explanation = "timerx2 is a dual timerx, a stopwatch that counts time since it's shown and determines whether it fits within the window defined by StartTime and EndTime",
630+
arguments = {{StartTime = "number"}, {EndTime = "number"}, {reset_on_hide = "boolean"}, {synced_time = "boolean"}},
631+
userdata = {
632+
{default = 0.5, timerx_property = "StartTime"},
633+
{default = 1, timerx_property = "EndTime"},
634+
{default = true, timerx_property = "reset_on_hide"}
635+
},
636+
nice = function(self, ent, seconds, seconds2)
637+
return "timerx2: " .. ("%.2f"):format(self.number or 0, 2) .. " between " .. seconds .. " and " .. seconds2 .. " seconds"
638+
end,
639+
callback = function(self, ent, StartTime, EndTime, reset_on_hide, synced_time)
640+
641+
local time = synced_time and CurTime() or RealTime()
642+
643+
self.time = self.time or time
644+
self.timerx_reset = reset_on_hide
645+
646+
if self.AffectChildrenOnly and self:IsHiddenBySomethingElse() then
647+
return false
648+
end
649+
self.number = time - self.time
650+
651+
return self.number > StartTime and self.number < EndTime
652+
653+
--return self:NumberOperator(self.number, seconds)
654+
end,
655+
},
656+
580657
timersys = {
581658
operator_type = "number", preferred_operator = "above",
582659
tutorial_explanation = "like timerx, timersys is a stopwatch that counts time (it uses SysTime()) since it's shown (hiding and re-showing is an important resetting condition).\nit takes that time and compares it with the duration defined in seconds.\nmeaning it can show things after(above) a delay or until(below) a certain amount of time passes",
@@ -1549,9 +1626,32 @@ PART.OldEvents = {
15491626
end,
15501627
},
15511628

1629+
ratio_timer = {
1630+
operator_type = "none",
1631+
arguments = {{interval = "number"}, {offset = "number"}, {ratio = "number"}, {reset_on_hide = "boolean"}},
1632+
userdata = {{default = 1}, {default = 0}, {default = 0.5}, {default = false}},
1633+
callback = function(self, ent, interval, offset, ratio, reset_on_hide)
1634+
interval = interval or 1
1635+
offset = offset or 0
1636+
final_offset = offset
1637+
ratio = math.Clamp(math.abs(ratio or 0.5), 0, 1)
1638+
1639+
if interval == 0 or interval < FrameTime() then
1640+
self.timer_hack = not self.timer_hack
1641+
return self.timer_hack
1642+
end
1643+
1644+
if reset_on_hide then
1645+
final_offset = -self.showtime + offset
1646+
end
1647+
return (CurTime() + final_offset) % interval < (interval * ratio)
1648+
end,
1649+
},
1650+
15521651
timer = {
15531652
operator_type = "none",
15541653
arguments = {{interval = "number"}, {offset = "number"}},
1654+
userdata = {{default = 1}, {default = 0}},
15551655
callback = function(self, ent, interval, offset)
15561656
interval = interval or 1
15571657
offset = offset or 0
@@ -2358,7 +2458,7 @@ PART.OldEvents = {
23582458
damage_zone_hit = {
23592459
operator_type = "number", preferred_operator = "above",
23602460
arguments = {{time = "number"}, {damage = "number"}, {uid = "string"}},
2361-
userdata = {{default = 1}, {default = 0}, {enums = function(part)
2461+
userdata = {{default = 1}, {default = 0}, {default = "", enums = function(part)
23622462
local output = {}
23632463
local parts = pac.GetLocalParts()
23642464

@@ -2408,7 +2508,7 @@ PART.OldEvents = {
24082508
damage_zone_kill = {
24092509
operator_type = "mixed", preferred_operator = "above",
24102510
arguments = {{time = "number"}, {uid = "string"}},
2411-
userdata = {{default = 1}, {enums = function(part)
2511+
userdata = {{default = 1}, {default = "", enums = function(part)
24122512
local output = {}
24132513
local parts = pac.GetLocalParts()
24142514

@@ -2635,8 +2735,8 @@ PART.OldEvents = {
26352735
or_gate = {
26362736
operator_type = "none", preferred_operator = "find simple",
26372737
tutorial_explanation = "combines multiple events into an OR gate, the event will activate as soon as one of the events listed is activated (taking inverts into account)",
2638-
arguments = {{uids = "string"}},
2639-
userdata = {{enums = function(part)
2738+
arguments = {{uids = "string"}, {ignore_inverts = "boolean"}},
2739+
userdata = {{default = "", enums = function(part)
26402740
local output = {}
26412741
local parts = pac.GetLocalParts()
26422742
for i, part in pairs(parts) do
@@ -2646,20 +2746,108 @@ PART.OldEvents = {
26462746
end
26472747
return output
26482748
end}},
2649-
callback = function(self, ent, uids)
2749+
callback = function(self, ent, uids, ignore_inverts)
26502750
if uids == "" then return false end
26512751
local uid_splits = string.Split(uids, ";")
2752+
local true_count = 0
26522753
for i,uid in ipairs(uid_splits) do
26532754
local part = self:GetOrFindCachedPart(uid)
2654-
if part then
2655-
local b = part.event_triggered
2656-
if part.Invert then b = not b end
2755+
if part:IsValid() then
2756+
local raw = part.raw_event_condition
2757+
local b = false
2758+
if ignore_inverts then
2759+
b = raw
2760+
else
2761+
b = not part.event_triggered
2762+
end
26572763
if b then
2658-
return true
2764+
true_count = true_count + 1
26592765
end
26602766
end
26612767
end
2662-
return false
2768+
return true_count > 0
2769+
end,
2770+
},
2771+
xor_gate = {
2772+
operator_type = "none", preferred_operator = "find simple",
2773+
tutorial_explanation = "combines multiple events into an XOR gate, the event will activate if one (and only one) of the two events is activated (taking inverts into account)",
2774+
arguments = {{uid1 = "string"},{uid2 = "string"}},
2775+
userdata = {
2776+
{default = "", enums = function(part)
2777+
local output = {}
2778+
local parts = pac.GetLocalParts()
2779+
for i, part in pairs(parts) do
2780+
if part.ClassName == "event" then
2781+
output["[UID:" .. string.sub(i,1,16) .. "...] " .. part:GetName() .. "; in " .. part:GetParent().ClassName .. " " .. part:GetParent():GetName()] = part.UniqueID
2782+
end
2783+
end
2784+
return output
2785+
end},
2786+
{default = "", enums = function(part)
2787+
local output = {}
2788+
local parts = pac.GetLocalParts()
2789+
for i, part in pairs(parts) do
2790+
if part.ClassName == "event" then
2791+
output["[UID:" .. string.sub(i,1,16) .. "...] " .. part:GetName() .. "; in " .. part:GetParent().ClassName .. " " .. part:GetParent():GetName()] = part.UniqueID
2792+
end
2793+
end
2794+
return output
2795+
end}
2796+
},
2797+
callback = function(self, ent, uid1, uid2)
2798+
if uid1 == "" then return false end
2799+
if uid2 == "" then return false end
2800+
local part1 = self:GetOrFindCachedPart(uid1)
2801+
local part2 = self:GetOrFindCachedPart(uid2)
2802+
if not IsValid(part1) or not IsValid(part2) then return false end
2803+
local b1 = part1.event_triggered if part1.Invert then b1 = not b1 end
2804+
local b2 = part2.event_triggered if part2.Invert then b2 = not b2 end
2805+
return ((b1 and not b2) or (b2 and not b1)) and not (b1 and b2)
2806+
end,
2807+
nice = function(self, ent, uid1, uid2)
2808+
local part1 = self:GetOrFindCachedPart(uid1)
2809+
local part2 = self:GetOrFindCachedPart(uid2)
2810+
if not IsValid(part1) or not IsValid(part2) then return "xor_gate : [" .. uid1 .. ", " .. uid2 .. "] <ERROR>" end
2811+
local str = "xor_gate : [" .. part1:GetName() .. ", " .. part2:GetName() .. "]"
2812+
return str
2813+
end
2814+
},
2815+
and_gate = {
2816+
operator_type = "none", preferred_operator = "find simple",
2817+
tutorial_explanation = "combines multiple events into an AND gate, the event will activate when all the events listed are activated (taking inverts into account)",
2818+
arguments = {{uids = "string"}, {ignore_inverts = "boolean"}},
2819+
userdata = {{default = "", enums = function(part)
2820+
local output = {}
2821+
local parts = pac.GetLocalParts()
2822+
for i, part in pairs(parts) do
2823+
if part.ClassName == "event" then
2824+
output["[UID:" .. string.sub(i,1,16) .. "...] " .. part:GetName() .. "; in " .. part:GetParent().ClassName .. " " .. part:GetParent():GetName()] = part.UniqueID
2825+
end
2826+
end
2827+
return output
2828+
end}},
2829+
callback = function(self, ent, uids, ignore_inverts)
2830+
if uids == "" then return false end
2831+
local uid_splits = string.Split(uids, ";")
2832+
for i,uid in ipairs(uid_splits) do
2833+
local part = self:GetOrFindCachedPart(uid)
2834+
if part:IsValid() then
2835+
local raw = part.raw_event_condition
2836+
local b = false
2837+
if ignore_inverts then
2838+
b = raw
2839+
else
2840+
b = not part.event_triggered
2841+
end
2842+
2843+
if not b then
2844+
return false
2845+
end
2846+
else
2847+
return false
2848+
end
2849+
end
2850+
return true
26632851
end,
26642852
}
26652853

@@ -3263,6 +3451,7 @@ local function should_trigger(self, ent, eventObject)
32633451
else
32643452
b = eventObject:Think(self, ent, self:GetParsedArgumentsForObject(eventObject)) or false
32653453
end
3454+
self.raw_event_condition = b
32663455

32673456
if self.Invert then
32683457
b = not b

lua/pac3/editor/client/parts.lua

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3841,6 +3841,47 @@ end
38413841
function pace.AddClassSpecificPartMenuComponents(menu, obj)
38423842
if obj.Notes == "showhidetest" then menu:AddOption("(hide/show test) reset", function() obj:CallRecursive("OnShow") end):SetIcon("icon16/star.png") end
38433843

3844+
do --event reorganization
3845+
local full_events = true
3846+
for i,v in ipairs(pace.BulkSelectList) do
3847+
if v.ClassName ~= "event" then full_events = false end
3848+
end
3849+
if full_events then
3850+
menu:AddOption("reorganize into a non-ACO pocket", function()
3851+
for i,part in ipairs(pace.BulkSelectList) do
3852+
part:SetParent(part:GetRootPart())
3853+
end
3854+
local prime_parent = obj:GetParent()
3855+
if prime_parent.ClassName == "event" or pace.BulkSelectList[1] == prime_parent then
3856+
prime_parent = obj:GetRootPart()
3857+
end
3858+
for i,part in ipairs(pace.BulkSelectList) do
3859+
part:SetParent()
3860+
part:SetAffectChildrenOnly(false)
3861+
part:SetDestinationPart()
3862+
end
3863+
obj:SetParent(prime_parent)
3864+
for i,part in ipairs(pace.BulkSelectList) do
3865+
part:SetParent(obj)
3866+
end
3867+
end):SetIcon("icon16/clock_link.png")
3868+
menu:AddOption("reorganize into an ACO downward tower", function()
3869+
local parent = obj:GetParent()
3870+
local grandparent = obj:GetParent()
3871+
if parent.Parent then grandparent = parent:GetParent() end
3872+
3873+
for i,part in ipairs(pace.BulkSelectList) do
3874+
part:SetAffectChildrenOnly(true)
3875+
part:SetDestinationPart()
3876+
part:SetParent(parent)
3877+
parent = part
3878+
end
3879+
pace.BulkSelectList[1]:SetParent(obj:GetParent())
3880+
obj:SetParent(parent)
3881+
end):SetIcon("icon16/clock_link.png")
3882+
end
3883+
end
3884+
38443885
if obj.ClassName == "camera" then
38453886
if not obj:IsHidden() then
38463887
local remembered_view = {pace.ViewPos, pace.ViewAngles}
@@ -3994,6 +4035,24 @@ function pace.AddClassSpecificPartMenuComponents(menu, obj)
39944035
end
39954036
obj:SetMultipleTargetParts(table.concat(uid_tbl,";"))
39964037
end):SetIcon("icon16/star.png")
4038+
if obj.Event == "and_gate" or obj.Event == "or_gate" then
4039+
menu:AddOption("(" .. #pace.BulkSelectList .. " parts in Bulk select) Set AND or OR gate arguments", function()
4040+
local uid_tbl = {}
4041+
for i,part in ipairs(pace.BulkSelectList) do
4042+
table.insert(uid_tbl, part.UniqueID)
4043+
end
4044+
obj:SetProperty("uids", table.concat(uid_tbl,";"))
4045+
end):SetIcon("icon16/clock_link.png")
4046+
end
4047+
if obj.Event == "xor_gate" and #pace.BulkSelectList == 2 then
4048+
menu:AddOption("(2 parts in Bulk select) Set XOR arguments", function()
4049+
local uid_tbl = {}
4050+
for i,part in ipairs(pace.BulkSelectList) do
4051+
table.insert(uid_tbl, part.UniqueID)
4052+
end
4053+
obj:SetArguments(table.concat(uid_tbl,"@@"))
4054+
end):SetIcon("icon16/clock_link.png")
4055+
end
39974056
end
39984057
if not IsValid(obj.DestinationPart) then
39994058
menu:AddOption("engrave / quick-link to parent", function() obj:SetDestinationPart(obj:GetParent()) end):SetIcon("icon16/star.png")

0 commit comments

Comments
 (0)