File size: 7,889 Bytes
b6a38d7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
DefineClass.CooldownDef = {
	__parents = { "Preset", },
	properties = {
		{ category = "General", id = "DisplayName", name = "Display Name", 
			editor = "text", default = false, translate = true, },
		{ category = "General", id = "TimeScale", name = "Time Scale", 
			editor = "choice", default = "sec", items = function (self) return GetTimeScalesCombo() end, },
		{ category = "General", id = "TimeMin", name = "Default min", help = "Defaut cooldown time.", 
			editor = "number", default = 1000, 
			scale = function(obj) return obj.TimeScale end, },
		{ category = "General", id = "TimeMax", name = "Default max", 
			editor = "number", default = false, 
			scale = function(obj) return obj.TimeScale end, },
		{ category = "General", id = "MaxTime", name = "Max time", help = "The maximum time the cooldown can accumulate to.", 
			editor = "number", default = false, 
			scale = function(obj) return obj.TimeScale end, },
		{ category = "General", id = "ExpireMsg", name = "Send CooldownExpired message", 
			editor = "bool", default = false },
		{ category = "General", id = "OnExpire", name = "OnExpire", 
			editor = "script", default = false, params = "cooldown_obj, cooldown_def" },
	},
	GlobalMap = "CooldownDefs",
	EditorMenubarName = "Cooldowns",
	EditorMenubar = "Editors.Lists",
	EditorIcon = "CommonAssets/UI/Icons/cooldown.png",
}

DefineClass.CooldownObj = {
	__parents = { "InitDone" },
	cooldowns = false,
	cooldowns_thread = false,
}

function CooldownObj:Init()
	self.cooldowns = {}
end

function CooldownObj:Done()
	self.cooldowns = nil
	DeleteThread(self.cooldowns_thread)
	self.cooldowns_thread = nil
end

function CooldownObj:GetCooldown(cooldown_id)
	local cooldowns = self.cooldowns
	local time = cooldowns and cooldowns[cooldown_id]
	if not time or time == true then return time end
	time = time - GameTime()
	if time >= 0 then
		return time
	end
	cooldowns[cooldown_id] = nil
end

function CooldownObj:GetCooldowns()
	for id in pairs(self.cooldowns) do
		self:GetCooldown(id)
	end
	return self.cooldowns
end

function CooldownObj:OnCooldownExpire(cooldown_id)
	local def = CooldownDefs[cooldown_id]
	assert(def)
	if def.ExpireMsg then
		Msg("CooldownExpired", self, cooldown_id, def)
	end
	local OnExpire = def.OnExpire
	if OnExpire then
		return OnExpire(self, def)
	end
end

function CooldownObj:DefaultCooldownTime(cooldown_id, def)
	def = def or CooldownDefs[cooldown_id]
	local min, max = def.TimeMin, def.TimeMax
	if not max or min > max then
		return min
	end
	return InteractionRandRange(min, max, cooldown_id)
end

function CooldownObj:SetCooldown(cooldown_id, time, max)
	local cooldowns = self.cooldowns
	if not cooldowns then return end
	local def = CooldownDefs[cooldown_id]
	assert(def)
	if not def then return end
	time = time or self:DefaultCooldownTime(cooldown_id, def)
	local prev_time = cooldowns[cooldown_id]
	local now = GameTime()
	if time == true then
		cooldowns[cooldown_id] = true
	else
		if max then
			if prev_time == true or prev_time and prev_time - now >= time then
				return
			end
		end
		time = Min(time, def.MaxTime)
		cooldowns[cooldown_id] = now + time
		if def.OnExpire or def.ExpireMsg then
			if IsValidThread(self.cooldowns_thread) then
				Wakeup(self.cooldowns_thread)
			else
				self.cooldowns_thread = CreateGameTimeThread(function(self)
					while self:UpdateCooldowns() do end
				end, self)
			end
		end
	end
	if not prev_time or prev_time ~= true and prev_time - now < 0 then
		Msg("CooldownSet", self, cooldown_id, def)
	end
end

function CooldownObj:ModifyCooldown(cooldown_id, delta_time)
	local cooldowns = self.cooldowns
	if not cooldowns or (delta_time or 0) == 0 then return end
	local def = CooldownDefs[cooldown_id]
	assert(def)
	local time = cooldowns[cooldown_id]
	if not time or time == true then
		return
	end
	local now = GameTime()
	if time - now < 0 then
		assert(not (def.OnExpire or def.ExpireMsg)) -- messages with expiration effects should be removed by now
		cooldowns[cooldown_id] = nil
		return
	end
	cooldowns[cooldown_id] = now + Min(time + delta_time - now, def.MaxTime)
	if delta_time < 0 and (def.OnExpire or def.ExpireMsg) then
		Wakeup(self.cooldowns_thread)
	end
	return true
