Skip to content

Scriptable birds

San Andreas has ambient birds, but they are decoration: GTA owns six slots, picks the models, and nothing in MTA could touch them.

Neon adds a bird element with its own renderer and flight control. A resource creates as many as it wants, steers them, restyles them, and can make them shootable.

The clip runs test-resources/bird-showcase and bird-test. No custom model, texture or shader is involved: everything is the managed bird renderer driven from Lua.

local bird = createBird(x, y, z, {
preset = "desert", -- normal, water or desert
speed = 12.0,
size = 1.5,
renderDistance = 250.0,
wingBeatTime = 0.35,
curvedFlight = true,
shootable = true,
movementEnabled = true,
bodyColor = tocolor(220, 220, 220),
wingColor = tocolor(255, 255, 255),
velocity = { 1, 0, 0 },
})

Every field is optional. The OOP form is available too, since the element is registered as a Bird class extending Element:

local bird = Bird.create(x, y, z, { preset = "water" })
bird.size = 2.0
bird.shootable = true

setBirdVelocity moves a bird now. setBirdTargetVelocity sets where it should be heading and lets the bird ease toward it, which is what makes a flock turn as a group instead of snapping.

PropertyFunctionsOOP
VelocitygetBirdVelocity / setBirdVelocity:getVelocity() / :setVelocity()
Target velocitygetBirdTargetVelocity / setBirdTargetVelocity:getTargetVelocity() / :setTargetVelocity()
SizegetBirdSize / setBirdSize.size
ColorsgetBirdColors / setBirdColors:getColors() / :setColors()
Wing beatgetBirdWingBeatTime / setBirdWingBeatTime.wingBeatTime
Render distancegetBirdRenderDistance / setBirdRenderDistance.renderDistance
Curved flightisBirdCurvedFlightEnabled / setBirdCurvedFlightEnabled.curvedFlight
ShootableisBirdShootable / setBirdShootable.shootable
MovementisBirdMovementEnabled / setBirdMovementEnabled.movementEnabled

Turning movement off freezes a bird in place while the rest of the flock keeps flying, which is how the showcase pulls one bird out of formation without disturbing the others.

A shootable bird raises onClientBirdShot, and the event can be cancelled to keep the bird alive:

addEventHandler("onClientBirdShot", root, function(attacker, weapon, hitX, hitY, hitZ)
if weapon == 23 then -- silenced pistol does not count
cancelEvent()
end
end)
EventParameters
onClientBirdShotelement attacker, int weapon, float hitX, float hitY, float hitZ

processBirdGunShot exposes the same hit test directly, so a custom weapon or bullet system can feed its own traces into managed-bird hit detection instead of reimplementing it.

  • Client-side only. There is no server element and no synchronization; each client builds its own birds.
  • Birds fade out near their render distance rather than being destroyed, so the element survives even when nothing is drawn.
  • Dimension and interior are respected: a mismatch hides the bird while keeping its identity.
  • Stopping the owning resource removes its birds.
  • GTA’s own ambient birds keep spawning and behaving independently. Managed birds do not replace them.

test-resources/bird-test prints explicit PASS and FAIL lines. /birdtest all covers creation and type identity, property round-trips, movement and freeze behavior, invalid inputs, and 128 simultaneous managed birds, well past GTA’s six native ambient slots.

/birdtest shoot creates one large red stationary bird to confirm onClientBirdShot fires and the element is destroyed; /birdtest shootcancel repeats it with a blue bird and cancels the event, so the same element must survive. The harness routes onClientPlayerWeaponFire traces through processBirdGunShot.

Manual checks cover the three presets looking visibly distinct, render-distance fading, dimension hiding, and resource cleanup.

test-resources/bird-showcase is the cinematic. Neither resource is auto-deployed; copy them into the server resources directory first.

Implementation: 23fedfbc9.