File size: 8,236 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
DefineClass.CoverObj = {
	__parents = { "Object", "ComponentAttach" },
	entity = false,
	dbg_mesh = false,
}

function CoverObj:GameInit()
	local parent = self:GetParent()
	if not self:IsAligned() then
		self:Notify("delete")
		return
	end
end

function CoverObj:IsAligned()
end

function CoverObj:Show()
end

function CoverObj:Hide()
	if IsValid(self.dbg_mesh) then
		DoneObject(self.dbg_mesh)
		self.dbg_mesh = nil
	end
end


DefineClass.CoverWall = {
	__parents = { "CoverObj" },
}

function CoverWall:Show()
	if not IsValid(self.dbg_mesh) then
		self.dbg_mesh = PlaceObject("Mesh")		
		self.dbg_mesh:SetDepthTest(true)

		local width = 3 * const.SlabSizeX / 10
		local height = const.SlabSizeY
		local depth = const.SlabSizeZ

		width = width / 2
		height = height / 2
		--depth = -depth
		local p_pstr = pstr("")
		local function AddPoint(x,y,z) 
			p_pstr:AppendVertex(point(x*width, y*height, z*depth), RGB(120, 20, 180))
		end
		
		-- -x
		AddPoint(-1, -1, 0) AddPoint(-1,  1, 0) AddPoint(-1,  1, 1)		
		AddPoint(-1,  1, 1) AddPoint(-1, -1, 1) AddPoint(-1, -1, 0)		
		-- +x
		AddPoint( 1, -1, 0) AddPoint( 1,  1, 0) AddPoint( 1,  1, 1)		
		AddPoint( 1,  1, 1) AddPoint( 1, -1, 1) AddPoint( 1, -1, 0)
		-- -y
		AddPoint(-1, -1, 0) AddPoint( 1, -1, 0) AddPoint( 1, -1, 1)
		AddPoint( 1, -1, 1) AddPoint(-1, -1, 1) AddPoint(-1, -1, 0)		
		-- +y
		AddPoint(-1,  1, 0) AddPoint( 1,  1, 0) AddPoint( 1,  1, 1)
		AddPoint( 1,  1, 1) AddPoint(-1,  1, 1) AddPoint(-1,  1, 0)		
		-- z0
		AddPoint(-1, -1, 0) AddPoint(-1,  1, 0) AddPoint( 1,  1, 0)
		AddPoint( 1,  1, 0) AddPoint( 1, -1, 0) AddPoint(-1, -1, 0)
		-- +z
		AddPoint(-1, -1, 1) AddPoint(-1,  1, 1) AddPoint( 1,  1, 1)
		AddPoint( 1,  1, 1) AddPoint( 1, -1, 1) AddPoint(-1, -1, 1)
		
		self.dbg_mesh:SetMesh(p_pstr)
		
		self:Attach(self.dbg_mesh)
	end
end

function CoverWall:IsAligned()
	local x, y, z = self:GetPosXYZ()
	local angle = self:GetAngle()
	local tx, ty, tz = WallVoxelToWorld(WallWorldToVoxel(x, y, z, angle))
	return x == tx and y == ty and z == tz
end

-- cover shields angle!
local cover_dir_angle = {
	["up"] = 90 * 60,
	["right"] = 2 * 90 * 60,
	["down"] = 3 * 90 * 60,
	["left"] = 0,
}
function GetCoverDirAngle(dir)
	return cover_dir_angle[dir]
end

local coverHigh = const.CoverHigh
local coverLow = const.CoverLow

function GetCoversAt(pos_or_obj)
	local up, right, down, left = GetCover(pos_or_obj)
	if not up then
		return
	end
	
	local covers = {
		[cover_dir_angle.up] = (up == coverHigh or up == coverLow) and up or nil,
		[cover_dir_angle.right] = (right == coverHigh or right == coverLow) and right or nil,
		[cover_dir_angle.down] = (down == coverHigh or down == coverLow) and down or nil,
		[cover_dir_angle.left] = (left == coverHigh or left == coverLow) and left or nil,
	}
	
	return next(covers) and covers or nil
end

local cover_offsets = {
	point(0, -const.SlabSizeY / 2, 0), -- up
	point(const.SlabSizeX / 2, 0, 0), -- "right"
	point(0, const.SlabSizeY / 2, 0), -- "down"
	point(-const.SlabSizeX / 2, 0, 0), -- "left"
}

function GetCoverOffset(angle)
	local idx = 1 + (1 + CardinalDirection(angle) / (90*60)) % 4
	return cover_offsets[idx]
end

function GetAngleCover(pos, angle)
	local idx = 1 + (1 + CardinalDirection(angle) / (90*60)) % 4
	local cover = select(idx, GetCover(pos))
	return cover
end

function GetHighestCoverUI(pos_or_obj)
	if not IsPoint(pos_or_obj) and pos_or_obj.return_pos then
		pos_or_obj = pos_or_obj.return_pos
	end
	
	return GetHighestCover(pos_or_obj)
end

function GetCoverTypes(pos_or_obj)
	local up, right, down, left = GetCover(pos_or_obj)
	if not up then
		return
	end

	local cover_low = up == coverLow or right == coverLow or down == coverLow or left == coverLow
	local cover_high = up == coverHigh or right == coverHigh or down == coverHigh or left == coverHigh
	
	return cover_high, cover_low
