CCCPaste

Beamng 0.37 standard role lua

-- This Source Code Form is subject to the terms of the bCDDL, v. 1.1.
-- If a copy of the bCDDL was not distributed with this
-- file, You can obtain one at http://beamng.com/bCDDL-1.1.txt

local min = math.min
local max = math.max
local ceil = math.ceil
local random = math.random

local C = {}

function C:init()
  self.personalityModifiers = {}
  self.actions = {
    fleePostCrash = function (args)
      if self.veh.isAi then
        self.veh:setAiMode('flee')
        local obj = getObjectByID(self.veh.id)
        obj:queueLuaCommand('ai.driveInLane("off")')
        obj:queueLuaCommand('ai.setAggressionMode("off")')
        self:setAggression(args.aggression or max(0.5, random()))
      end
      self.state = 'flee'
      self.flags.askInsurance = nil
    end,
    followPostCrash = function (args)
      if self.veh.isAi then
        self.veh:setAiMode(args.isChase and 'chase' or 'follow')
        local obj = getObjectByID(self.veh.id)
        obj:queueLuaCommand('ai.driveInLane("off")')
        obj:queueLuaCommand('ai.setAggressionMode("off")')
        self:setAggression(args.aggression or max(0.5, random()))
      end
      self.state = 'follow'
      self.flags.askInsurance = nil
    end,
    askInsurance = function (args)
      if self.veh.isAi then
        self.veh:setAiMode('stop')
      end
      self.flags.askInsurance = nil
    end,
    disabled = function (args)
      if self.veh.isAi then
        self.veh:setAiMode('stop')
      end
      self.state = 'disabled'
    end
  }

  for k, v in pairs(self.baseActions) do
    self.actions[k] = v
  end
  self.baseActions = nil
end

function C:onRefresh()
  self.targetId = nil
  self.actionTimer = 0
  local personality = self.driver.personality

  self.driver.eventType = nil
  self.driver.damageAction = 'none'
  self.driver.damageThreshold = 1000
  self.driver.collisionThreshold = math.huge

  if self.veh.isAi then -- only calculates values if vehicle is AI
    self.driver.damageAction = 'stop'

    if personality.bravery >= 0.55 then
      self.driver.damageAction = 'followPostCrash'
    elseif personality.bravery <= 0.45 then
      self.driver.damageAction = 'fleePostCrash'
    end

    local squareRandom = square(random())
    self.driver.damageThreshold = max(self.veh.damageLimits[1], squareRandom * 1000) -- minimum damage that triggers damage action
    self.driver.collisionThreshold = ceil(squareRandom * 6) -- minimum number of collisions that triggers damage action
  end

  self.driver.selfHitCount = 0
  self.driver.otherHitCount = 0
  self.driver.enableAskInsurance = true
end

function C:onCrashDamage(data)
  -- triggers if self is currently not in a collision or witness to one
  if self.driver.eventType ~= 'selfCollision' and self.driver.eventType ~= 'otherCollision' then
    self.driver.eventType = 'selfCrash'
  end
end

function C:onOtherCrashDamage(otherId, data)
  -- triggers if self is currently not in a collision or witness to one
  if self.driver.eventType ~= 'selfCollision' and self.driver.eventType ~= 'otherCollision' then
    self.driver.eventType = 'otherCrash'
    self:setTarget(otherId)
  end
end

function C:onCollision(otherId, data)
  if self.veh.speed >= self.veh.tracking.speedLimit * 1.2 then -- speeding always means collision fault for self
    self.veh.collisions[otherId].fault = true
  end
  if self.driver.eventType ~= 'selfCollision' then -- overrides previous events
    self.driver.eventType = 'selfCollision'
    self:setTarget(otherId)
  end

  if self.veh.isAi and not self.flags.askInsurance and self.driver.personality.bravery >= 0.52 and random() >= 0.5 / clamp(data.count, 1, 3) then
    self.flags.honkHornDelay = 1
  end

  self.driver.selfHitCount = data.count
end

function C:onOtherCollision(id1, id2, data)
  if self.driver.eventType ~= 'selfCollision' then
    local targetVeh, secondVeh = gameplay_traffic.getTrafficData()[id1], gameplay_traffic.getTrafficData()[id2]
    if not targetVeh or not secondVeh then return end
    local targetId = targetVeh.speed < secondVeh.speed and id1 or id2 -- target the slower vehicle in collision
    if targetId == id2 then targetVeh = gameplay_traffic.getTrafficData()[targetId] end

    -- checks if target is visible, within distance, and in front of self
    if self:checkTargetVisible(targetId) and self.veh:getInteractiveDistance(targetVeh.pos, true) <= 3600 and self.veh.dirVec:dot(targetVeh.pos - self.veh.pos) > 0 then
      if self.driver.eventType ~= 'otherCollision' then
        self.driver.eventType = 'otherCollision'
        self:setTarget(targetId)
      end

      if self.veh.isAi and self.driver.personality.bravery >= 0.56 and random() >= 0.5 then
        self.flags.honkHornDelay = 1
      end

      self.driver.otherHitCount = data.count
    end
  end
end

