26 | function CrabSteering.initSpecialization() |
27 | local schema = Vehicle.xmlSchema |
28 | schema:setXMLSpecializationType("CrabSteering") |
29 | |
30 | schema:register(XMLValueType.FLOAT, "vehicle.crabSteering#distFromCompJointToCenterOfBackWheels", "Distance from component joint to center of back wheels") |
31 | schema:register(XMLValueType.FLOAT, "vehicle.crabSteering#aiSteeringModeIndex", "AI steering mode index", 1) |
32 | schema:register(XMLValueType.FLOAT, "vehicle.crabSteering#toggleSpeedFactor", "Toggle speed factor", 1) |
33 | |
34 | CrabSteering.registerSteeringModeXMLPaths(schema, "vehicle.crabSteering.steeringMode(?)") |
35 | CrabSteering.registerSteeringModeXMLPaths(schema, "vehicle.crabSteering.crabSteeringConfiguration(?).steeringMode(?)") |
36 | |
37 | Dashboard.registerDashboardXMLPaths(schema, "vehicle.crabSteering.dashboards", "state") |
38 | schema:register(XMLValueType.VECTOR_N, "vehicle.crabSteering.dashboards.dashboard(?)#states", "Crab steering states which activate the dashboard") |
39 | |
40 | schema:register(XMLValueType.INT, "vehicle.wheels.wheelConfigurations.wheelConfiguration(?).wheels#crabSteeringIndex", "Crab steering configuration index") |
41 | |
42 | schema:setXMLSpecializationType() |
43 | |
44 | local schemaSavegame = Vehicle.xmlSchemaSavegame |
45 | schemaSavegame:register(XMLValueType.INT, "vehicles.vehicle(?).crabSteering#state", "Current steering mode", 1) |
46 | end |
99 | function CrabSteering:onLoad(savegame) |
100 | local spec = self.spec_crabSteering |
101 | |
102 | spec.state = 1 |
103 | spec.stateMax = -1 |
104 | |
105 | spec.configurationIndex = spec.configurationIndex or 1 |
106 | |
107 | spec.distFromCompJointToCenterOfBackWheels = self.xmlFile:getValue("vehicle.crabSteering#distFromCompJointToCenterOfBackWheels") |
108 | spec.aiSteeringModeIndex = self.xmlFile:getValue("vehicle.crabSteering#aiSteeringModeIndex", 1) |
109 | spec.toggleSpeedFactor = self.xmlFile:getValue("vehicle.crabSteering#toggleSpeedFactor", 1) |
110 | |
111 | spec.currentArticulatedAxisOffset = 0 |
112 | spec.articulatedAxisOffsetChanged = false |
113 | spec.articulatedAxisLastAngle = 0 |
114 | spec.articulatedAxisChangingTime = 0 |
115 | |
116 | local baseKey = "vehicle.crabSteering" |
117 | local configKey = string.format("vehicle.crabSteering.crabSteeringConfiguration(%d)", spec.configurationIndex - 1) |
118 | if self.xmlFile:hasProperty(configKey) then |
119 | baseKey = configKey |
120 | end |
121 | |
122 | spec.steeringModes = {} |
123 | local i = 0 |
124 | while true do |
125 | local key = string.format("%s.steeringMode(%d)", baseKey, i) |
126 | if not self.xmlFile:hasProperty(key) then |
127 | break |
128 | end |
129 | |
130 | local entry = {} |
131 | entry.name = self.xmlFile:getValue(key .. "#name", "", self.customEnvironment, false) |
132 | |
133 | local inputBindingName = self.xmlFile:getValue(key .. "#inputBindingName") |
134 | if inputBindingName ~= nil then |
135 | if InputAction[inputBindingName] ~= nil then |
136 | entry.inputAction = InputAction[inputBindingName] |
137 | else |
138 | Logging.xmlWarning(self.xmlFile, "Invalid inputBindingname '%s' for '%s'", tostring(inputBindingName), key) |
139 | end |
140 | end |
141 | |
142 | entry.wheels = {} |
143 | local j = 0 |
144 | while true do |
145 | local wheelKey = string.format("%s.wheel(%d)", key, j) |
146 | if not self.xmlFile:hasProperty(wheelKey) then |
147 | break |
148 | end |
149 | local wheelEntry = {} |
150 | wheelEntry.wheelIndex = self.xmlFile:getValue(wheelKey .. "#index") |
151 | wheelEntry.wheelNode = self.xmlFile:getValue(wheelKey .. "#node", nil, self.components, self.i3dMappings) |
152 | if wheelEntry.wheelNode ~= nil then |
153 | local wheel = self:getWheelByWheelNode(wheelEntry.wheelNode) |
154 | if wheel ~= nil then |
155 | wheelEntry.wheelIndex = wheel.xmlIndex + 1 |
156 | else |
157 | Logging.xmlError(self.xmlFile, "Invalid wheel node '%s' for '%s'", self.xmlFile:getString(wheelKey .. "#node"), wheelKey) |
158 | end |
159 | end |
160 | |
161 | wheelEntry.offset = self.xmlFile:getValue(wheelKey .. "#offset", 0) |
162 | wheelEntry.locked = self.xmlFile:getValue(wheelKey .. "#locked", false) |
163 | |
164 | local wheels = self:getWheels() |
165 | if wheels[wheelEntry.wheelIndex] ~= nil then |
166 | wheels[wheelEntry.wheelIndex].steeringOffset = 0 |
167 | wheels[wheelEntry.wheelIndex].forceSteeringAngleUpdate = true |
168 | |
169 | wheels[wheelEntry.wheelIndex].rotSpeedBackUp = wheels[wheelEntry.wheelIndex].rotSpeed |
170 | else |
171 | Logging.xmlError(self.xmlFile, "Invalid wheelIndex '%s' for '%s'", tostring(wheelEntry.wheelIndex), wheelKey) |
172 | end |
173 | |
174 | table.insert(entry.wheels, wheelEntry) |
175 | j = j + 1 |
176 | end |
177 | |
178 | local specArticulatedAxis = self.spec_articulatedAxis |
179 | if specArticulatedAxis ~= nil and specArticulatedAxis.componentJoint ~= nil then |
180 | entry.articulatedAxis = {} |
181 | entry.articulatedAxis.rotSpeedBackUp = specArticulatedAxis.rotSpeed |
182 | entry.articulatedAxis.offset = self.xmlFile:getValue(key .. ".articulatedAxis#offset", 0) |
183 | entry.articulatedAxis.locked = self.xmlFile:getValue(key .. ".articulatedAxis#locked", false) |
184 | entry.articulatedAxis.wheelIndices = self.xmlFile:getValue(key .. ".articulatedAxis#wheelIndices", nil, true) |
185 | end |
186 | |
187 | entry.animations = {} |
188 | j = 0 |
189 | while true do |
190 | local animKey = string.format("%s.animation(%d)", key, j) |
191 | if not self.xmlFile:hasProperty(animKey) then |
192 | break |
193 | end |
194 | local animName = self.xmlFile:getValue(animKey .. "#name") |
195 | local animSpeed = self.xmlFile:getValue(animKey .. "#speed", 1.0) |
196 | local stopTime = self.xmlFile:getValue(animKey .. "#stopTime") |
197 | if animName ~= nil and self:getAnimationExists(animName) then |
198 | table.insert(entry.animations, {animName=animName, animSpeed=animSpeed, stopTime=stopTime}) |
199 | else |
200 | Logging.xmlWarning(self.xmlFile, "Invalid animation '%s' for '%s'", tostring(animName), animKey) |
201 | end |
202 | j = j + 1 |
203 | end |
204 | |
205 | table.insert(spec.steeringModes, entry) |
206 | i = i + 1 |
207 | end |
208 | |
209 | spec.stateMax = #spec.steeringModes |
210 | if spec.stateMax > ((2^CrabSteering.STEERING_SEND_NUM_BITS) - 1) then |
211 | Logging.xmlError(self.xmlFile, "CrabSteering only supports %d steering modes!", (2^CrabSteering.STEERING_SEND_NUM_BITS) - 1) |
212 | end |
213 | |
214 | spec.hasSteeringModes = spec.stateMax > 0 |
215 | |
216 | if spec.hasSteeringModes then |
217 | self.customSteeringAngleFunction = true |
218 | |
219 | self:setCrabSteering(1, true) |
220 | |
221 | if self.loadDashboardsFromXML ~= nil then |
222 | self:loadDashboardsFromXML(self.xmlFile, "vehicle.crabSteering.dashboards", {valueTypeToLoad = "state", |
223 | valueObject = self.spec_crabSteering, |
224 | valueFunc = "state", |
225 | additionalAttributesFunc = CrabSteering.dashboardCrabSteeringAttributes, |
226 | stateFunc = CrabSteering.dashboardCrabSteeringState}) |
227 | end |
228 | else |
229 | SpecializationUtil.removeEventListener(self, "onReadStream", CrabSteering) |
230 | SpecializationUtil.removeEventListener(self, "onWriteStream", CrabSteering) |
231 | SpecializationUtil.removeEventListener(self, "onReadUpdateStream", CrabSteering) |
232 | SpecializationUtil.removeEventListener(self, "onWriteUpdateStream", CrabSteering) |
233 | SpecializationUtil.removeEventListener(self, "onDraw", CrabSteering) |
234 | SpecializationUtil.removeEventListener(self, "onAIImplementStart", CrabSteering) |
235 | SpecializationUtil.removeEventListener(self, "onRegisterActionEvents", CrabSteering) |
236 | end |
237 | end |
588 | function CrabSteering:onRegisterActionEvents(isActiveForInput, isActiveForInputIgnoreSelection) |
589 | if self.isClient then |
590 | local spec = self.spec_crabSteering |
591 | if spec.hasSteeringModes then |
592 | self:clearActionEventsTable(spec.actionEvents) |
593 | |
594 | if isActiveForInputIgnoreSelection then |
595 | local _, actionEventId = self:addPoweredActionEvent(spec.actionEvents, InputAction.TOGGLE_CRABSTEERING, self, CrabSteering.actionEventToggleCrabSteeringModes, false, true, false, true, 1) |
596 | g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_NORMAL) |
597 | g_inputBinding:setActionEventText(actionEventId, string.format(g_i18n:getText("action_steeringModeToggle"), spec.steeringModes[spec.state].name)) |
598 | |
599 | for _, mode in pairs(spec.steeringModes) do |
600 | if mode.inputAction ~= nil then |
601 | _, actionEventId = self:addPoweredActionEvent(spec.actionEvents, mode.inputAction, self, CrabSteering.actionEventSetCrabSteeringMode, false, true, false, true, nil) |
602 | g_inputBinding:setActionEventTextVisibility(actionEventId, false) |
603 | g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_NORMAL) |
604 | end |
605 | end |
606 | |
607 | _, actionEventId = self:addPoweredActionEvent(spec.actionEvents, InputAction.TOGGLE_CRABSTEERING_BACK, self, CrabSteering.actionEventToggleCrabSteeringModes, false, true, false, true, -1) |
608 | g_inputBinding:setActionEventTextVisibility(actionEventId, false) |
609 | end |
610 | end |
611 | end |
612 | end |
50 | function CrabSteering.registerSteeringModeXMLPaths(schema, basePath) |
51 | schema:register(XMLValueType.L10N_STRING, basePath .. "#name", "Steering mode name") |
52 | schema:register(XMLValueType.STRING, basePath .. "#inputBindingName", "Input action name") |
53 | schema:register(XMLValueType.INT, basePath .. ".wheel(?)#index", "Wheel Index") |
54 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".wheel(?)#node", "Wheel Node") |
55 | schema:register(XMLValueType.ANGLE, basePath .. ".wheel(?)#offset", "Rotation offset", 0) |
56 | schema:register(XMLValueType.BOOL, basePath .. ".wheel(?)#locked", "Steering is locked", false) |
57 | |
58 | schema:register(XMLValueType.ANGLE, basePath .. ".articulatedAxis#offset", "Articulated axis offset angle", 0) |
59 | schema:register(XMLValueType.BOOL, basePath .. ".articulatedAxis#locked", "Articulated axis is locked", false) |
60 | schema:register(XMLValueType.VECTOR_N, basePath .. ".articulatedAxis#wheelIndices", "Wheel indices") |
61 | |
62 | schema:register(XMLValueType.STRING, basePath .. ".animation(?)#name", "Change animation name") |
63 | schema:register(XMLValueType.FLOAT, basePath .. ".animation(?)#speed", "Animation speed", 1) |
64 | schema:register(XMLValueType.FLOAT, basePath .. ".animation(?)#stopTime", "Animation stop time") |
65 | end |
324 | function CrabSteering:setCrabSteering(state, noEventSend) |
325 | local spec = self.spec_crabSteering |
326 | |
327 | if noEventSend == nil or noEventSend == false then |
328 | if g_server ~= nil then |
329 | g_server:broadcastEvent(SetCrabSteeringEvent.new(self, state), nil, nil, self) |
330 | else |
331 | g_client:getServerConnection():sendEvent(SetCrabSteeringEvent.new(self, state)) |
332 | end |
333 | end |
334 | |
335 | if state ~= spec.state then |
336 | local currentMode = spec.steeringModes[spec.state] |
337 | if currentMode.animations ~= nil then |
338 | for _,anim in pairs(currentMode.animations) do |
339 | local curTime = self:getAnimationTime(anim.animName) |
340 | if anim.stopTime == nil then |
341 | self:playAnimation(anim.animName, -anim.animSpeed, curTime, noEventSend) |
342 | end |
343 | end |
344 | end |
345 | local newMode = spec.steeringModes[state] |
346 | if newMode.animations ~= nil then |
347 | for _,anim in pairs(newMode.animations) do |
348 | local curTime = self:getAnimationTime(anim.animName) |
349 | if anim.stopTime ~= nil then |
350 | self:setAnimationStopTime(anim.animName, anim.stopTime) |
351 | local speed = 1.0 |
352 | if curTime > anim.stopTime then |
353 | speed = -1.0 |
354 | end |
355 | self:playAnimation(anim.animName, speed, curTime, noEventSend) |
356 | else |
357 | self:playAnimation(anim.animName, anim.animSpeed, curTime, noEventSend) |
358 | end |
359 | end |
360 | end |
361 | end |
362 | |
363 | spec.state = state |
364 | |
365 | local actionEvent = spec.actionEvents[InputAction.TOGGLE_CRABSTEERING] |
366 | if actionEvent ~= nil then |
367 | g_inputBinding:setActionEventText(actionEvent.actionEventId, string.format(g_i18n:getText("action_steeringModeToggle"), spec.steeringModes[spec.state].name)) |
368 | end |
369 | end |
452 | function CrabSteering:updateArticulatedAxisRotation(steeringAngle, dt) |
453 | local spec = self.spec_crabSteering |
454 | local specArticulatedAxis = self.spec_articulatedAxis |
455 | local specDriveable = self.spec_drivable |
456 | |
457 | if spec.stateMax == 0 then |
458 | return steeringAngle |
459 | end |
460 | |
461 | if not self.isServer then |
462 | return specArticulatedAxis.curRot |
463 | end |
464 | |
465 | local currentMode = spec.steeringModes[spec.state] |
466 | if currentMode.articulatedAxis == nil then |
467 | return steeringAngle |
468 | end |
469 | |
470 | -- |
471 | local rotScale = math.min(1.0/(self.lastSpeed*specDriveable.speedRotScale+specDriveable.speedRotScaleOffset), 1) |
472 | local delta = dt*0.001*self.autoRotateBackSpeed*rotScale * spec.toggleSpeedFactor |
473 | |
474 | if spec.currentArticulatedAxisOffset < currentMode.articulatedAxis.offset then |
475 | spec.currentArticulatedAxisOffset = math.min(currentMode.articulatedAxis.offset, spec.currentArticulatedAxisOffset + delta) |
476 | elseif spec.currentArticulatedAxisOffset > currentMode.articulatedAxis.offset then |
477 | spec.currentArticulatedAxisOffset = math.max(currentMode.articulatedAxis.offset, spec.currentArticulatedAxisOffset - delta) |
478 | end |
479 | |
480 | -- adjust rotSpeed |
481 | if currentMode.articulatedAxis.locked then |
482 | if specArticulatedAxis.rotSpeed > 0 then |
483 | specArticulatedAxis.rotSpeed = math.max(0, specArticulatedAxis.rotSpeed - delta) |
484 | elseif specArticulatedAxis.rotSpeed < 0 then |
485 | specArticulatedAxis.rotSpeed = math.min(0, specArticulatedAxis.rotSpeed + delta) |
486 | end |
487 | else |
488 | if specArticulatedAxis.rotSpeed > currentMode.articulatedAxis.rotSpeedBackUp then |
489 | specArticulatedAxis.rotSpeed = math.max(currentMode.articulatedAxis.rotSpeedBackUp, specArticulatedAxis.rotSpeed - delta) |
490 | elseif specArticulatedAxis.rotSpeed < currentMode.articulatedAxis.rotSpeedBackUp then |
491 | specArticulatedAxis.rotSpeed = math.min(currentMode.articulatedAxis.rotSpeedBackUp, specArticulatedAxis.rotSpeed + delta) |
492 | end |
493 | end |
494 | |
495 | local rotSpeed |
496 | if (self.rotatedTime) * (currentMode.articulatedAxis.rotSpeedBackUp) > 0 then |
497 | rotSpeed = (specArticulatedAxis.rotMax - spec.currentArticulatedAxisOffset) / self.wheelSteeringDuration |
498 | else |
499 | rotSpeed = (specArticulatedAxis.rotMin - spec.currentArticulatedAxisOffset) / self.wheelSteeringDuration |
500 | end |
501 | |
502 | local f = math.abs(specArticulatedAxis.rotSpeed) / math.abs(currentMode.articulatedAxis.rotSpeedBackUp) |
503 | rotSpeed = rotSpeed * f |
504 | |
505 | steeringAngle = spec.currentArticulatedAxisOffset + (math.abs(self.rotatedTime) * rotSpeed) |
506 | |
507 | -- change rotation just if wheels are moving (so you don't have to steer in the opposite direction while turning on crab steering) |
508 | if table.getn(currentMode.articulatedAxis.wheelIndices) > 0 and spec.distFromCompJointToCenterOfBackWheels ~= nil and self.movingDirection >= 0 then |
509 | local wheels = self:getWheels() |
510 | |
511 | local curRot = MathUtil.sign(currentMode.articulatedAxis.rotSpeedBackUp) * specArticulatedAxis.curRot |
512 | |
513 | local alpha = 0 |
514 | local count = 0 |
515 | for _,wheelIndex in pairs(currentMode.articulatedAxis.wheelIndices) do |
516 | alpha = alpha + wheels[wheelIndex].steeringAngle |
517 | count = count + 1 |
518 | end |
519 | alpha = alpha / count |
520 | alpha = alpha - curRot |
521 | |
522 | local v = 0 |
523 | count = 0 |
524 | for _,wheelIndex in pairs(currentMode.articulatedAxis.wheelIndices) do |
525 | local wheel = wheels[wheelIndex] |
526 | local axleSpeed = getWheelShapeAxleSpeed(wheel.node, wheel.wheelShape) -- rad/sec |
527 | if wheel.hasGroundContact then |
528 | local longSlip, _ = getWheelShapeSlip(wheel.node, wheel.wheelShape) |
529 | local fac = 1.0 - math.min(1.0, longSlip) |
530 | v = v + fac * axleSpeed * wheel.radius |
531 | count = count + 1 |
532 | end |
533 | end |
534 | v = v / count |
535 | local h = v * 0.001 * dt |
536 | local g = math.sin(alpha) * h |
537 | local a = math.cos(alpha) * h |
538 | local ls = spec.distFromCompJointToCenterOfBackWheels |
539 | local beta = math.atan2(g, ls - a) |
540 | |
541 | steeringAngle = MathUtil.sign(currentMode.articulatedAxis.rotSpeedBackUp) * (curRot + beta) |
542 | |
543 | spec.articulatedAxisOffsetChanged = true |
544 | spec.articulatedAxisLastAngle = steeringAngle |
545 | else |
546 | local changingTime = spec.articulatedAxisChangingTime |
547 | if spec.articulatedAxisOffsetChanged then |
548 | changingTime = 2500 |
549 | spec.articulatedAxisOffsetChanged = false |
550 | end |
551 | |
552 | --smooth blending if steering change is from crab to normal |
553 | if changingTime > 0 then |
554 | local pos = changingTime / 2500 |
555 | steeringAngle = steeringAngle * (1-pos) + spec.articulatedAxisLastAngle * pos |
556 | spec.articulatedAxisChangingTime = changingTime - dt |
557 | end |
558 | end |
559 | |
560 | steeringAngle = math.max(specArticulatedAxis.rotMin, math.min(specArticulatedAxis.rotMax, steeringAngle)) |
561 | |
562 | return steeringAngle |
563 | end |
377 | function CrabSteering:updateSteeringAngle(superFunc, wheel, dt, steeringAngle) |
378 | local spec = self.spec_crabSteering |
379 | local specDriveable = self.spec_drivable |
380 | |
381 | if spec.stateMax == 0 then |
382 | return superFunc(self, wheel, dt, steeringAngle) |
383 | end |
384 | |
385 | local currentMode = spec.steeringModes[spec.state] |
386 | for i=1, #currentMode.wheels do |
387 | local wheelProperties = currentMode.wheels[i] |
388 | if wheelProperties.wheelIndex == wheel.xmlIndex + 1 then |
389 | local rotScale = math.min(1.0/(self.lastSpeed*specDriveable.speedRotScale+specDriveable.speedRotScaleOffset), 1) |
390 | local delta = dt*0.001*self.autoRotateBackSpeed*rotScale * spec.toggleSpeedFactor |
391 | |
392 | if wheel.steeringOffset < wheelProperties.offset then |
393 | wheel.steeringOffset = math.min(wheelProperties.offset, wheel.steeringOffset + delta) |
394 | elseif wheel.steeringOffset > wheelProperties.offset then |
395 | wheel.steeringOffset = math.max(wheelProperties.offset, wheel.steeringOffset - delta) |
396 | end |
397 | |
398 | if not wheelProperties.locked then |
399 | local rotSpeed |
400 | if self.rotatedTime > 0 then |
401 | rotSpeed = (wheel.rotMax - wheel.steeringOffset) / self.wheelSteeringDuration |
402 | if wheel.rotSpeedBackUp < 0 then |
403 | rotSpeed = (wheel.rotMin - wheel.steeringOffset) / self.wheelSteeringDuration |
404 | end |
405 | else |
406 | rotSpeed = -(wheel.rotMin - wheel.steeringOffset) / self.wheelSteeringDuration |
407 | if wheel.rotSpeedBackUp < 0 then |
408 | rotSpeed = -(wheel.rotMax - wheel.steeringOffset) / self.wheelSteeringDuration |
409 | end |
410 | end |
411 | |
412 | if wheel.rotSpeed < wheel.rotSpeedBackUp then |
413 | wheel.rotSpeed = math.min(wheel.rotSpeedBackUp, wheel.rotSpeed + delta) |
414 | elseif wheel.rotSpeed > wheel.rotSpeedBackUp then |
415 | wheel.rotSpeed = math.max(wheel.rotSpeedBackUp, wheel.rotSpeed - delta) |
416 | end |
417 | local f = wheel.rotSpeed / wheel.rotSpeedBackUp |
418 | |
419 | steeringAngle = wheel.steeringOffset + (self.rotatedTime * f * rotSpeed) |
420 | else |
421 | if wheel.steeringAngle > wheel.steeringOffset or steeringAngle > wheel.steeringOffset then |
422 | steeringAngle = math.max(wheel.steeringOffset, math.min(wheel.steeringAngle, steeringAngle) - delta) |
423 | elseif wheel.steeringAngle < wheel.steeringOffset or steeringAngle < wheel.steeringOffset then |
424 | steeringAngle = math.min(wheel.steeringOffset, math.max(wheel.steeringAngle, steeringAngle) + delta) |
425 | end |
426 | |
427 | if steeringAngle == wheel.steeringOffset then |
428 | wheel.rotSpeed = 0 |
429 | else |
430 | if wheel.rotSpeed < 0 then |
431 | wheel.rotSpeed = math.min(0, wheel.rotSpeed + delta) |
432 | elseif wheel.rotSpeed > 0 then |
433 | wheel.rotSpeed = math.max(0, wheel.rotSpeed - delta) |
434 | end |
435 | end |
436 | end |
437 | |
438 | steeringAngle = MathUtil.clamp(steeringAngle, wheel.rotMin, wheel.rotMax) |
439 | |
440 | break |
441 | end |
442 | end |
443 | |
444 | return steeringAngle |
445 | end |