179 | function VehicleSchemaDisplay:animateDocking(startX, startY, targetX, targetY, isDocking) |
180 | local sequence = TweenSequence.new(self) |
181 | local lateDockInstant = HUDDisplayElement.MOVE_ANIMATION_DURATION * 0.5 |
182 | if not isDocking then |
183 | sequence:addInterval(HUDDisplayElement.MOVE_ANIMATION_DURATION) -- synchronize with input help element |
184 | lateDockInstant = lateDockInstant + HUDDisplayElement.MOVE_ANIMATION_DURATION |
185 | end |
186 | |
187 | sequence:addTween(MultiValueTween.new(self.setPosition, {startX, startY}, {targetX, targetY}, HUDDisplayElement.MOVE_ANIMATION_DURATION)) |
188 | -- set docked state in the middle of the animation: |
189 | sequence:insertCallback(self.lateSetDocked, isDocking, lateDockInstant) |
190 | |
191 | sequence:start() |
192 | self.animation = sequence |
193 | end |
197 | function VehicleSchemaDisplay:collectVehicleSchemaDisplayOverlays(overlays, depth, vehicle, rootVehicle, parentOverlay, x, y, rotation, invertingX) |
198 | if vehicle.getAttachedImplements == nil then |
199 | return |
200 | end |
201 | |
202 | local attachedImplements = vehicle:getAttachedImplements() |
203 | for _, implement in pairs(attachedImplements) do |
204 | local object = implement.object |
205 | if object ~= nil and object.schemaOverlay ~= nil then |
206 | local selected = object:getIsSelected() |
207 | local turnedOn = object:getUseTurnedOnSchema() |
208 | local jointDesc = vehicle.schemaOverlay.attacherJoints[implement.jointDescIndex] |
209 | |
210 | if jointDesc ~= nil then |
211 | local invertX = invertingX ~= jointDesc.invertX |
212 | local overlay = self:getSchemaOverlayForState(object.schemaOverlay, true) |
213 | |
214 | local baseY = y + jointDesc.y * parentOverlay.height |
215 | local baseX |
216 | if invertX then |
217 | baseX = x + jointDesc.x * parentOverlay.width |
218 | else |
219 | baseX = x - overlay.width + (1 - jointDesc.x) * parentOverlay.width |
220 | end |
221 | |
222 | local rot = rotation + jointDesc.rotation |
223 | |
224 | local offsetX, offsetY |
225 | if invertX then |
226 | offsetX = -object.schemaOverlay.offsetX * overlay.width |
227 | else |
228 | offsetX = object.schemaOverlay.offsetX * overlay.width |
229 | end |
230 | |
231 | offsetY = object.schemaOverlay.offsetY * overlay.height |
232 | local rotatedX = offsetX * math.cos(rot) - offsetY * math.sin(rot) |
233 | local rotatedY = offsetX * math.sin(rot) + offsetY * math.cos(rot) |
234 | baseX = baseX - rotatedX |
235 | baseY = baseY - rotatedY |
236 | |
237 | local isLowered = object.getIsLowered ~= nil and object:getIsLowered(true) |
238 | if not isLowered then |
239 | local widthOffset, heightOffset = getNormalizedScreenValues(jointDesc.liftedOffsetX, jointDesc.liftedOffsetY) |
240 | baseX = baseX + widthOffset |
241 | baseY = baseY + heightOffset * 0.5 |
242 | end |
243 | |
244 | local additionalText = object:getAdditionalSchemaText() |
245 | |
246 | table.insert(overlays, { |
247 | overlay = overlay, |
248 | additionalText = additionalText, |
249 | x = baseX, |
250 | y = baseY, |
251 | rotation = rot, |
252 | invertX = not invertX, |
253 | invisibleBorderRight = object.schemaOverlay.invisibleBorderRight, |
254 | invisibleBorderLeft = object.schemaOverlay.invisibleBorderLeft, |
255 | selected = selected, |
256 | turnedOn = turnedOn |
257 | }) |
258 | |
259 | if depth <= VehicleSchemaDisplay.MAX_SCHEMA_COLLECTION_DEPTH then |
260 | self:collectVehicleSchemaDisplayOverlays(overlays, depth + 1, object, rootVehicle, overlay, baseX, baseY, rot, invertX) |
261 | end |
262 | end |
263 | end |
264 | end |
265 | end |
43 | function VehicleSchemaDisplay:delete() |
44 | VehicleSchemaDisplay:superClass().delete(self) |
45 | |
46 | if self.overlayFront ~= nil then |
47 | self.overlayFront:delete() |
48 | end |
49 | if self.overlayMiddle ~= nil then |
50 | self.overlayMiddle:delete() |
51 | end |
52 | if self.overlayBack ~= nil then |
53 | self.overlayBack:delete() |
54 | end |
55 | |
56 | for k, v in pairs(self.vehicleSchemaOverlays) do |
57 | v:delete() |
58 | self.vehicleSchemaOverlays[k] = nil |
59 | end |
60 | end |
328 | function VehicleSchemaDisplay:drawVehicleSchemaOverlays(vehicle) |
329 | vehicle = vehicle.rootVehicle |
330 | |
331 | if vehicle.schemaOverlay ~= nil then |
332 | local overlays, overlayHeight = self:getVehicleSchemaOverlays(vehicle) |
333 | |
334 | local x, y = self:getPosition() |
335 | local baseX, baseY = x, y |
336 | |
337 | baseY = baseY + (self:getHeight() - overlayHeight) * 0.5 -- vertically center icon base in panel |
338 | if self.isDocked then |
339 | baseX = baseX + self:getWidth() -- right-align when docked to input help |
340 | end |
341 | |
342 | local minX, maxX = self:getSchemaDelimiters(overlays) |
343 | |
344 | -- dynamically scale schemas if going over size limit: |
345 | local scale = 1 |
346 | local sizeX = maxX - minX |
347 | if sizeX > self.maxSchemaWidth then |
348 | scale = self.maxSchemaWidth / sizeX |
349 | end |
350 | |
351 | local barOffsetX = self:updateBarComponents(baseX, y, sizeX, self.overlay.height * self.uiScale, self.isDocked) |
352 | self.overlayFront:render() |
353 | self.overlayMiddle:render() |
354 | self.overlayBack:render() |
355 | |
356 | local newPosX = baseX |
357 | if self.isDocked then |
358 | newPosX = newPosX - maxX * scale - barOffsetX |
359 | else |
360 | newPosX = newPosX - minX * scale + barOffsetX |
361 | end |
362 | |
363 | for _, overlayDesc in pairs(overlays) do |
364 | local overlay = overlayDesc.overlay |
365 | local width, height = overlay.width, overlay.height |
366 | |
367 | overlay:setInvertX(overlayDesc.invertX) |
368 | overlay:setPosition(newPosX + overlayDesc.x, baseY + overlayDesc.y) |
369 | overlay:setRotation(overlayDesc.rotation, 0, 0) |
370 | overlay:setDimension(width * scale, height * scale) |
371 | |
372 | local color = overlayDesc.turnedOn and VehicleSchemaDisplay.COLOR.TURNED_ON or VehicleSchemaDisplay.COLOR.DEFAULT |
373 | overlay:setColor(color[1], color[2], color[3], overlayDesc.selected and 1 or 0.5) |
374 | |
375 | overlay:render() |
376 | |
377 | if overlayDesc.additionalText ~= nil then |
378 | local posX = newPosX + overlayDesc.x + (width * scale) * 0.5 |
379 | local posY = baseY + overlayDesc.y + (height * scale * 0.85) |
380 | setTextBold(false) |
381 | setTextColor(1, 1, 1, 1) |
382 | setTextAlignment(RenderText.ALIGN_CENTER) |
383 | renderText(posX, posY, getCorrectTextSize(0.008), overlayDesc.additionalText) |
384 | setTextAlignment(RenderText.ALIGN_LEFT) |
385 | setTextColor(1, 1, 1, 1) |
386 | end |
387 | |
388 | -- reset dimension |
389 | overlay:setDimension(width, height) |
390 | end |
391 | end |
392 | end |
300 | function VehicleSchemaDisplay:getSchemaDelimiters(overlayDescriptions) |
301 | local minX = math.huge |
302 | local maxX = -math.huge |
303 | for _, overlayDesc in pairs(overlayDescriptions) do |
304 | local overlay = overlayDesc.overlay |
305 | |
306 | local cosRot = math.cos(overlayDesc.rotation) |
307 | local sinRot = math.sin(overlayDesc.rotation) |
308 | |
309 | local offX = overlayDesc.invisibleBorderLeft * overlay.width |
310 | local dx = overlay.width + (overlayDesc.invisibleBorderRight + overlayDesc.invisibleBorderLeft) * overlay.width |
311 | |
312 | local dy = overlay.height |
313 | local x = overlayDesc.x + offX * cosRot |
314 | local dx2 = dx * cosRot |
315 | local dx3 = -dy * sinRot |
316 | local dx4 = dx2 + dx3 |
317 | |
318 | maxX = math.max(maxX, x, x + dx2, x + dx3, x + dx4) |
319 | minX = math.min(minX, x, x + dx2, x + dx3, x + dx4) |
320 | end |
321 | |
322 | return minX, maxX |
323 | end |
401 | function VehicleSchemaDisplay:getSchemaOverlayForState(schemaOverlayData, isImplement, iconOverride) |
402 | local schemaName |
403 | |
404 | schemaName = schemaOverlayData.schemaName |
405 | |
406 | -- Backwards compatibility |
407 | if schemaName == "DEFAULT_IMPLEMENT" then |
408 | schemaName = "IMPLEMENT" |
409 | elseif schemaName == "DEFAULT_VEHICLE" then |
410 | schemaName = "VEHICLE" |
411 | end |
412 | |
413 | if not schemaName or schemaName == "" or self.vehicleSchemaOverlays[schemaName] == nil then |
414 | schemaName = isImplement and VehicleSchemaOverlayData.SCHEMA_OVERLAY.IMPLEMENT or VehicleSchemaOverlayData.SCHEMA_OVERLAY.VEHICLE |
415 | end |
416 | |
417 | return self.vehicleSchemaOverlays[schemaName] |
418 | end |
271 | function VehicleSchemaDisplay:getVehicleSchemaOverlays(vehicle) |
272 | local overlay = self:getSchemaOverlayForState(vehicle.schemaOverlay, false) |
273 | local additionalText = vehicle:getAdditionalSchemaText() |
274 | local overlays = {} |
275 | |
276 | table.insert(overlays, { |
277 | overlay = overlay, |
278 | additionalText = additionalText, |
279 | x = 0, |
280 | y = 0, |
281 | rotation = 0, |
282 | invertX = false, |
283 | invisibleBorderRight = vehicle.schemaOverlay.invisibleBorderRight, |
284 | invisibleBorderLeft = vehicle.schemaOverlay.invisibleBorderLeft, |
285 | turnedOn = vehicle:getUseTurnedOnSchema(), |
286 | selected = vehicle:getIsSelected() |
287 | }) |
288 | |
289 | self:collectVehicleSchemaDisplayOverlays(overlays, 1, vehicle, vehicle, overlay, 0, 0, 0, false) |
290 | |
291 | return overlays, overlay.height |
292 | end |
84 | function VehicleSchemaDisplay:loadVehicleSchemaOverlaysFromXML(xmlFile, modPath) |
85 | local rootPath = "vehicleSchemaOverlays" |
86 | local baseDirectory = "" |
87 | local prefix = "" |
88 | if modPath then |
89 | rootPath = "modDesc.vehicleSchemaOverlays" |
90 | local modName, dir = Utils.getModNameAndBaseDirectory(modPath) |
91 | baseDirectory = dir |
92 | prefix = modName |
93 | end |
94 | |
95 | local atlasPath = getXMLString(xmlFile, rootPath .. "#filename") |
96 | local imageSize = GuiUtils.get2DArray(getXMLString(xmlFile, rootPath .. "#imageSize"), {1024, 1024}) |
97 | |
98 | local i = 0 |
99 | while true do |
100 | local baseName = string.format("%s.overlay(%d)", rootPath, i) |
101 | if not hasXMLProperty(xmlFile, baseName) then |
102 | break -- no more overlay definitions |
103 | end |
104 | |
105 | local baseOverlayName = getXMLString(xmlFile, baseName .. "#name") |
106 | local uvString = getXMLString(xmlFile, baseName .. "#uvs") or string.format("0px 0px %ipx %ipx", imageSize[1], imageSize[2]) |
107 | local uvs = GuiUtils.getUVs(uvString, imageSize) |
108 | |
109 | local sizeString = getXMLString(xmlFile, baseName .. "#size") or string.format("%ipx %ipx", |
110 | VehicleSchemaDisplay.SIZE.ICON[1], VehicleSchemaDisplay.SIZE.ICON[1]) |
111 | local size = GuiUtils.getNormalizedValues(sizeString, {1, 1}) -- remove pixel units but do not change numbers |
112 | |
113 | if baseOverlayName then |
114 | local overlayName = prefix .. baseOverlayName |
115 | |
116 | local atlasFileName = Utils.getFilename(atlasPath, baseDirectory) |
117 | local schemaOverlay = Overlay.new(atlasFileName, 0, 0, size[1], size[2]) -- store pixel size to be scaled later |
118 | schemaOverlay:setUVs(uvs) |
119 | |
120 | self.vehicleSchemaOverlays[overlayName] = schemaOverlay |
121 | end |
122 | |
123 | i = i + 1 |
124 | end |
125 | end |
22 | function VehicleSchemaDisplay.new(modManager) |
23 | local backgroundOverlay = VehicleSchemaDisplay.createBackground() |
24 | local self = VehicleSchemaDisplay:superClass().new(backgroundOverlay, nil, VehicleSchemaDisplay_mt) |
25 | |
26 | self:createBackgroundBar() |
27 | |
28 | self.modManager = modManager |
29 | |
30 | self.vehicle = nil -- currently controlled vehicle |
31 | self.isDocked = false -- If true, the schema display is docked to the input help display |
32 | self.vehicleSchemaOverlays = {} -- schema name -> overlay |
33 | |
34 | self.iconSizeX, self.iconSizeY = 0, 0 -- schema overlay icon size |
35 | self.maxSchemaWidth = 0 -- maximum width of vehicle configuration schema |
36 | |
37 | return self |
38 | end |
145 | function VehicleSchemaDisplay:setDocked(isDocked, animate) |
146 | local targetX, targetY = VehicleSchemaDisplay.getBackgroundPosition(isDocked, self:getScale()) |
147 | if animate and self.animation:getFinished() then |
148 | local startX, startY = self:getPosition() |
149 | |
150 | self:animateDocking(startX, startY, targetX, targetY, isDocked) |
151 | else |
152 | self.animation:stop() |
153 | self.isDocked = isDocked |
154 | self:setPosition(targetX, targetY) |
155 | end |
156 | end |
440 | function VehicleSchemaDisplay:storeScaledValues() |
441 | self.iconSizeX, self.iconSizeY = self:scalePixelToScreenVector(VehicleSchemaDisplay.SIZE.ICON) |
442 | self.maxSchemaWidth = self:scalePixelToScreenWidth(VehicleSchemaDisplay.MAX_SCHEMA_WIDTH) |
443 | |
444 | for _, overlay in pairs(self.vehicleSchemaOverlays) do |
445 | overlay:resetDimensions() |
446 | |
447 | local pixelSize = {overlay.defaultWidth, overlay.defaultHeight} |
448 | local width, height = self:scalePixelToScreenVector(pixelSize) |
449 | overlay:setDimension(width, height) |
450 | end |
451 | end |