forked from Courseplay/courseplay
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ShieldAIDriver.lua
197 lines (167 loc) · 8.57 KB
/
ShieldAIDriver.lua
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
--[[
This file is part of Courseplay (https://github.com/Courseplay/courseplay)
Copyright (C) 2021 Courseplay Dev team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
]]
---@class ShieldAIDriver : CompactingAIDriver
ShieldAIDriver = CpObject(CompactingAIDriver)
function ShieldAIDriver:start(startingPoint)
self.leveler = AIDriverUtil.getImplementWithSpecialization(self.vehicle, Leveler) or self.vehicle
self.hasFrontAttachedShield = self:isShieldAttachedAtFront()
CompactingAIDriver.start(self,startingPoint)
end
function ShieldAIDriver:drive(dt)
self:updateShieldHeight(dt)
CompactingAIDriver.drive(self,dt)
end
--- Updates shield height and rotation.
function ShieldAIDriver:updateShieldHeight(dt)
local shield = self.leveler
local spec = shield.spec_attacherJointControl
if shield and spec then
local jointDesc = spec.jointDesc
local objectAttacherJoint = shield.spec_attachable.attacherJoint
if self:isDrivingIntoSilo() then
local levelerNode = shield.spec_leveler.nodes[1].node
local x,y,z = getWorldTranslation(levelerNode)
local terrainHeight = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x,y,z)
---target height of leveling, fill up is 0 by default
local targetHeight = self:getTargetShieldHeight()
--safety check to make sure shieldHeightOffset ~= nil
if self.shieldHeightOffset == nil then
self.shieldHeightOffset = 0
end
self:updateShieldHeightOffset()
--get the height difference that needs to be adjusted form the shield leveler node to the ground
local heightDiff = terrainHeight+self.shieldHeightOffset+targetHeight-y
--[[ In the long term it should be safer to calculate the new alpha directly,
instead of adjusting the alpha by a constant.
This is currently not working as the shield then tends to toggle between going up and down repeatedly.
---Reference: AttacherJoints:calculateAttacherJointMoveUpperLowerAlpha(jointDesc, object)
local dx, dy, dz = localToLocal(jointDesc.jointTransform, jointDesc.rootNode, 0, 0, 0)
local delta = jointDesc.lowerDistanceToGround - dy
local ax,ay,az = localToLocal(jointDesc.jointTransform,levelerNode,0,heightDiff,0)
local hx, hy, hz = localToLocal(jointDesc.jointTransform, jointDesc.rootNode, ax, ay, az)
local lowerDistanceToGround = hy + delta
--calculate the target alpha
local alpha = MathUtil.clamp((lowerDistanceToGround - jointDesc.upperDistanceToGround) / (jointDesc.lowerDistanceToGround - jointDesc.upperDistanceToGround), 0, 1)
self:debug("lastCurAlpha: %.2f, nextAlpha: %.2f, heightDiff: %.2f",spec.lastHeightAlpha,alpha,heightDiff)
self:debug("terrainHeight: %.2f,shieldHeight: %.2f, shieldHeightOffset: %.2f, targetHeight: %.2f",terrainHeight,y,self.shieldHeightOffset,targetHeight)
]]--
-- self:debug("heightDiff: %.2f, shieldHeightOffset: %.2f, targetHeight: %.2f",heightDiff,self.shieldHeightOffset,targetHeight)
local curAlpha = spec.heightController.moveAlpha
--For now we are only adjusting the shield height by a constant
--heightDiff > -0.04 means we are under the target height, for example in fillUp modi below the ground offset by 0.04
if heightDiff > -0.04 then
spec.heightTargetAlpha = curAlpha - 0.05
--heightDiff < -0.12 means we are above the target height by 0.12, which also is used to minimize going up and down constantly
elseif heightDiff < -0.12 then
spec.heightTargetAlpha = curAlpha + 0.05
else
--shield is in valid height scope, so we stop all movement
spec.heightTargetAlpha =-1
end
--TODO: maybe change the shield tilt angle relative to the shield height alpha
--rotate shield to standing on ground position, should roughly be 90 degree to ground by default
--tilt the shield relative to the additional shield height offset
--added a factor of 2 to make sure the shield is getting tilted enough
local targetAngle = math.min(spec.maxTiltAngle*self.shieldHeightOffset*2,spec.maxTiltAngle)
self:controlShieldTilt(dt,jointDesc,spec.maxTiltAngle,targetAngle)
else
self.shieldHeightOffset = 0
spec.heightTargetAlpha = jointDesc.upperAlpha
--move shield to upperPosition and rotate it up
self:controlShieldTilt(dt,jointDesc,spec.maxTiltAngle,spec.maxTiltAngle)
end
end
end
--- Controls the tilt of the shield, as giants doesn't have implement a function for tilting the shield smoothly
---@param dt number
---@param jointDesc table of the vehicle
---@param maxTiltAngle number max tilt angle
---@param targetAngle number target tilt angle
function ShieldAIDriver:controlShieldTilt(dt,jointDesc,maxTiltAngle,targetAngle)
local curAngle = jointDesc.upperRotationOffset-jointDesc.upperRotationOffsetBackup
local diff = curAngle - targetAngle + 0.0001
local moveTime = diff / maxTiltAngle * jointDesc.moveTime
local moveStep = dt / moveTime * diff
if diff > 0 then
moveStep = -moveStep
end
local newAngle = targetAngle + moveStep/10
jointDesc.upperRotationOffset = jointDesc.upperRotationOffsetBackup - newAngle
jointDesc.lowerRotationOffset = jointDesc.lowerRotationOffsetBackup - newAngle
end
--- If the driver is slower than 2 km/h, then move the shield slowly up (increase self.shieldHeightOffset)
function ShieldAIDriver:updateShieldHeightOffset()
--- A small reduction to the offset, as the shield should be lifted after a only a bit silage.
local smallOffsetReduction = 0.3
local fillLevelPercentage = self:getShieldFillLevelPercentage()
self.shieldHeightOffset = MathUtil.clamp(fillLevelPercentage-smallOffsetReduction,0,1)
end
--- Is the shield full ?
---@return boolean shield is full
function ShieldAIDriver:isShieldFull()
local shield = self.leveler
return shield and shield:getFillUnitFillLevel(1)/shield:getFillUnitCapacity(1) > 0.98 or false
end
--- Gets the shield fill level percentage.
---@return number fillLevelPercentage
function ShieldAIDriver:getShieldFillLevelPercentage()
local shield = self.leveler
return shield:getFillUnitFillLevel(1)/shield:getFillUnitCapacity(1)
end
--- Overrides the lowering implements of the CompactingAIDriver.
function ShieldAIDriver:beforeDriveIntoSilo()
BunkerSiloAIDriver.beforeDriveIntoSilo(self)
end
--- Overrides the raising implements of the CompactingAIDriver.
function ShieldAIDriver:beforeDriveOutOfSilo()
BunkerSiloAIDriver.beforeDriveOutOfSilo(self)
end
--- Gets the target shield height, could be used for a possible LevelingAIDriver.
function ShieldAIDriver:getTargetShieldHeight()
return 0
end
function ShieldAIDriver:getTargetBunkerSiloMode()
return BunkerSiloManager.MODE.SHIELD
end
--- The target node for the silo is the leveler node, which is just in front of the driver.
function ShieldAIDriver:getTargetNode()
return self.leveler.spec_leveler.nodes[1].node
end
function ShieldAIDriver:getWorkWidth()
return math.max(CompactingAIDriver.getWorkWidth(self),3)
end
--- Overrides the player shield controls, while a cp driver is driving.
function ShieldAIDriver.actionEventAttacherJointControl(object,superFunc, actionName, inputValue, callbackState, isAnalog)
local rootVehicle = object:getRootVehicle()
if courseplay:isAIDriverActive(rootVehicle) then
return
end
superFunc(object,actionName, inputValue, callbackState, isAnalog)
end
AttacherJointControl.actionEventAttacherJointControl = Utils.overwrittenFunction(AttacherJointControl.actionEventAttacherJointControl,ShieldAIDriver.actionEventAttacherJointControl)
--- Is the shield attached at the front ?
--- make sure the logic gets inverted for reverse driving vehicles,
--- for example a tractor with a rotated cabin.
function ShieldAIDriver:isShieldAttachedAtFront()
local isReverseDriving = AIDriverUtil.isReverseDriving(self.vehicle)
local isAttachedAtFront = AIDriverUtil.isObjectAttachedOnTheFront(self.vehicle,self.leveler)
return isReverseDriving and not isAttachedAtFront or isAttachedAtFront
end
--- The direction from which to drive into the silo depends,
--- if the shield is attached at the front, then drive forwards into the silo
--- or if the shield is attached at the back, then drive backwards into the silo.
function ShieldAIDriver:isDriveDirectionReverse()
return not self.hasFrontAttachedShield
end