function C:onTrafficTick(tickTime)
  if self.state == 'disabled' then return end

  if self.veh.isAi and self.state ~= 'disabled' and self.veh.state == 'active' and self.veh.damage > self.veh.damageLimits[3] then
    self:setAction('disabled')
  end

  local targetVeh = self.targetId and gameplay_traffic.getTrafficData()[self.targetId]
  self.targetVisible = self:checkTargetVisible()
  self.targetNear = (targetVeh and self.veh:getInteractiveDistance(targetVeh.pos, true) <= 3600) and true or false

  if targetVeh and self.veh.isAi then
    local driver = self.driver

    if self.targetVisible and self.targetNear then
      local actionType

      -- target vehicle collided with self
      if driver.eventType == 'selfCollision' and self.veh.damage >= driver.damageThreshold then
        if driver.selfHitCount >= driver.collisionThreshold then
          actionType = 'crashThreshold'
          driver.collisionThreshold = driver.collisionThreshold + 2
        else
          actionType = 'crash'
        end

      -- target vehicle collided with other vehicle
      elseif driver.eventType == 'otherCollision' and targetVeh.damage >= driver.damageThreshold then
        if driver.otherHitCount >= driver.collisionThreshold + 1 then -- higher threshold than self collision
          actionType = 'crashThreshold'
          driver.collisionThreshold = driver.collisionThreshold + 2
        else
          actionType = 'crash'
        end

      -- target vehicle crashed by itself
      elseif driver.eventType == 'otherCrash' then
        if self.state == 'none' and driver.personality.bravery + driver.personality.patience >= 0.8
        and self.veh.driveVec:dot(targetVeh.pos - self.veh.pos) > 0 then
          actionType = 'crash'
        end
      end

      if actionType == 'crashThreshold' then -- follow, flee, etc.
        local minTime = 5
        if driver.damageAction == 'followPostCrash' and self.state ~= 'follow' then
          local chaseTarget = false
          if targetVeh.tracking.collisions >= 2 and driver.personality.bravery >= 0.6 then -- mode change if target driver is reckless
            chaseTarget = true
            minTime = 10
          end
          self:setAction('followPostCrash', {reason = driver.eventType, targetId = self.targetId, isChase = chaseTarget})
          self.actionTimer = max(minTime, (driver.personality.bravery - 0.5) * 60) -- duration for following target
        elseif driver.damageAction == 'fleePostCrash' and self.state ~= 'flee' then
          self:setAction('fleePostCrash', {reason = driver.eventType, targetId = self.targetId})
          self.actionTimer = max(minTime, (0.5 - driver.personality.bravery) * 60) -- duration for fleeing from target
        else
          if self.state == 'none' then
            self:setAction('pullOver', {dist = self.veh:getBrakingDistance(self.veh.speed, driver.aggression * 1.5), reason = driver.eventType, useWarnSignal = true})
            self.actionTimer = max(minTime, driver.personality.patience * 30)
          end
        end
      elseif actionType == 'crash' then -- just pull over and stop
        if self.state == 'none' then
          self:setAction('pullOver', {dist = self.veh:getBrakingDistance(self.veh.speed, driver.aggression * 1.5), reason = driver.eventType, useWarnSignal = true})
          self.actionTimer = max(3, driver.personality.patience * 20)
          if driver.eventType == 'selfCollision' then
            self.flags.askInsurance = 1
          end
        end
      end
    else -- target not visible or out of range
      if self.state ~= 'none' and self.veh.pos:squaredDistance(targetVeh.pos) >= 14400 then -- check if state needs to be reset
        self:resetAction()
      end
    end
  end

  if self.enableTrafficSignalsChange and self.veh.speed >= 6 and next(map.objects[self.veh.id].states) then -- lightbar triggers all traffic lights to change to the red state
    -- this exists here until we have a way to properly recognize emergency vehicles (lightbar exists)
    if map.objects[self.veh.id].states.lightbar then
      self:freezeTrafficSignals(true)
    else
      if self.flags.freezeSignals then
        self:freezeTrafficSignals(false)
      end
    end
  else
    if self.flags.freezeSignals then
      self:freezeTrafficSignals(false)
    end
  end
end

function C:onUpdate(dt, dtSim)
  if self.state == 'disabled' then return end

  local targetVeh = self.targetId and gameplay_traffic.getTrafficData()[self.targetId]
  if targetVeh then
    if self.actionTimer > 0 then
      self.actionTimer = self.actionTimer - dtSim

      if self.flags.askInsurance then
        if self.actionTimer <= 0 then
          if self.targetVisible and self.veh.speed <= 1 and targetVeh.speed <= 1 and self.veh.pos:squaredDistance(targetVeh.pos) <= 400 then
            self:setAction('askInsurance') -- exchange insurance information
            if gameplay_traffic.showMessages and self.targetId == be:getPlayerVehicleID(0) then
              if career_career and career_career.isActive() then
                -- career insurance logic might go here
              else
                ui_message('ui.traffic.interactions.insuranceExchanged', 5, 'traffic', 'traffic')
              end
            end
            self.targetId = nil
          else
            self.actionTimer = 5 -- bounce time
          end
        end
      elseif self.actionName == 'followPostCrash' or self.actionName == 'fleePostCrash' then
        if self.veh.pos:squaredDistance(targetVeh.pos) > 1600 or self.actionTimer <= 0 then
          if self.veh.collisions[self.targetId] then -- switch to askInsurance mode
            self:setAction('pullOver', {dist = self.veh:getBrakingDistance(self.veh.speed, self.driver.aggression * 1.5), reason = 'selfCollision', useWarnSignal = true})
            self.flags.askInsurance = 1
            self.actionTimer = 5
          else
            self:resetAction()
          end
        end
      elseif self.actionName == 'pullOver' then
        if self.actionTimer <= 0 then
          self:resetAction()
        end
      end
    end
  end

  if self.flags.honkHornDelay and random() >= 0.9 then
    self.veh:honkHorn(max(0.25, square(random()) * 1.5))
    self.flags.honkHornDelay = nil
  end
end

return function(...) return require('/lua/ge/extensions/gameplay/traffic/baseRole')(C, ...) end