220 | function PlaceableAI:loadAIObstacle(xmlFile, key, obstacle) |
221 | obstacle.node = xmlFile:getValue(key .. "#node", nil, self.components, self.i3dMappings) |
222 | if obstacle.node == nil then |
223 | Logging.xmlWarning(xmlFile, "Obstacle '%s' node does not exist", key) |
224 | return false |
225 | end |
226 | |
227 | obstacle.size = xmlFile:getValue(key .. "#size", "0 0 0", true) |
228 | obstacle.offset = xmlFile:getValue(key .. "#offset", "0 0 0", true) |
229 | |
230 | -- size can only be omitted if node is a rigid body / has a collision box |
231 | if getRigidBodyType(obstacle.node) == RigidBodyType.NONE and obstacle.size[1] == 0 then |
232 | Logging.xmlWarning(xmlFile, "Obstacle '%s' is not a rigid body and needs a size", key) |
233 | return false |
234 | end |
235 | |
236 | obstacle.animatedObjectIndex = xmlFile:getValue(key .. ".animatedObject#index") |
237 | if obstacle.animatedObjectIndex == nil then |
238 | Logging.xmlWarning(xmlFile, "Obstacle '%s' is missing animated object index", key) |
239 | return false |
240 | end |
241 | |
242 | obstacle.animatedObjectTimeStart = xmlFile:getValue(key .. ".animatedObject#startTime") |
243 | obstacle.animatedObjectTimeEnd = xmlFile:getValue(key .. ".animatedObject#endTime") |
244 | |
245 | if obstacle.animatedObjectTimeStart == nil or obstacle.animatedObjectTimeEnd == nil then |
246 | Logging.xmlWarning(xmlFile, "Obstacle '%s' is missing start or end time", key) |
247 | return false |
248 | end |
249 | |
250 | if obstacle.animatedObjectTimeStart >= obstacle.animatedObjectTimeEnd then |
251 | Logging.xmlWarning(xmlFile, "Obstacle '%s' start time need to be smaller than end time", key) |
252 | return false |
253 | end |
254 | |
255 | obstacle.isActive = false |
256 | |
257 | return true |
258 | end |
199 | function PlaceableAI:loadAISpline(xmlFile, key, spline) |
200 | local splineNode = xmlFile:getValue(key .. "#node", nil, self.components, self.i3dMappings) |
201 | |
202 | if splineNode == nil then |
203 | return false |
204 | end |
205 | |
206 | setVisibility(splineNode, false) |
207 | |
208 | local maxWidth = xmlFile:getValue(key .. "#maxWidth") |
209 | local maxTurningRadius = xmlFile:getValue(key .. "#maxTurningRadius") |
210 | |
211 | spline.splineNode = splineNode |
212 | spline.maxWidth = maxWidth |
213 | spline.maxTurningRadius = maxTurningRadius |
214 | |
215 | return true |
216 | end |
162 | function PlaceableAI:loadAIUpdateArea(xmlFile, key, area) |
163 | local startNode = xmlFile:getValue(key .. "#startNode", nil, self.components, self.i3dMappings) |
164 | local endNode = xmlFile:getValue(key .. "#endNode", nil, self.components, self.i3dMappings) |
165 | |
166 | if startNode == nil then |
167 | Logging.xmlWarning(xmlFile, "Missing ai update area start node for '%s'", key) |
168 | return false |
169 | end |
170 | |
171 | if endNode == nil then |
172 | Logging.xmlWarning(xmlFile, "Missing ai update area end node for '%s'", key) |
173 | return false |
174 | end |
175 | |
176 | |
177 | local startX, _, startZ = localToLocal(startNode, self.rootNode, 0, 0, 0) |
178 | local endX, _, endZ = localToLocal(endNode, self.rootNode, 0, 0, 0) |
179 | |
180 | local sizeX = math.abs(endX - startX) |
181 | local sizeZ = math.abs(endZ - startZ) |
182 | |
183 | area.center = {} |
184 | area.center.x = (endX + startX) * 0.5 |
185 | area.center.z = (endZ + startZ) * 0.5 |
186 | |
187 | area.size = {} |
188 | area.size.x = sizeX |
189 | area.size.z = sizeZ |
190 | |
191 | area.startNode = startNode |
192 | area.endNode = endNode |
193 | |
194 | return true |
195 | end |
132 | function PlaceableAI:onDelete() |
133 | if self.isServer then |
134 | local spec = self.spec_ai |
135 | if spec.updateAreaOnDelete then |
136 | self:updateAIUpdateAreas() |
137 | end |
138 | |
139 | if g_currentMission.aiSystem ~= nil then |
140 | if spec.splines ~= nil then |
141 | for _, spline in pairs(spec.splines) do |
142 | g_currentMission.aiSystem:removeRoadSpline(spline.splineNode) |
143 | end |
144 | end |
145 | |
146 | if self.spec_animatedObjects ~= nil and g_currentMission.aiSystem.navigationMap ~= nil then |
147 | local animatedObjects = self.spec_animatedObjects.animatedObjects |
148 | if animatedObjects ~= nil then |
149 | for _, obstacle in ipairs(spec.obstacles) do |
150 | if obstacle.isActive then |
151 | self:setObstacleActive(obstacle, false) |
152 | end |
153 | end |
154 | end |
155 | end |
156 | end |
157 | end |
158 | end |
262 | function PlaceableAI:onFinalizePlacement() |
263 | if self.isServer then |
264 | local spec = self.spec_ai |
265 | local missionInfo = g_currentMission.missionInfo |
266 | |
267 | spec.updateAreaOnDelete = true |
268 | |
269 | if not self.isLoadedFromSavegame or not missionInfo.isValid then |
270 | self:updateAIUpdateAreas() |
271 | end |
272 | |
273 | if g_currentMission.aiSystem ~= nil then |
274 | for _, spline in ipairs(spec.splines) do |
275 | g_currentMission.aiSystem:addRoadSpline(spline.splineNode, spline.maxWidth, spline.maxTurningRadius) |
276 | end |
277 | |
278 | -- explicitly update state once |
279 | if g_currentMission.aiSystem.navigationMap ~= nil then |
280 | for _, obstacle in ipairs(spec.obstacles) do |
281 | if obstacle.animatedObject ~= nil then |
282 | obstacle.updateState(nil, obstacle.animatedObject.animation.time) |
283 | end |
284 | end |
285 | end |
286 | end |
287 | end |
288 | end |
64 | function PlaceableAI:onLoad(savegame) |
65 | local spec = self.spec_ai |
66 | local xmlFile = self.xmlFile |
67 | |
68 | spec.updateAreaOnDelete = false |
69 | |
70 | spec.areas = {} |
71 | xmlFile:iterate("placeable.ai.updateAreas.updateArea", function(_, key) |
72 | local area = {} |
73 | if self:loadAIUpdateArea(xmlFile, key, area) then |
74 | table.insert(spec.areas, area) |
75 | end |
76 | end) |
77 | |
78 | if not self.xmlFile:hasProperty("placeable.ai.updateAreas") then |
79 | Logging.xmlWarning(self.xmlFile, "Missing ai update areas") |
80 | end |
81 | |
82 | spec.splines = {} |
83 | xmlFile:iterate("placeable.ai.splines.spline", function(_, key) |
84 | local spline = {} |
85 | if self:loadAISpline(xmlFile, key, spline) then |
86 | table.insert(spec.splines, spline) |
87 | end |
88 | end) |
89 | |
90 | spec.obstacles = {} |
91 | xmlFile:iterate("placeable.ai.obstacles.obstacle", function(_, key) |
92 | local obstacle = {} |
93 | if self:loadAIObstacle(xmlFile, key, obstacle) then |
94 | table.insert(spec.obstacles, obstacle) |
95 | end |
96 | end) |
97 | end |
101 | function PlaceableAI:onPostLoad(savegame) |
102 | local spec = self.spec_ai |
103 | if self.spec_animatedObjects ~= nil and g_currentMission.aiSystem ~= nil and g_currentMission.aiSystem.navigationMap ~= nil then |
104 | local animatedObjects = self.spec_animatedObjects.animatedObjects |
105 | |
106 | for obstacleIndex, obstacle in ipairs(spec.obstacles) do |
107 | local animatedObject = animatedObjects[obstacle.animatedObjectIndex] |
108 | if animatedObject == nil then |
109 | Logging.warning("Placeable AI obstacle %d: AnimatedObject with index %d does not exist", obstacleIndex, obstacle.animatedObjectIndex) |
110 | else |
111 | obstacle.animatedObject = animatedObject |
112 | obstacle.updateState = function(_, t, omitSound) |
113 | if t >= obstacle.animatedObjectTimeStart and t <= obstacle.animatedObjectTimeEnd then |
114 | if not obstacle.isActive then |
115 | self:setObstacleActive(obstacle, true) |
116 | end |
117 | else |
118 | if obstacle.isActive then |
119 | self:setObstacleActive(obstacle, false) |
120 | end |
121 | end |
122 | end |
123 | -- hook into setAnimTime to add and remove AI obstacle in animation |
124 | animatedObject.setAnimTime = Utils.appendedFunction(animatedObject.setAnimTime, obstacle.updateState) |
125 | end |
126 | end |
127 | end |
128 | end |
43 | function PlaceableAI.registerXMLPaths(schema, basePath) |
44 | schema:setXMLSpecializationType("AI") |
45 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".ai.updateAreas.updateArea(?)#startNode", "Start node of ai update area") |
46 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".ai.updateAreas.updateArea(?)#endNode", "End node of ai update area") |
47 | |
48 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".ai.splines.spline(?)#node", "Spline node or transform group containing splines. Spline direction not relevant") |
49 | schema:register(XMLValueType.FLOAT, basePath .. ".ai.splines.spline(?)#maxWidth", "Maximum vehicle width supported by the spline") |
50 | schema:register(XMLValueType.FLOAT, basePath .. ".ai.splines.spline(?)#maxTurningRadius", "Maxmium vehicle turning supported by the spline") |
51 | |
52 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".ai.obstacles.obstacle(?)#node", "Node to be used for obstacle box", nil, true) |
53 | schema:register(XMLValueType.VECTOR_3, basePath .. ".ai.obstacles.obstacle(?)#size", "Obstacle box size as x y z vector, required if node is not a rigid body") |
54 | schema:register(XMLValueType.VECTOR_3, basePath .. ".ai.obstacles.obstacle(?)#offset", "Obstacle box offset to node as x y z vector") |
55 | schema:register(XMLValueType.INT, basePath .. ".ai.obstacles.obstacle(?).animatedObject#index", "Index of corresponding animated object in xml", nil, true) |
56 | schema:register(XMLValueType.FLOAT, basePath .. ".ai.obstacles.obstacle(?).animatedObject#startTime", "Normalized start time to activate obstacle", nil, true) |
57 | schema:register(XMLValueType.FLOAT, basePath .. ".ai.obstacles.obstacle(?).animatedObject#endTime", "Normalized end time to deactivate obstacle", nil, true) |
58 | schema:setXMLSpecializationType() |
59 | end |
292 | function PlaceableAI:setObstacleActive(obstacle, active) |
293 | if active then |
294 | local sx, sy, sz = obstacle.size[1],obstacle.size[2],obstacle.size[3] |
295 | local ox, oy, oz = obstacle.offset[1],obstacle.offset[2],obstacle.offset[3] |
296 | addVehicleNavigationPhysicsObstacle(g_currentMission.aiSystem.navigationMap, obstacle.node, ox,oy,oz, sx, sy, sz, 0) |
297 | setVehicleNavigationPhysicsObstacleIsPassable(g_currentMission.aiSystem.navigationMap, obstacle.node, false) |
298 | else |
299 | removeVehicleNavigationPhysicsObstacle(g_currentMission.aiSystem.navigationMap, obstacle.node) |
300 | end |
301 | obstacle.isActive = active |
302 | end |
306 | function PlaceableAI:updateAIUpdateAreas() |
307 | if self.isServer then |
308 | local spec = self.spec_ai |
309 | for _, area in pairs(spec.areas) do |
310 | local x, z = area.center.x, area.center.z |
311 | local sizeX, sizeZ = area.size.x, area.size.z |
312 | |
313 | local x1, _, z1 = localToWorld(self.rootNode, x + sizeX * 0.5, 0, z + sizeZ * 0.5) |
314 | local x2, _, z2 = localToWorld(self.rootNode, x - sizeX * 0.5, 0, z + sizeZ * 0.5) |
315 | local x3, _, z3 = localToWorld(self.rootNode, x + sizeX * 0.5, 0, z - sizeZ * 0.5) |
316 | local x4, _, z4 = localToWorld(self.rootNode, x - sizeX * 0.5, 0, z - sizeZ * 0.5) |
317 | |
318 | local minX = math.min(x1, x2, x3, x4) |
319 | local maxX = math.max(x1, x2, x3, x4) |
320 | local minZ = math.min(z1, z2, z3, z4) |
321 | local maxZ = math.max(z1, z2, z3, z4) |
322 | g_currentMission.aiSystem:setAreaDirty(minX, maxX, minZ, maxZ) |
323 | end |
324 | end |
325 | end |