end

function GetHighestCover(pos_or_obj)
	local high, low = GetCoverTypes(pos_or_obj)
	if high then
		return coverHigh
	end
	if low then
		return coverLow
	end
end

function GetUnitOrientationToHighCover(pos, angle)
	local up, right, down, left = GetCover(pos)
	if not up then
		return
	end
	if up ~= coverHigh and right ~= coverHigh and down ~= coverHigh and left ~= coverHigh then
		return
	end
	
	local max_diff = 90*60
	local best_angle, best_diff
	-- rotations against the cover
	local a1 = cover_dir_angle.up + 180*60
	local a2 = cover_dir_angle.right + 180*60
	local a3 = cover_dir_angle.down + 180*60
	local a4 = cover_dir_angle.left + 180*60

	local diff1 = abs(AngleDiff(angle, a1))
	local diff2 = abs(AngleDiff(angle, a2))
	local diff3 = abs(AngleDiff(angle, a3))
	local diff4 = abs(AngleDiff(angle, a4))

	-- avoid facing another high cover
	-- up / down
	if right == coverHigh and diff2 < max_diff or left == coverHigh and diff4 < max_diff then
		if up ~= coverHigh   and (not best_diff or diff1 < best_diff) then best_angle, best_diff = a1, diff1 end
		if down ~= coverHigh and (not best_diff or diff3 < best_diff) then best_angle, best_diff = a3, diff3 end
	end
	-- left / right
	if up == coverHigh and diff1 < max_diff or down == coverHigh and diff3 < max_diff then
		if left ~= coverHigh and (not best_diff or diff4 < best_diff) then best_angle, best_diff = a4, diff4 end
		if right ~= coverHigh and (not best_diff or diff2 < best_diff) then best_angle, best_diff = a2, diff2 end
	end

	-- fallback (can face another high cover)
	if not best_angle then
		-- up / down
		if right == coverHigh and diff2 < max_diff or left == coverHigh and diff4 < max_diff then
			if not best_diff or diff1 < best_diff then best_angle, best_diff = a1, diff1 end
			if not best_diff or diff3 < best_diff then best_angle, best_diff = a3, diff3 end
		end
		-- left / right
		if up == coverHigh and diff1 < max_diff or down == coverHigh and diff3 < max_diff then
			if not best_diff or diff4 < best_diff then best_angle, best_diff = a4, diff4 end
			if not best_diff or diff2 < best_diff then best_angle, best_diff = a2, diff2 end
		end
	end

	return best_angle
end

DefineClass.BaseObjectWithCover = {
	__parents = { "AutoAttachObject" },
	covers = false,
}

function BaseObjectWithCover:GameInit()
	self.covers = self:GetAttaches("CoverObj")
	for _, cover in ipairs(self.covers or empty_table) do
		if self:GetParent() then
			DoneObject(cover)
		else
			local pos = cover:GetPos() + cover:GetAttachOffset()
			cover:Detach()
			cover:SetPos(pos)
		end
	end
	if self:GetParent() then
		self.covers = {}
	end
end

function BaseObjectWithCover:Done()
	for _, obj in ipairs(self.covers) do
		DoneObject(obj)
	end
	self.covers = nil
end

DefineClass.BaseCliff = {
	__parents = { "FloorAlignedObj", "Deposition" },
	flags = {efPathSlab = true},
}

DefineClass.BaseTrench = {
	__parents = { "FloorAlignedObj", "Deposition" },
	flags = {efPathSlab = true},
}

local halfVoxelSizeX = const.SlabSizeX / 2
local halfVoxelSizeY = const.SlabSizeY / 2
local VoxelSizeZ = const.SlabSizeZ
local clrInvisible = RGBA(0, 0, 0, 0)
local slabx, slaby, slabz = const.SlabSizeX, const.SlabSizeY, const.SlabSizeZ

function GetVoxelBox(padding, world_pos)
	padding = padding or 1
	world_pos = world_pos or GetCursorPos()
	
	local surface = terrain.GetHeight(world_pos)
	world_pos = (surface and surface > world_pos:z()) and world_pos:SetZ(surface) or world_pos
	local x, y, z = VoxelToWorld(WorldToVoxel(world_pos))
	local pt2d = point(x, y)
	local offset = point(padding * slabx + slabx / 2, padding * slaby + slaby / 2)
	local bbox = box(pt2d - offset, pt2d + offset)
	local minz, maxz = z, z
	MapForEach(bbox, function(obj)
		local obj_bbox = obj:GetEntityBBox()
		local obj_z = obj:GetPos():z()
		if obj_z then
			local obj_minz, obj_maxz = obj_z + obj_bbox:minz(), obj_z + obj_bbox:maxz()
			minz = (obj_minz < minz) and obj_minz or minz
			maxz = (obj_maxz > maxz) and obj_maxz or maxz
		end
	end)
	minz = minz - slabz / 2 - padding * slabz
	maxz = maxz + slabz / 2 + padding * slabz
	
	return box(bbox:min():SetZ(minz), bbox:max():SetZ(maxz))
end

function GetCoverPercentage(stand_pos, attack_pos, target_stance)
	local cover, any, coverage = PosGetCoverPercentageFrom(stand_pos, attack_pos)
	if cover == coverLow and target_stance == "Standing" then
		cover, coverage = false, 0
	end
	return cover, any, coverage or 0
end