-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcustomEvents.lua
More file actions
270 lines (232 loc) · 9.33 KB
/
Copy pathcustomEvents.lua
File metadata and controls
270 lines (232 loc) · 9.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
-- if custom.storeInChart == false, then we place everything into 'level', new.forceStoreInLevel = true, new.variant = custom.variant
-- else, if configs.storeInChart == true, we have more of a dilema
-- generated events that have new.storeInChart == true, can simply go into the chart (IF that is the variant we're editing!!)
-- other events (including the driver comment) must go into the level, but can be tagged with new.variant = custom.variant
local customEvents = {
eventConfigs = {} -- event id -> custom event config
}
function customEvents:init()
Event.convertToVanilla = {}
-- TODO: using BBPs `bbp.mods`
-- partially stolen from BeatblockPlus
local modsPath = "Mods"
if not love.filesystem.getInfo(modsPath, 'directory') then
log("[CustomEvents] Failed to find Mods directory. Custom events won't be loaded.", "error")
return
end
for _, dir in ipairs(love.filesystem.getDirectoryItems(modsPath)) do
local modPath = modsPath .. "/" .. dir
if not love.filesystem.getInfo(modPath, 'directory') then goto nextMod end
if not love.filesystem.getInfo(modPath .. "/custom-events.json", 'file') then goto nextMod end
if not love.filesystem.getInfo(modPath .. "/mod.json", 'file') then goto nextMod end
if love.filesystem.getInfo(modPath .. "/.lovelyignore", 'file') then goto nextMod end
local modConfig = dpf.loadJson(modPath .. "/mod.json")
local eventConfigList = dpf.loadJson(modPath .. "/custom-events.json")
if #eventConfigList == 0 then log("[CustomEvents] file '" .. modPath .. "/custom-events.json' does not contain any events.", "error") end
for i,eventConfig in ipairs(eventConfigList) do
self:registerEvent(modConfig, modPath, eventConfig)
end
::nextMod::
end
end
function customEvents:registerEvent(mod, modPath, eventConfig)
if eventConfig.source == nil then return log("[CustomEvents] '" .. modPath .. "/custom-events.json': missing 'source' field", "error") end
if type(eventConfig.source) ~= "string" then return log("[CustomEvents] '" .. modPath .. "/custom-events.json': 'source' field must be a string", "error") end
if eventConfig.type == nil then return log("[CustomEvents] '" .. modPath .. "/custom-events.json': missing 'type' field", "error") end
if type(eventConfig.type) ~= "string" then return log("[CustomEvents] '" .. modPath .. "/custom-events.json': 'type' field must be a string", "error") end
eventConfig.source = modPath.."/"..eventConfig.source
eventConfig.modId = mod.id
eventConfig.modName = mod.name
if eventConfig.icon then eventConfig.icon = love.graphics.newImage(modPath .. "/" .. eventConfig.icon) end
if eventConfig.type == "modded" then
local info, onLoad, onOffset, onBeat, editorDraw, editorProperties, hitCount, checkActiveRange = dofile(eventConfig.source)
info.event = mod.id .. ":" .. info.event
info.modId = mod.id
info.modName = mod.name
Event.onLoad[info.event] = onLoad
Event.onOffset[info.event] = onOffset
Event.onBeat[info.event] = onBeat
Event.editorDraw[info.event] = editorDraw or eventConfig.icon
Event.editorProperties[info.event] = editorProperties
Event.hitCount[info.event] = hitCount
Event.checkActiveRange[info.event] = checkActiveRange or function(event, beat, lastBeat) return event.time >= beat and event.time <= lastBeat end
Event.info[info.event] = info
self.eventConfigs[info.event] = eventConfig
log('loaded event "'..info.name..'" ('..info.event..')')
elseif eventConfig.type == "vanilla-compatible" then
local info, editorDraw, editorProperties, checkActiveRange, convertToVanilla = dofile(eventConfig.source)
info.event = mod.id .. ":" .. info.event
info.modId = mod.id
info.modName = mod.name
info.custom = true
Event.editorDraw[info.event] = editorDraw or eventConfig.icon
Event.editorProperties[info.event] = editorProperties
Event.convertToVanilla[info.event] = convertToVanilla
Event.info[info.event] = info
checkActiveRange = checkActiveRange or function(event, beat, lastBeat) return event.time >= beat and event.time <= lastBeat end
Event.checkActiveRange[info.event] = function(v, beat, lastBeat) -- hide events that are "stored in other chart variants"
local storeInChart = Event.info[v.type].storeInChart and not v.forceStoreInLevel
if v.type == "custom-events-api:unknownCustomEvent" then storeInChart = v.info.storeInChart end
if storeInChart and v.variant and v.variant ~= cs.variant.name then
return false
else
return checkActiveRange(v, beat, lastBeat)
end
end
self.eventConfigs[info.event] = eventConfig
log('loaded event "'..info.name..'" ('..info.event..')')
else
return log("[CustomEvents] '" .. modPath .. "/custom-events.json': 'type' field has invalid value '" .. eventConfig.type .. "'", "error")
end
end
local function randomID()
local id = ""
for i=1,16 do -- there are ~10^22 possible IDs, so we shouldn't get collisions
id = id .. string.char(math.random(97,122))
end
return id
end
function customEvents.unpack(events, levelVariant) -- unpack custom events into vanilla ones (returns a new array)
local unpackingInfo = {}
local newEvents = {}
table.sort(events, function(a, b)
if a.time ~= b.time then return a.time < b.time end
if (a.order or 0) ~= (b.order or 0) then return (a.order or 0) < (b.order or 0) end
if a.angle ~= b.angle then return a.angle < b.angle end
return false
end)
for _,v in ipairs(events) do
if not Event.info[v.type] then
cs:playbackError("Event '" .. v.type .. "' not found!\n\nMissing mod: '" .. tostring(v.info and v.info.modName or nil) .. "'")
if cs.name == "Editor" then
cs.errorExit = true
end
return {}
end
if not Event.info[v.type].custom then
if Event.info[v.type].modId then
v.info = {
modName = Event.info[v.type].modName,
modId = Event.info[v.type].modId
}
end
table.insert(newEvents, v)
goto nextEvent
end
local generatedEvents = Event.convertToVanilla[v.type](v, newEvents, unpackingInfo)
local storeInChart = Event.info[v.type].storeInChart and not v.forceStoreInLevel
local modName = Event.info[v.type].modName
local modId = Event.info[v.type].modId
if v.type == "custom-events-api:unknownCustomEvent" then
storeInChart = v.info.storeInChart
modName = v.info.modName
v = v.customEventData
end
local id = v.customEventId or randomID()
if storeInChart and not v.variant then v.variant = levelVariant.name end
local variant = v.variant
local dataComment = {
type = "comment",
text = "!! THIS IS A CUSTOM EVENT !!\n mod: '" .. tostring(modName) .. "'\n event: '" .. v.type .. "'.\nYou probably want to install the required mod if you want to edit this chart properly.",
time = v.time,
angle = v.angle,
customEventId = id,
customEventData = v,
variant = variant,
info = {
storeInChart = storeInChart,
modName = modName,
modId = modId,
},
}
table.insert(newEvents, dataComment)
for _,new in ipairs(generatedEvents) do
new.customEventId = id
new.variant = variant
new.customEventData = nil -- just in case
if Event.info[new.type].storeInChart then
if storeInChart then
new.variant = nil
if variant == levelVariant.name then
table.insert(newEvents, new)
end
else
new.forceStoreInLevel = true
table.insert(newEvents, new)
end
else
table.insert(newEvents, new)
end
end
::nextEvent::
end
return newEvents
end
function customEvents:resolveEventId(id) -- compatibility with 1.0.0 saves (eventid -> modid:eventid)
if string.find(id, ":") then return id end
for id2,_ in pairs(self.eventConfigs) do
local pure_id2 = string.sub(id2, string.find(id2, ":")+1)
if id == pure_id2 then return id2 end
end
return id
end
function customEvents:pack(events, variant) -- pack vanilla events into custom ones (returns a new array)
local unrecognisedEvents = {} -- id -> list of events
-- find all unrecognised event ids
for _,v in ipairs(events) do
if v.customEventData then
v.customEventData.type = self:resolveEventId(v.customEventData.type)
if not Event.info[v.customEventData.type] then
unrecognisedEvents[v.customEventId] = {}
end
end
end
-- gather unrecognised events
for _,v in ipairs(events) do
if v.customEventId and unrecognisedEvents[v.customEventId] and not v.customEventData then
table.insert(unrecognisedEvents[v.customEventId], v)
end
end
local newEvents = {}
for _,v in ipairs(events) do
if not v.customEventId then -- normal events
table.insert(newEvents, v)
goto nextEvent
end
if not v.customEventData then -- these events get packed up
goto nextEvent
end
if unrecognisedEvents[v.customEventId] then -- vanilla compatible event, but we're missing the mod it's from
local new = {
type = "custom-events-api:unknownCustomEvent",
time = v.time,
angle = v.angle,
customEventData = v.customEventData,
customEventId = v.customEventId,
variant = v.variant,
packedEvents = unrecognisedEvents[v.customEventId],
info = v.info,
}
table.insert(newEvents, new)
else
local new = v.customEventData -- normal custom event
new.customEventId = v.customEventId
table.insert(newEvents, new)
end
::nextEvent::
end
return newEvents
end
function customEvents:updateEventPalette(palette)
for id,conf in pairs(self.eventConfigs) do
if conf.palette then
local p = palette
for _,folder in ipairs(conf.palette) do
p[folder] = p[folder] or {}
p = p[folder]
end
table.insert(p, id)
end
end
end
return customEvents