File size: 8,298 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
if (const.pfPassTypeGridBits or 0) == 0 then
	return
end

local pass_tile = const.PassTileSize
local pass_type_tile = const.PassTypeTileSize
local overlap_dist = pass_type_tile * 3 / 2
local InvalidZ = const.InvalidZ
local gofAny = const.gofSyncObject | const.gofPermanent
local GetPosXYZ = CObject.GetPosXYZ
local IsValidPos = CObject.IsValidPos
local PassTypeCircleOr = terrain.PassTypeCircleOr
local PassTypeCircleSet = terrain.PassTypeCircleSet
local PassTypeInvalidate = terrain.PassTypeInvalidate
local GetMapCenter = terrain.GetMapCenter
local InplaceExtend = box().InplaceExtend
local IsEmpty = box().IsEmpty
local point_pack, point_unpack = point_pack, point_unpack

function OnMsg.Autorun()
	local pass_type_bits = rawget(_G, "pass_type_bits")
	if not next(pass_type_bits) then
		return
	end
	local PassTypeComboName = function(bit_names)
		table.sort(bit_names)
		return table.concat(bit_names, "|")
	end
	assert(#pass_type_bits == (const.pfPassTypeGridBits or 0))
	pathfind_pass_types = { "cost_default" } -- the first type is the default type
	local ptype_name_to_value = {}
	local bit_count = Min(const.pfPassTypeGridBits or 0, #pass_type_bits)
	local ptypes_combos = (1<<bit_count) - 1
	for value=1,ptypes_combos do
		local names = {}
		for k=1,bit_count do
			if value & (1<<(k-1)) ~= 0 then
				names[#names + 1] = pass_type_bits[k]
			end
		end
		pathfind_pass_types[#pathfind_pass_types + 1] = PassTypeComboName(names)
	end
	for idx, pfc in pairs(pathfind) do
		for value=1,ptypes_combos do
			local names = {}
			local cost_max = 0
			for k=1,bit_count do
				if value & (1<<(k-1)) ~= 0 then
					local name = pass_type_bits[k]
					names[#names + 1] = name
					cost_max = Max(cost_max, pfc[name] or PF_DEFAULT_COST)
				end
			end
			local name = PassTypeComboName(names)
			pfc[name] = cost_max
			--print("\t", pfc.name, ":", name, cost_max)
		end
	end
	pathfind_pass_grid_types = pathfind_pass_types
end

function GetPassGridType(PassTypeName)
	return (PassTypeName or "") ~= "" and ((table.find(pathfind_pass_grid_types, PassTypeName) or 1) - 1)
end
local GetPassGridType = GetPassGridType

function PassTypesCombo()
	local items = { "" }
	return table.iappend(items, pass_type_bits or empty_table)
end

----

if FirstLoad then
	PassTypeMaxRadius = -1
	PassTypeMaxCount = 0
	PassTypesDisabled = false
end

local function AddPassTypeMaxRadius(radius)
	if radius == PassTypeMaxRadius then
		PassTypeMaxCount = PassTypeMaxCount + 1
	elseif radius > PassTypeMaxRadius then
		PassTypeMaxRadius = radius
		PassTypeMaxCount = 1
	end
end
local function UpdatePassTypeMaxRadius(obj)
	local radius = obj.PassTypeRadius
	if radius > 0 and obj.pass_type_applied and GetPassGridType(obj.PassTypeName) ~= 0 then
		return AddPassTypeMaxRadius(radius)
	end
end
local function RemovePassTypeMaxRadius(radius)
	assert(radius <= PassTypeMaxRadius)
	if radius == PassTypeMaxRadius then
		assert(PassTypeMaxCount > 0)
		PassTypeMaxCount = Max(0, PassTypeMaxCount - 1)
		if PassTypeMaxCount == 0 then
			PassTypeMaxRadius = -1
			MapForEach("map", "PassTypeObj", nil, nil, nil, gofAny, UpdatePassTypeMaxRadius)
		end
	end
end
local function ReapplyCost(obj, inv, reapply)
	return obj:SetCostRadius(nil, nil, inv, reapply)
end
local function OnPassTypeOverlap(obj, target, radius, x0, y0, z0, inv)
	if obj == target or not obj.pass_type_applied then return end
	local _, _, z = GetPosXYZ(obj)
	if z ~= z0 then return end -- different pass grid
	local dist = radius + obj.PassTypeRadius + overlap_dist
	if not obj:IsCloser2D(x0, y0, dist) then return end
	return ReapplyCost(obj, inv, "overlap")
end

function RemoveCost(obj)
	return obj:SetCostRadius(-1)
end

local function ReapplyAllPassTypes()
	local inv = box()
	PassTypeMaxRadius = -1
	PassTypeMaxCount = 0
	MapForEach("map", "PassTypeObj", nil, nil, nil, gofAny, ReapplyCost, inv, "rebuild")
	terrain.PassTypeInvalidate(inv)
end

local function ClearAllPassTypes()
	PassTypeMaxRadius = -1
	PassTypeMaxCount = 0
	terrain.PassTypeClear()
end

function DisablePassTypes()
	if not PassTypesDisabled then
		PassTypesDisabled = true
		ClearAllPassTypes()
	end
end

function EnablePassTypes()
	if PassTypesDisabled then
		PassTypesDisabled = false
		ReapplyAllPassTypes()
	end
end

-- the pass type grid isn't persisted, restore it
OnMsg.LoadGameObjectsUnpersisted = ReapplyAllPassTypes
OnMsg.DoneMap = ClearAllPassTypes

----

DefineClass.PassTypeObj = {
	__parents = { "Object" },
	properties = {
		{ id = "PassTypeRadius", name = "Pass Radius", editor = "number", default = 0, scale = "m" },
		{ id = "PassTypeName",   name = "Pass Type",   editor = "choice", default = "", items = PassTypesCombo },
	},
	pass_type_applied = false,
}

function PassTypeObj:IsVirtual()
	return self:GetGameFlags(gofAny) == 0
end

AutoResolveMethods.ApplyPassCostOnTerrain = "or"
PassTypeObj.ApplyPassCostOnTerrain = empty_func

function PassTypeObj:SetCostRadius(radius, name, inv, reapply)
	--DbgClear(true) DbgSetVectorZTest(false)
	if PassTypesDisabled then
		return
	end
	local applied = self.pass_type_applied
	if not applied and reapply then
		return
	end
	local prev_radius = self.PassTypeRadius
	radius = radius or prev_radius
	local prev_name = self.PassTypeName
	name = name or prev_name
	local pass_type = GetPassGridType(name)
	local valid = radius >= 0 and pass_type ~= 0 and IsValidPos(self) and not self:IsVirtual()
	if not applied and not valid then
		return
	end
	local xc, yc = GetMapCenter()
	local x, y, z, x0, y0, z0, apply
	if valid then
		x, y, z = GetPosXYZ(self)
		if z and self:ApplyPassCostOnTerrain() then
			z = InvalidZ
		end
		apply = point_pack(x - xc, y - yc, z)
	end
	if applied == apply and radius == prev_radius and name == prev_name and not reapply then
		return
	end
	local moved = applied and applied ~= apply
	local shrinked = applied and radius < prev_radius
	local enlarged = apply and (not applied or reapply == "rebuild" or prev_radius < radius)
	local type_changed = applied and apply and name ~= prev_name

	if radius ~= prev_radius then
		self.PassTypeRadius = radius
	end
	if name ~= prev_name then
		self.PassTypeName = name
	end
	if applied ~= apply then
		self.pass_type_applied = apply
	end

	inv = inv or box()
	if not reapply and (shrinked or moved or type_changed) then
		-- clear the previous mark
		x0, y0, z0 = point_unpack(applied)
		x0, y0 = xc + x0, yc + y0
		InplaceExtend(inv, PassTypeCircleSet(x0, y0, z0 or InvalidZ, prev_radius, 0))
		--DbgAddCircle(point(x0, y0, z0), prev_radius, red)
		if shrinked or moved then
			if shrinked then
				RemovePassTypeMaxRadius(prev_radius)
			end
			-- reapply only the affected surrounding objects on the same pf level
			local enum_radius = overlap_dist + prev_radius + PassTypeMaxRadius
			MapForEach(x0, y0, z0 or InvalidZ, enum_radius, "PassTypeObj", nil, nil, nil, gofAny, OnPassTypeOverlap, self, prev_radius, x0, y0, z0, inv)
		end
	elseif enlarged then
		AddPassTypeMaxRadius(radius)
	end
	if apply then
		--DbgAddCircle(self, radius, green)
		InplaceExtend(inv, PassTypeCircleOr(x, y, z or InvalidZ, radius, pass_type))
	end
	--DbgAddBox(inv)
	if not reapply and not IsEmpty(inv) then
		NetUpdateHash("SetCostRadius", x or x0, y or y0, z or z0, radius, name, inv)
		PassTypeInvalidate(inv)
	end
	return inv
end

function PassTypeObj:SetPassTypeRadius(value)
	self:SetCostRadius(value)
end

function PassTypeObj:SetPassTypeName(value)
	self:SetCostRadius(nil, value)
end

function PassTypeObj:Done()
	ExecuteProcess("Passability", "RemoveCost", self)
end

function PassTypeObj:GameInit()
	self:SetCostRadius()
end

function PassTypeObj:CompleteElementConstruction()
	self:SetCostRadius()
end

----

DefineClass.PassTypeMarker = {
	__parents = { "PassTypeObj", "RadiusMarker", "EditorColorObject" },
	entity = "NoteMarker",
	radius_prop = "PassTypeRadius",
	editor_text_member = "PassTypeName",
	editor_text_offset = point(0, 0, 3*guim),
}

function PassTypeMarker:EditorGetColor()
	local grid_type = GetPassGridType(self.PassTypeName)
	if grid_type == 0 then
		return white
	end
	local color = pass_type_colors and pass_type_colors[self.PassTypeName]
	return color or RandColor(xxhash(grid_type))
end
function PassTypeMarker:EditorGetTextColor()
	return self:EditorGetColor()
end
function PassTypeMarker:EditorCallbackMove()
	self:SetCostRadius()
end