File size: 10,154 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
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
----- UpdateObject

DefineClass.UpdateObject = {
	__parents = {"Object"},
	
	update_thread_on_init = true,
	update_interval = 10000,
	update_thread = false,
}

RecursiveCallMethods.OnObjUpdate = "call"

local Sleep = Sleep
local procall = procall
local GameTime = GameTime
function UpdateObject:Init()
	if self.update_thread_on_init then
		self:StartObjUpdateThread()
	end
end

function UpdateObject:ObjUpdateProc(update_interval)
	self:InitObjUpdate(update_interval)
	while true do
		procall(self.OnObjUpdate, self, GameTime(), update_interval)
		Sleep(update_interval)
	end
end

function UpdateObject:StartObjUpdateThread()
	if not self:IsSyncObject() or not mapdata.GameLogic or not self.update_interval then
		return
	end
	DeleteThread(self.update_thread)
	self.update_thread = CreateGameTimeThread(self.ObjUpdateProc, self, self.update_interval)
	if Platform.developer then
		ThreadsSetThreadSource(self.update_thread, "ObjUpdateThread", self.ObjUpdateProc)
	end
end

function UpdateObject:StopObjUpdateThread()
	DeleteThread(self.update_thread)
	self.update_thread = nil
end

function UpdateObject:InitObjUpdate(update_interval)
	Sleep(1 + self:Random(update_interval, "InitObjUpdate"))
end

function UpdateObject:Done()
	self:StopObjUpdateThread()
end


----- ReservedObject

DefineClass.ReservedObject = {
	__parents = { "InitDone" },
	properties = {
		{ id = "reserved_by", editor = "object", default = false, no_edit = true },
	},
}

function TryInterruptReserved(reserved_obj)
	local reserved_by = reserved_obj.reserved_by
	if IsValid(reserved_by) then
		return reserved_by:OnReservationInterrupted()
	end 
	reserved_obj.reserved_by = nil
end

ReservedObject.Disown = TryInterruptReserved

AutoResolveMethods.CanReserverBeInterrupted = "or"
ReservedObject.CanReserverBeInterrupted = empty_func

function ReservedObject:CanBeReservedBy(obj)
	return not self.reserved_by or self.reserved_by == obj or self:CanReserverBeInterrupted(obj)
end

function ReservedObject:TryReserve(reserved_by)
	if not self:CanBeReservedBy(reserved_by) then return false end
	if self.reserved_by and self.reserved_by ~= reserved_by then
		if not TryInterruptReserved(self) then return end
	end
	return self:Reserve(reserved_by)
end

function ReservedObject:Reserve(reserved_by)
	assert(IsKindOf(reserved_by, "ReserverObject"))
	local previous_reservation = reserved_by.reserved_obj
	if previous_reservation and previous_reservation ~= self then
		--assert(not previous_reservation, "Reserver trying to reserve two objects at once!")
		previous_reservation:CancelReservation(reserved_by)
	end
	self.reserved_by = reserved_by
	reserved_by.reserved_obj = self
	self:OnReserved(reserved_by)
	return true
end

ReservedObject.OnReserved = empty_func
ReservedObject.OnReservationCanceled = empty_func

function ReservedObject:CancelReservation(reserved_by)
	if self.reserved_by == reserved_by then
		self.reserved_by = nil
		reserved_by.reserved_obj = nil
		self:OnReservationCanceled()
		return true
	end
end

function ReservedObject:Done()
	self:Disown()
end

DefineClass.ReserverObject = {
	__parents = { "CommandObject" },

	reserved_obj = false,
}

function ReserverObject:OnReservationInterrupted()
	return self:TrySetCommand("CmdInterrupt")
end

----- OwnedObject

DefineClass.OwnershipStateBase = {
	OnStateTick  = empty_func,
	OnStateExit  = empty_func,

	CanDisown    = empty_func,
	CanBeOwnedBy = empty_func,
}

DefineClass("ConcreteOwnership", "OwnershipStateBase")

local function SetOwnerObject(owned_obj, owner)
	assert(not owner or IsKindOf(owner, "OwnerObject"))
	owner = owner or false
	local prev_owner = owned_obj.owner
	if owner ~= prev_owner then
		owned_obj.owner = owner
		
		local notify_owner = not prev_owner or prev_owner:GetOwnedObject(owned_obj.ownership_class) == owned_obj		
		if notify_owner then
			if prev_owner then
				prev_owner:SetOwnedObject(false, owned_obj.ownership_class)
			end
			if owner then
				owner:SetOwnedObject(owned_obj)
			end
		end
	end
end

function ConcreteOwnership.OnStateTick(owned_obj, owner)
	return SetOwnerObject(owned_obj, owner)
end

function ConcreteOwnership.OnStateExit(owned_obj)
	return SetOwnerObject(owned_obj, false)
end

function ConcreteOwnership.CanDisown(owned_obj, owner, reason)
	return owned_obj.owner == owner
end

function ConcreteOwnership.CanBeOwnedBy(owned_obj, owner, ...)
	return owned_obj.owner == owner
end

DefineClass("SharedOwnership", "OwnershipStateBase")
SharedOwnership.CanBeOwnedBy = return_true

DefineClass("ForbiddenOwnership", "OwnershipStateBase")

DefineClass.OwnedObject = {
	__parents = { "ReservedObject" },
	properties = {
		{ id = "owner",                                               editor = "object", default = false, no_edit = true },
		{ id = "can_change_ownership", name = "Can change ownership", editor = "bool",   default = true,  help = "If true, the player can change who owns the object", },
		{ id = "ownership_class",      name = "Ownership class",      editor = "combo",  default = false, items = GatherComboItems("GatherOwnershipClasses"), },
	},
	
	ownership = "SharedOwnership",
}

AutoResolveMethods.CanDisown = "and"
function OwnedObject:CanDisown(owner, reason)
	return g_Classes[self.ownership].CanDisown(self, owner, reason)
end

function OwnedObject:Disown()
	ReservedObject.Disown(self)
	self:TrySetSharedOwnership()
end

AutoResolveMethods.CanBeOwnedBy = "and"
function OwnedObject:CanBeOwnedBy(obj, ...)
	if not self:CanBeReservedBy(obj) then return end
	return g_Classes[self.ownership].CanBeOwnedBy(self, obj, ...)
end

AutoResolveMethods.CanChangeOwnership = "and"
function OwnedObject:CanChangeOwnership()
	return self.can_change_ownership
end

function OwnedObject:GetReservedByOrOwner()
	return self.reserved_by or self.owner
end

OwnedObject.OnOwnershipChanged = empty_func

function OwnedObject:TrySetOwnership(ownership, forced, ...)
	assert(ownership)
	if not ownership or not forced and not self:CanChangeOwnership() then return end
	
	local prev_owner = self.owner
	local prev_ownership = self.ownership
	self.ownership = ownership
	if prev_ownership ~= ownership then
		g_Classes[prev_ownership].OnStateExit(self, ...)
	end
	g_Classes[ownership].OnStateTick(self, ...)
	self:OnOwnershipChanged(prev_ownership, prev_owner)
end

local function TryInterruptReservedOnDifferentOwner(owned_obj)
	local reserved_by = owned_obj.reserved_by
	if IsValid(reserved_by) and reserved_by ~= owned_obj.owner then
		reserved_by:OnReservationInterrupted()
		owned_obj.reserved_by = nil
	end
end

local OwnershipChangedReactions = {
	ConcreteOwnership = {
		ConcreteOwnership  = TryInterruptReservedOnDifferentOwner,
		ForbiddenOwnership = TryInterruptReserved,
	},
	SharedOwnership = {
		ConcreteOwnership  = TryInterruptReservedOnDifferentOwner,
		ForbiddenOwnership = TryInterruptReserved,
	}
}

function OwnedObject:OnOwnershipChanged(prev_ownership, prev_owner)
	local transition = table.get(OwnershipChangedReactions, prev_ownership, self.ownership)
	if transition then
		transition(self)
	end
end

----- OwnedObject helper functions

function OwnedObject:TrySetConcreteOwnership(forced, owner)
	return self:TrySetOwnership("ConcreteOwnership", forced, owner)
end

function OwnedObject:SetConcreteOwnership(...)
	return self:TrySetConcreteOwnership("forced", ...)
end

function OwnedObject:HasConcreteOwnership()
	return self.ownership == "ConcreteOwnership"
end

function OwnedObject:TrySetSharedOwnership(forced, ...)
	return self:TrySetOwnership("SharedOwnership", forced, ...)
end

function OwnedObject:SetSharedOwnership(...)
	return self:TrySetSharedOwnership("forced", ...)
end

function OwnedObject:HasSharedOwnership()
	return self.ownership == "SharedOwnership"
end

function OwnedObject:TrySetForbiddenOwnership(forced, ...)
	return self:TrySetOwnership("ForbiddenOwnership", forced, ...)
end

function OwnedObject:SetForbiddenOwnership(...)
	return self:TrySetForbiddenOwnership("forced", ...)
end

function OwnedObject:HasForbiddenOwnership()
	return self.ownership == "ForbiddenOwnership"
end

----- OwnedObject helper functions end

DefineClass.OwnedByUnit = {
	__parents = { "OwnedObject" },
	properties = {
		{ id = "can_have_dead_owners", name = "Can have dead owners", editor = "bool", default = false, help = "If true, the object can have dead units as owners", },
	}
}

function OwnedByUnit:CanBeOwnedBy(obj, ...)
	if not self.can_have_dead_owners and obj:IsDead() then return end
	return OwnedObject.CanBeOwnedBy(self, obj, ...)
end

DefineClass.OwnerObject = {
	__parents = { "ReserverObject" },
	owned_objects = false,
}

function OwnerObject:Init()
	self.owned_objects = {}
end

function OwnerObject:Owns(object)
	local ownership_class = object.ownership_class
	if not ownership_class then return end
	return self.owned_objects[ownership_class] == object
end

function OwnerObject:DisownObjects(reason)
	local owned_objects = self.owned_objects
	for _, ownership_class in ipairs(owned_objects) do
		local owned_object = owned_objects[ownership_class]
		if owned_object and owned_object:CanDisown(self, reason) then
			owned_object:Disown()
		end
	end
end

function OwnerObject:GetOwnedObject(ownership_class)
	assert(ownership_class)
	return self.owned_objects[ownership_class]
end

function OwnerObject:SetOwnedObject(owned_obj, ownership_class)
	assert(not owned_obj or owned_obj:IsKindOf("OwnedObject"))
	if owned_obj then
		ownership_class = ownership_class or owned_obj.ownership_class
		assert(ownership_class == owned_obj.ownership_class)
	end
	assert(ownership_class)
	if not ownership_class then
		return false
	end
	local prev_owned_obj = self:GetOwnedObject(ownership_class)
	if prev_owned_obj == owned_obj then
		return false
	end
	local owned_objects = self.owned_objects
	
	owned_objects[ownership_class] = owned_obj
	table.remove_entry(owned_objects, ownership_class)
	if owned_obj then
		table.insert(owned_objects, ownership_class)
		if prev_owned_obj then
			prev_owned_obj:TrySetSharedOwnership()
		end
		owned_obj:TrySetConcreteOwnership(nil, self)
	end
	return true
end

if Platform.developer then

function OwnedObject:GetTestData(data)
	data.ReservedBy = self.reserved_by
end

end

-----