end

function CooldownObj:ModifyCooldowns(delta_time, filter)
	local cooldowns = self.cooldowns
	if not cooldowns or (delta_time or 0) == 0 then
		return
	end
	if delta_time <= 0 then
		Wakeup(self.cooldowns_thread)
	end
	local now = GameTime()
	for cooldown_id, time in sorted_pairs(cooldowns) do
		if time ~= true and time - now >= 0 or (not filter or filter(cooldown_id, time)) then
			cooldowns[id] = now + Min(time + delta_time - now, def.MaxTime)
		end
	end
end

function CooldownObj:RemoveCooldown(cooldown_id)
	local cooldowns = self.cooldowns
	if not cooldowns then return end
	local def = CooldownDefs[cooldown_id]
	assert(def)
	local time = cooldowns[cooldown_id]
	if time then
		cooldowns[cooldown_id] = nil
		if time == true or time - GameTime() >= 0 then
			self:OnCooldownExpire(cooldown_id)
		end
	end
end

function CooldownObj:RemoveCooldowns(filter)
	local cooldowns = self.cooldowns
	if not cooldowns then return end
	local removed
	local now = GameTime()
	for cooldown_id, time in sorted_pairs(cooldowns) do
		if not filter or filter(cooldown_id) then
			cooldowns[id] = nil
			if time == true or time - now >= 0 then
				removed = removed or {}
				removed[#removed + 1] = id
			end
		end
	end
	for _, id in ipairs(removed) do
		self:OnCooldownExpire(id)
	end
end

function CooldownObj:UpdateCooldowns()
	local cooldowns = self.cooldowns
	if not cooldowns then return end
	local now = GameTime()
	local next_time
	local CooldownDefs = CooldownDefs
	while true do
		local expired, more_expired
		for cooldown_id, time in pairs(cooldowns) do
			if time ~= true then
				local def = CooldownDefs[cooldown_id]
				time = time - now
				if time <= 0 then
					if def.OnExpire or def.ExpireMsg then
						if expired then
							more_expired = true
							if expired > cooldown_id then
								expired = cooldown_id
							end
						else
							expired = cooldown_id
						end
					else
						cooldowns[cooldown_id] = nil
					end
				else
					if def.OnExpire or def.ExpireMsg then
						next_time = Min(next_time, time)
					end
				end
			end	
		end
		if expired then
			cooldowns[expired] = nil
			self:OnCooldownExpire(expired)
		end
		if not more_expired then break end
	end
	if next_time then
		WaitWakeup(next_time)
		return true -- get called again
	end
	self.cooldowns_thread = nil
end

function CooldownObj:GetDynamicData(data)
	local cooldowns = self.cooldowns
	if not cooldowns then return end
	local now = GameTime()
	for cooldown_id, time in pairs(cooldowns) do
		if time ~= true and time - now < 0 then
			cooldowns[cooldown_id] = nil
		end
	end
	data.cooldowns = next(cooldowns) and cooldowns or nil
end

function CooldownObj:SetDynamicData(data)
	local cooldowns = data.cooldowns
	if not cooldowns then
		self.cooldowns = {}
		DeleteThread(self.cooldowns_thread)
		self.cooldowns_thread = nil
		return
	end
	self.cooldowns = cooldowns
	local CooldownDefs = CooldownDefs
	for cooldown_id, time in pairs(cooldowns) do
		local def = CooldownDefs[def]
		if not def then
			cooldowns[cooldown_id] = nil
		elseif time ~= true then
			if def.OnExpire or def.ExpireMsg then
				if IsValidThread(self.cooldowns_thread) then
					Wakeup(self.cooldowns_thread)
				else
					self.cooldowns_thread = CreateGameTimeThread(function(self)
						while self:UpdateCooldowns() do end
					end, self)
				end
				return
			end
		end
	end
	DeleteThread(self.cooldowns_thread)
	self.cooldowns_thread = nil
end

function CooldownObj:CheatClearCooldowns()
	local cooldowns = self.cooldowns
	if not cooldowns then return end
	for cooldown_id in pairs(cooldowns) do
		cooldowns[cooldown_id] = nil
		self:OnCooldownExpire(cooldown_id)
	end
	self.cooldowns_thread = nil
	ObjModified(self)
end