Skip to content

Add createWaterCannon for script-controlled water jets - #5313

Open
TheCrazy17 wants to merge 4 commits into
multitheftauto:masterfrom
TheCrazy17:feat/custom-water-cannons
Open

Add createWaterCannon for script-controlled water jets#5313
TheCrazy17 wants to merge 4 commits into
multitheftauto:masterfrom
TheCrazy17:feat/custom-water-cannons

Conversation

@TheCrazy17

Copy link
Copy Markdown
Contributor

Summary

Adds createWaterCannon, a Lua function that spawns a standalone native water jet (the same CWaterCannon system a Firetruck or SWAT tank uses) at any position, not tied to a vehicle. The jet uses the real native physics, particle spray, and sound, also knocks peds down the same way a real vehicle cannon does.

mta-screen_2026-09-02_21-31-38

createWaterCannon(x, y, z [, color, knockdownEnabled])
Creates the cannon at the given position. color is a packed color from tocolor (defaults to the native light blue if omitted). knockdownEnabled defaults to true. Returns the new water-cannon element, or false if the (separate, capped) pool of custom cannons is full.

local cannon = createWaterCannon(0, 10, 3, tocolor(255, 0, 0, 200), false)

setWaterCannonDirection(cannon, x, y, z) / getWaterCannonDirection(cannon)
The jet's aim.

setWaterCannonDirection(cannon, 0, 1, -0.2) -- slightly downward, +Y
local x, y, z = getWaterCannonDirection(cannon)

setWaterCannonForce(cannon, force) / getWaterCannonForce(cannon)
Scales the jet's velocity, and with it, its range. 1.0 is the default/native strength.

setWaterCannonForce(cannon, 1.5)

setWaterCannonEnabled(cannon, enabled) / isWaterCannonEnabled(cannon)
Turns the spray on or off without destroying the cannon. Starts enabled.

setWaterCannonEnabled(cannon, false)

setWaterCannonKnockdownEnabled(cannon, enabled) / isWaterCannonKnockdownEnabled(cannon)
Whether getting sprayed knocks a ped down. Starts enabled, matching vanilla vehicle-cannon behavior; turn it off for a purely decorative jet.

setWaterCannonKnockdownEnabled(cannon, false)

setWaterCannonColor(cannon, r, g, b [, a]) / getWaterCannonColor(cannon) / resetWaterCannonColor(cannon)
Overrides the jet's RGBA color as separate components (not a packed tocolor value, unlike the color argument on creation). resetWaterCannonColor goes back to the native light blue.

setWaterCannonColor(cannon, 0, 255, 0, 200)

Also available as the WaterCannon OOP class (WaterCannon.create(...), cannon.direction, cannon:setColor(...), etc), and two new events:

onClientPedHitByWaterCannon(ped) / onClientPlayerHitByWaterCannon(player)
Fired on the cannon element (vehicle-mounted or standalone) when it hits a ped or player. Cancel it to block that specific hit's push and knockdown.

addEventHandler("onClientPlayerHitByWaterCannon", root, function(player)
    cancelEvent() -- block the knockdown for this hit only
end)

Motivation

The water cannon was only ever exposed through a vehicle. This lets scripts place one anywhere (a fountain, a trap, a custom prop or vehicle) while keeping the native jet behavior.

Test plan

Test script: a hose 😄

https://streamable.com/7k78mq
hosetest.zip

Checklist

  • Your code should follow the coding guidelines.
  • Smaller pull requests are easier to review. If your pull request is beefy, your pull request should be reviewable commit-by-commit.

createWaterCannon/setWaterCannonDirection/setWaterCannonForce/
setWaterCannonEnabled/setWaterCannonModel (plus getters and the OOP
WaterCannon class) let a script aim and spray a water jet anywhere,
independent of any vehicle.

A custom cannon runs on its own native CWaterCannon instance, kept
entirely apart from the Firetruck/SWAT tank's own fixed-size array
(CWaterCannons::aCannons); CMultiplayerSA_WaterCannons.cpp drives a
separate list of these from the same two call sites that already run
the vehicle-owned array every frame (CWaterCannons::Update/Render),
without ever touching that array. A script spawning many custom
cannons can this way never starve a real vehicle's own cannon of a
slot, capped at 256 only as a memory/perf sanity limit of its own,
not a native constraint. Vehicle water cannons are untouched by this.

setWaterCannonModel optionally attaches a plain CClientObject kept
aimed with the same heading/pitch-from-direction formula
CAutomobile::FireTruckControl/TankControl already use for their own
nozzle, since the native jet itself needs no visible part to work.
onClientPedHitByWaterCannon's handler unconditionally cancelled the native
push and event whenever it could not resolve a cannon vehicle, which was
always the case for a script-owned standalone cannon; it now only asks the
water cannon manager whether that particular cannon has knockdown enabled,
and createWaterCannon plus setWaterCannonKnockdownEnabled/
isWaterCannonKnockdownEnabled let a script opt back out per cannon.
PushPeds hit-tests from a ped's feet with a tight ~2.24 unit radius,
which only a vehicle cannon's sagging arc reliably reaches; a custom
cannon aimed at someone's chest can miss every droplet despite visibly
spraying them. Widen the radius only for vehicle-less cannons, and fire
onClientPedHitByWaterCannon/onClientPlayerHitByWaterCannon from the
cannon element itself, matching what a vehicle cannon already gives
scripts.
@FileEX FileEX added the enhancement New feature or request label Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants