Created
April 3, 2025 10:49
-
-
Save extratone/896d5245e151a5a6493dde77eb965e4c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- This object takes care of controlling the NPC cars. | |
-- List of cars that we control that are currently driving. | |
-- Fields: | |
-- thing: the car Thing | |
-- speed: the car's absolute speed (always positive) | |
-- lane: the car's lane (1,2 for northbound, -1,-2 southbound) | |
local fleet = {} | |
-- List of cars that we control that are currently | |
-- invisible waiting for placement. This is a list of | |
-- just the Thing objects, not tables. | |
local garage = { | |
getThing("CAR 0"), getThing("CAR 1"), getThing("CAR 2"), | |
getThing("CAR 3"), getThing("CAR 4"), getThing("CAR 5"), | |
getThing("CAR 6"), getThing("CAR 7"), getThing("CAR 8") | |
} | |
-- How far ahead of the player we spawn cars. | |
local SPAWN_DIST = 400 | |
-- Car speeds | |
local SPEED_MIN, SPEED_MAX = 4, 5 | |
-- World Z coordinate beyond which we despawn. | |
local DESPAWN_PLUS_Z = 600 | |
local DESPAWN_MINUS_Z = -200 | |
-- Starting number of cars. | |
local BASE_NUM_CARS = 2 | |
-- Add a new car every how many meters driven? | |
local ADD_CAR_EVERY_M = 200 | |
local crashed = false | |
-- Distance traveled so far by the player. | |
local traveledM = 0 | |
-- The "forward" velocity along the road is given | |
-- by the world position of the "Forward" object, which is | |
-- set to local position (0,0,1) in the Road. | |
local FWD_X, FWD_Y, FWD_Z = | |
getThing("Forward"):getPosition() | |
-- Countdown to the next time we will do a slowdown | |
-- check to stop cars from colliding | |
local SLOW_CHECK_INTERVAL = 60 | |
local countdownToSlowCheck = SLOW_CHECK_INTERVAL | |
function onUpdate() | |
if crashed then return end | |
-- Iterate backwards so we can remove as we go. | |
for i = #fleet, 1, -1 do | |
local e = fleet[i] | |
local x, y, z = e.thing:getLocalPosition() | |
local dir = e.lane > 0 and 1 or -1 | |
-- Move the car. This is based on its speed and the | |
-- direction of motion which depends on whether it's | |
-- driving northbound or southbound. | |
z = z + e.speed * dir | |
e.thing:setLocalPosition(x, y, z) | |
x, y, z = e.thing:getPosition() | |
-- If the car gets too far away, despawn it. | |
if z > DESPAWN_PLUS_Z or z < DESPAWN_MINUS_Z then | |
removeFromFleet(i) | |
end | |
end | |
-- How many cars do we want to have on the fleet? | |
local desiredNumCars = BASE_NUM_CARS + | |
floor(traveledM / ADD_CAR_EVERY_M) | |
-- If we have fewer cars than we want to, try to spawn. | |
if #fleet < desiredNumCars then | |
-- If we can, spawn a new car. | |
trySpawnCar((randomFloat() > 0.5 and 1 or -1) * | |
randomBetween(1, 2)) | |
end | |
countdownToSlowCheck = countdownToSlowCheck - 1 | |
if countdownToSlowCheck < 1 then | |
countdownToSlowCheck = SLOW_CHECK_INTERVAL | |
doSlowCheck() | |
end | |
end | |
-- Removes the car from the active fleet, putting it back | |
-- into the inactive list (garage). | |
function removeFromFleet(i) | |
local fleetEntry = remove(fleet, i) | |
-- Back into the garage you go. | |
insert(garage, fleetEntry.thing) | |
fleetEntry.thing:setLocalPosition(0, 0, -100000) -- hide | |
end | |
-- Tries to spawn a car. | |
function trySpawnCar(lane) | |
if #garage < 1 then return end -- Nothing to spawn | |
local rx, ry, rz = | |
getThing("Road"):getLocalPosition() | |
-- The player's position relative to the road is -rz, | |
-- as the road moves backward under the player. | |
local playerOnRoadZ = -rz | |
-- We want to spawn cars SPAWN_DIST ahead of the player | |
-- so this is this Z position at which we want to spawn. | |
local spawnZ = SPAWN_DIST + playerOnRoadZ | |
-- Check if we conflict with any car. | |
for i = 1, #fleet do | |
local lx, ly, lz = fleet[i].thing:getLocalPosition() | |
if fleet[i].lane == lane and abs(lz - spawnZ) < 60 then | |
-- Conflict! Don't spawn. | |
return | |
end | |
end | |
-- If we got here, it's all clear to spawn. | |
local newCar = remove(garage, 1) | |
newCar:setLocalPosition(getLaneLocalX(lane),1,spawnZ) | |
newCar:setLocalRotation(0,lane > 0 and 0 or 180,0) | |
-- Set a random-ish speed. | |
local speed = SPEED_MIN + randomFloat() * (SPEED_MAX - SPEED_MIN) | |
-- If traveling southbound, cut the speed by half so the | |
-- player doesn't crash as easily. | |
if lane < 0 then speed = speed * 0.5 end | |
insert(fleet, {thing=newCar, speed=speed, lane=lane}) | |
end | |
-- Returns the local X coordinate for cars on the given | |
-- lane number. | |
function getLaneLocalX(lane) | |
return 15 * lane + (lane > 0 and -5 or 5) | |
end | |
function onCrashed() | |
crashed = true | |
end | |
function onSetTraveledDist(msg) | |
traveledM = msg.traveledM | |
end | |
-- Checks to see if any two cars on the same lane are too close, | |
-- in which case we tell the faster one to slow down to the | |
-- speed of the slower one. | |
function doSlowCheck() | |
for i = 1, #fleet do | |
if fleet[i].lane > 0 then | |
local inFront = getCarInFront(i) | |
-- If the car in front is going slower than us, slow down | |
-- to match its speed so we don't crash into it. | |
if inFront and inFront.speed < fleet[i].speed then | |
fleet[i].speed = inFront.speed | |
end | |
end | |
end | |
end | |
-- Gets the car that's directly in front of this one, | |
-- or nil if none. | |
function getCarInFront(carIndex) | |
local car = fleet[carIndex] | |
local carX, carY, carZ = car.thing:getLocalPosition() | |
-- We're looking for a car in the same lane and with | |
-- this range of Z positions: | |
local minZ, maxZ = carZ, carZ + 120 | |
for i = 1, #fleet do | |
local thisCar = fleet[i] | |
if i ~= carIndex and thisCar.lane == car.lane then | |
local x, y, z = thisCar.thing:getLocalPosition() | |
if z >= minZ and z <= maxZ then return thisCar end | |
end | |
end | |
end | |
function onGameOver() | |
-- Hide all cars | |
for i = 1, #fleet do | |
fleet[i].thing:setPosition(-10000, -10000, -10000) | |
end | |
end | |
-- All cars in the garage start hidden. | |
for i = 1, #garage do garage[i]:setPosition(0,0,-1000000) end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment