194 lines
9.5 KiB
Lua
194 lines
9.5 KiB
Lua
-- AD Hitch Creep
|
|
-- Bak de sidste meter hen til vognen: når AD "Kør til" når en Connect-markør, og vognens trækøje står 3-8 m
|
|
-- lige bag traktorens krog, lægges en lille bakke-opgave i AD's kø før stop. Traktoren bakker i krybefart og
|
|
-- standser, så snart spillet ser vognen inden for koblingsrækkevidde; ADHitch kobler derefter på som før.
|
|
-- AD's egen bak-kollisionssensor bruges ikke: den ser vognen selv som forhindring.
|
|
|
|
ADHitchCreep = {}
|
|
ADHitchCreep.LOG_PREFIX = "ADSmartPickup: kobling: "
|
|
ADHitchCreep.ROOMY_ATTACH_DISTANCE = 3 -- m: herinden for kobler ADHitch direkte (samme som ADHitch.ROOMY_ATTACH_DISTANCE)
|
|
ADHitchCreep.CREEP_MAX_DISTANCE = 8 -- m: længere væk end det bakkes ikke — markøren ligger forkert
|
|
ADHitchCreep.CREEP_MAX_LATERAL = 1.5 -- m til siden: mere end det rammer en lige bakning ikke trækøjet
|
|
ADHitchCreep.CREEP_TIMEOUT_MS = 25000
|
|
ADHitchCreep.CREEP_SPEED = 2 -- km/t
|
|
ADHitchCreep.AWAY_TOLERANCE = 1 -- m: længere væk end ved start = den bakker forkert
|
|
ADHitchCreep.RANGE_CHECK_MS = 250
|
|
|
|
-- ---- ren logik ----
|
|
|
|
-- offset = {x, z}: vognens trækøje i traktorens rum set fra krogen (z < 0 = bagved) -> "attach" | "creep" | "none"
|
|
function ADHitchCreep.plan(offset)
|
|
if offset == nil then return "none" end
|
|
local distance = math.sqrt(offset.x ^ 2 + offset.z ^ 2)
|
|
if distance <= ADHitchCreep.ROOMY_ATTACH_DISTANCE then return "attach" end
|
|
if offset.z >= 0 or distance > ADHitchCreep.CREEP_MAX_DISTANCE or math.abs(offset.x) > ADHitchCreep.CREEP_MAX_LATERAL then
|
|
return "none"
|
|
end
|
|
return "creep"
|
|
end
|
|
|
|
-- s = {inRange, distance, startDistance, elapsedMs, passed} -> "done" | "drive" | "giveUp"
|
|
function ADHitchCreep.step(s)
|
|
if s.inRange then return "done" end
|
|
if (s.elapsedMs or 0) >= ADHitchCreep.CREEP_TIMEOUT_MS then return "giveUp" end
|
|
if s.passed then return "giveUp" end
|
|
if s.distance > s.startDistance + ADHitchCreep.AWAY_TOLERANCE then return "giveUp" end
|
|
return "drive"
|
|
end
|
|
|
|
-- ---- spil ----
|
|
|
|
local function worldPos(node)
|
|
local x, y, z = getWorldTranslation(node)
|
|
return {x = x, y = y, z = z}
|
|
end
|
|
|
|
local function vehicleName(vehicle)
|
|
return vehicle ~= nil and vehicle.getName ~= nil and vehicle:getName() or "?"
|
|
end
|
|
|
|
-- Traktorens ledige bagerste krog-noder.
|
|
local function freeRearJoints(vehicle)
|
|
local spec = vehicle.spec_attacherJoints
|
|
if spec == nil or spec.attacherJoints == nil or vehicle.rootNode == nil then return {} end
|
|
local used = {}
|
|
for _, implement in ipairs(spec.attachedImplements or {}) do
|
|
if implement.jointDescIndex ~= nil then used[implement.jointDescIndex] = true end
|
|
end
|
|
local joints = {}
|
|
for index, joint in ipairs(spec.attacherJoints) do
|
|
if not used[index] and joint.jointTransform ~= nil then
|
|
local p = worldPos(joint.jointTransform)
|
|
local _, _, lz = worldToLocal(vehicle.rootNode, p.x, p.y, p.z)
|
|
if lz < 0 then table.insert(joints, joint.jointTransform) end
|
|
end
|
|
end
|
|
return joints
|
|
end
|
|
|
|
-- Vognens trækøje i traktorens rum set fra krogen.
|
|
local function offsetOf(vehicle, jointNode, targetNode)
|
|
local j, t = worldPos(jointNode), worldPos(targetNode)
|
|
local x, _, z = worldDirectionToLocal(vehicle.rootNode, t.x - j.x, 0, t.z - j.z)
|
|
return {x = x, z = z}
|
|
end
|
|
|
|
-- Nærmeste frakoblede redskab (samme gård) med et trækøje bag en ledig krog. -> {wagon, joint, target, offset, plan} | nil
|
|
function ADHitchCreep.findTarget(vehicle)
|
|
local joints = freeRearJoints(vehicle)
|
|
if #joints == 0 or g_currentMission == nil or g_currentMission.vehicleSystem == nil then return nil end
|
|
local farmId = vehicle.getOwnerFarmId ~= nil and vehicle:getOwnerFarmId() or nil
|
|
local best, bestDistance = nil, math.huge
|
|
for _, object in pairs(g_currentMission.vehicleSystem.vehicles or {}) do
|
|
local attachable = object.spec_attachable
|
|
if object ~= vehicle and attachable ~= nil and object.spec_motorized == nil and attachable.inputAttacherJoints ~= nil
|
|
and (object.getAttacherVehicle == nil or object:getAttacherVehicle() == nil)
|
|
and (farmId == nil or object.getOwnerFarmId == nil or object:getOwnerFarmId() == farmId) then
|
|
for _, input in ipairs(attachable.inputAttacherJoints) do
|
|
if input.node ~= nil then
|
|
for _, joint in ipairs(joints) do
|
|
local offset = offsetOf(vehicle, joint, input.node)
|
|
local distance = math.sqrt(offset.x ^ 2 + offset.z ^ 2)
|
|
if distance < bestDistance and distance <= ADHitchCreep.CREEP_MAX_DISTANCE + 2 then
|
|
bestDistance = distance
|
|
best = {wagon = object, joint = joint, target = input.node, offset = offset}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if best ~= nil then best.plan = ADHitchCreep.plan(best.offset) end
|
|
return best
|
|
end
|
|
|
|
local function isInRange(vehicle, wagon)
|
|
local spec = vehicle.spec_attacherJoints
|
|
if spec == nil or AttacherJoints == nil or AttacherJoints.updateVehiclesInAttachRange == nil then return false end
|
|
AttacherJoints.updateVehiclesInAttachRange(vehicle, ADHitchCreep.ROOMY_ATTACH_DISTANCE ^ 2, AttacherJoints.MAX_ATTACH_ANGLE, true)
|
|
local info = spec.attachableInfo
|
|
return info ~= nil and info.attachable == wagon
|
|
end
|
|
|
|
-- AD-opgave (samme metoder som AD's AbstractTask).
|
|
local CreepTask = {}
|
|
CreepTask.__index = CreepTask
|
|
|
|
function CreepTask.new(adEnv, vehicle, target)
|
|
return setmetatable({adEnv = adEnv, vehicle = vehicle, target = target, elapsedMs = 0, checkMs = 0, inRange = false}, CreepTask)
|
|
end
|
|
|
|
function CreepTask:setUp()
|
|
local offset = offsetOf(self.vehicle, self.target.joint, self.target.target)
|
|
self.startDistance = math.sqrt(offset.x ^ 2 + offset.z ^ 2)
|
|
Logging.info(ADHitchCreep.LOG_PREFIX .. "'%s' bakker %.1f m hen til '%s' (%.1f m til siden)", vehicleName(self.vehicle),
|
|
self.startDistance, vehicleName(self.target.wagon), offset.x)
|
|
end
|
|
|
|
function CreepTask:finish(action, distance)
|
|
local sdm = self.vehicle.ad.specialDrivingModule
|
|
if sdm ~= nil and sdm.stopVehicle ~= nil then sdm:stopVehicle() end
|
|
if action == "done" then
|
|
Logging.info(ADHitchCreep.LOG_PREFIX .. "'%s' står ved '%s' — kobler på", vehicleName(self.vehicle), vehicleName(self.target.wagon))
|
|
else
|
|
Logging.warning(ADHitchCreep.LOG_PREFIX .. "'%s' opgav at bakke hen til '%s' (%.1f m tilbage efter %d s)", vehicleName(self.vehicle),
|
|
vehicleName(self.target.wagon), distance, math.floor(self.elapsedMs / 1000))
|
|
end
|
|
local taskModule = self.adEnv.ADTaskModule
|
|
self.vehicle.ad.taskModule:setCurrentTaskFinished(taskModule ~= nil and taskModule.DONT_PROPAGATE or 1)
|
|
end
|
|
|
|
function CreepTask:update(dt)
|
|
self.elapsedMs = self.elapsedMs + dt
|
|
self.checkMs = self.checkMs + dt
|
|
if self.target.wagon.getAttacherVehicle ~= nil and self.target.wagon:getAttacherVehicle() ~= nil then
|
|
return self:finish("giveUp", 0) -- en anden har taget vognen
|
|
end
|
|
local offset = offsetOf(self.vehicle, self.target.joint, self.target.target)
|
|
local distance = math.sqrt(offset.x ^ 2 + offset.z ^ 2)
|
|
if self.checkMs >= ADHitchCreep.RANGE_CHECK_MS then
|
|
self.checkMs = 0
|
|
self.inRange = isInRange(self.vehicle, self.target.wagon)
|
|
end
|
|
local action = ADHitchCreep.step({inRange = self.inRange, distance = distance, startDistance = self.startDistance,
|
|
elapsedMs = self.elapsedMs, passed = offset.z > 0.5})
|
|
if action ~= "drive" then return self:finish(action, distance) end
|
|
|
|
local autoDrive, vehicle = self.adEnv.AutoDrive, self.vehicle
|
|
local p = worldPos(self.target.target)
|
|
local lx, lz = autoDrive.getDriveDirection(vehicle, p.x, p.y, p.z)
|
|
local sdm = vehicle.ad.specialDrivingModule
|
|
if sdm ~= nil and sdm.releaseVehicle ~= nil then sdm:releaseVehicle() end
|
|
if vehicle.startMotor ~= nil and not vehicle:getIsMotorStarted() and vehicle:getCanMotorRun() then vehicle:startMotor() end
|
|
-- som AD's ADSpecialDrivingModule:driveReverse (ikke-guidet): retning spejles ved bakning
|
|
autoDrive.driveInDirection(vehicle, dt, 30, 0.3, 0.2, 20, true, false, -lx, -lz, ADHitchCreep.CREEP_SPEED, 1)
|
|
end
|
|
|
|
function CreepTask:abort() end
|
|
function CreepTask:finished() end
|
|
function CreepTask:getInfoText() return "Bakker hen til vognen" end
|
|
function CreepTask:getI18nInfo() return "" end
|
|
function CreepTask:getExcludedVehiclesForCollisionCheck() return {self.target.wagon} end
|
|
|
|
local isInstalled = false
|
|
|
|
-- DriveToMode:handleFinishedTask lægger StopAndDisableADTask i køen; bakningen lægges ind lige før.
|
|
function ADHitchCreep.install(adEnv)
|
|
local mode = adEnv ~= nil and adEnv.DriveToMode or nil
|
|
if isInstalled or mode == nil or mode.handleFinishedTask == nil or ADHitch == nil or ADHitch.pendingAttach == nil then return end
|
|
isInstalled = true
|
|
local original = mode.handleFinishedTask
|
|
mode.handleFinishedTask = function(self)
|
|
local ok, err = pcall(function()
|
|
local job = self.driveToDestinationTask ~= nil and ADHitch.pendingAttach(self.vehicle) or nil
|
|
if job == nil or job.markerId ~= self.destinationID then return end
|
|
local target = ADHitchCreep.findTarget(self.vehicle)
|
|
if target == nil or target.plan ~= "creep" then return end
|
|
job.crept = true
|
|
self.vehicle.ad.taskModule:addTask(CreepTask.new(adEnv, self.vehicle, target))
|
|
end)
|
|
if not ok then Logging.warning(ADHitchCreep.LOG_PREFIX .. "bakning til vognen: %s", tostring(err)) end
|
|
return original(self)
|
|
end
|
|
end
|