File size: 5,575 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
DefineClass.CombatPath = {
	__parents = { "InitDone" },
	unit = false,
	start_pos = false,
	stance = false,
	ap = 0,
	move_modifier = 0,
	restrict_area = false,

	destinations = empty_table, -- map: packed_pos -> true (possibly occupied points are not present here)
	paths_ap = empty_table,           -- map: packed_pos -> cost ap
	paths_prev_pos = empty_table,     -- map: pos -> previous pos (start pos -> false)
	closest_free_pos = false,         -- closest not occupied pos
	destination_stances = false,      -- for merged combat path it holds the stance to arrive at that destination
}

function CombatPath:RebuildPaths(unit, ap, pos, stance, ignore_occupied, move_through_occupied, action_id)
	stance = stance or unit and unit.stance or "Standing"
	ap = ap or unit and unit.ActionPoints or 0

	self.unit = unit
	self.stance = stance
	self.ap = ap
	self.start_pos = pos or unit and unit:GetPos()
	if ap < 0 then
		self.destinations = nil
		self.paths_ap = nil
		self.paths_prev_pos = nil
		self.closest_free_pos = nil
		return
	end
	local consts = Presets.ConstDef["Action Point Costs"]
	local side = unit and unit.team and unit.team.side
	local player_controlled = (side == "player1" or side == "player2")

	local stance_modifier = 0
	if stance == "Crouch" then
		stance_modifier = consts["CrouchModifier"].value
	elseif stance == "Prone" then
		stance_modifier = consts["ProneModifier"].value
	end

	local tunnel_mask
	if side and side ~= "neutral" and unit.body_type == "Human" then
		if stance == "Prone" then
			tunnel_mask = player_controlled and const.TunnelMaskPlayerProne or const.TunnelMaskAIProne
		else
			tunnel_mask = player_controlled and const.TunnelMaskPlayerStanding or const.TunnelMaskAIStanding
		end
	end

	self.move_modifier = unit and unit:GetMoveModifier(stance, action_id) or 0
	local move_modifier = Max(-100, self.move_modifier)
	local walk_modifier = stance_modifier + move_modifier
	local vertical_move_modifier = 0
	if HasPerk(unit, "DeathFromAbove") then
		vertical_move_modifier = CharacterEffectDefs.DeathFromAbove:ResolveValue("vertical_cost_modifier")
	end
	local tunnel_param = {
		unit = unit,
		player_controlled = player_controlled,
		move_modifier = move_modifier,
		walk_modifier = walk_modifier,
		walk_stairs_modifier = Max(-100, walk_modifier + vertical_move_modifier),
		ladder_modifier = Max(-100, move_modifier + vertical_move_modifier),
		drop_down_modifier = Max(-100, move_modifier + vertical_move_modifier),
		climb_up_modifier = Max(-100, move_modifier + vertical_move_modifier),
	}
	local walk_cost = consts["Walk"].value * (100 + walk_modifier) / 100
	local avoid_mines = unit and not player_controlled

	self.destinations, self.paths_ap, self.paths_prev_pos, self.closest_free_pos = GetCombatPathPositions(unit, self.start_pos, ap, walk_cost, tunnel_param, tunnel_mask, self.restrict_area, ignore_occupied, move_through_occupied, avoid_mines)
end

function CombatPath:GetAP(pos, endStance)
	if not pos then return end
	local pos_type = type(pos)
	local ap, stance
	if pos_type == "number" then
		ap = self.paths_ap[pos]
		stance = self.stance_at_end or (self.destination_stances and self.destination_stances[pos])
		if endStance and stance and stance ~= endStance then
			return ap + GetStanceToStanceAP(stance, endStance)
		end
		return ap
	elseif pos_type == "table" then
		local min_cost
		for i, p in ipairs(pos) do
			local c = self:GetAP(p)
			if c and (not min_cost or c < min_cost) then
				min_cost = c
			end
		end
		return min_cost
	end
	
	pos = point_pack(pos)
	ap = self.paths_ap[pos]
	stance = self.stance_at_end or (self.destination_stances and self.destination_stances[pos])
	if ap and endStance and stance and stance ~= endStance then
		return ap + GetStanceToStanceAP(stance, endStance)
	end
	return ap
end

function CombatPath:GetCombatPathFromPos(pos)
	if not pos then return end
	local p = type(pos) == "number" and pos or point_pack(pos)
	local paths_prev_pos = self.paths_prev_pos
	if not paths_prev_pos[p] then
		return
	end
	local path = {}
	while p do
		table.insert(path, p)
		p = paths_prev_pos[p]
	end
	return path
end

function CombatPath:GetReachableMeleeRangePositions(target, check_occupied, min_ap)
	local list = GetMeleeRangePositions(self.unit, target, nil, check_occupied)
	local paths_ap = self.paths_ap
	for i = list and #list or 0, 1, -1 do
		local ap = paths_ap[list[i]]
		if not ap or min_ap and ap < min_ap or not self.destinations[list[i]] then
			table.remove(list, i)
		end
	end
	return list
end

function CombatPath:GetClosestMeleeRangePos(target, target_pos, check_free, interaction)
	local list = GetMeleeRangePositions(self.unit, target, target_pos, check_free)
	local paths_ap = self.paths_ap
	local closest, min_ap
	for i, packed_pos in ipairs(list) do
		local ap = paths_ap[packed_pos]
		if ap and (not min_ap or ap < min_ap) and (not check_free or self.destinations[packed_pos]) then
			if interaction or IsMeleeRangeTarget(self.unit, packed_pos, nil, target, target_pos) then
				closest = packed_pos
				min_ap = ap
			end
		end
	end
	if closest then
		return point(point_unpack(closest))
	end
end

function GetCombatPathLen(path, obj)
	local len = 0
	if #path > 0 then
		local x1, y1, z1 = point_unpack(path[1])
		for i = 2, #path do
			local x2, y2, z2 = point_unpack(path[i])
			len = len + GetLen(x2 - x1, y2 - y1, (z1 or z2) and (z2 or terrain.GetHeight(x2, y2)) - (z1 or terrain.GetHeight(x1, y1)) or 0)
			x1, y1, z1 = x2, y2, z2
		end
		if IsValid(obj) and obj:IsValidPos() then
			len = len + obj:GetVisualDist(x1, y1, z1)
		end
	end
	return len
end