Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/HarbourMasters/Starship/llms.txt

Use this file to discover all available pages before exploring further.

Player action events fire at precise points during player combat and movement actions. They are defined in port/hooks/list/ActionEvent.h and give mods fine-grained control over shooting behaviour, movement mechanics, and bomb usage — including the ability to cancel or modify actions before they take effect.

Event Overview

EventWhen it firesCancellableKey fields
PlayerActionBoostEventPlayer activates boostYesplayer
PlayerActionBrakeEventPlayer activates brakeYesplayer
PlayerActionPreShootEventBefore a laser shot is createdYesplayer, laser
PlayerActionPostShootEventAfter a laser shot is createdNoplayer, shot
PlayerActionPreShootChargedEventBefore a charged shot is createdYesplayer
PlayerActionPostShootChargedEventAfter a charged shot is createdNoplayer
PlayerActionPreBombEventBefore a bomb is droppedYesplayer
PlayerActionPostBombEventAfter a bomb is droppedNoplayer

Movement Events

PlayerActionBoostEvent

Fires when a player presses the boost button before the engine applies the boost velocity.
player
Player*
required
Pointer to the Player struct for the player who is boosting. Access speed, position, and boost meter via this pointer.
Cancelling this event prevents the engine’s default boost logic from running for this input.

PlayerActionBrakeEvent

Fires when a player presses the brake button before the engine applies the braking force.
player
Player*
required
Pointer to the Player struct for the player who is braking.
Cancelling this event prevents the engine’s default brake logic from running.

Movement example — refill boost meter on boost or brake

infinite_boost.c
#include "port/hooks/list/ActionEvent.h"

static void RefillBoostMeter(Player* player) {
    if (player->boostMeter > 1.0f) {
        player->boostMeter = 1.0f;
    }
}

void OnPlayerBoost(IEvent* evt) {
    PlayerActionBoostEvent* event = (PlayerActionBoostEvent*)evt;

    if (CVarGetInteger("gInfiniteBoost", 0) == 1) {
        RefillBoostMeter(event->player);
    }
}

void OnPlayerBrake(IEvent* evt) {
    PlayerActionBrakeEvent* event = (PlayerActionBrakeEvent*)evt;

    if (CVarGetInteger("gInfiniteBoost", 0) == 1) {
        RefillBoostMeter(event->player);
    }
}

void MyMod_Register() {
    REGISTER_LISTENER(PlayerActionBoostEvent, OnPlayerBoost, EVENT_PRIORITY_NORMAL);
    REGISTER_LISTENER(PlayerActionBrakeEvent, OnPlayerBrake, EVENT_PRIORITY_NORMAL);
}

Laser Events

PlayerActionPreShootEvent

Fires before the engine allocates the PlayerShot struct. This is the correct place to modify which laser type will be fired, or to cancel the shot entirely.
player
Player*
required
Pointer to the shooting Player struct.
laser
LaserStrength
required
The laser type that will be fired (e.g. LASERS_SINGLE, LASERS_TWIN, LASERS_HYPER). Modify this field to change the laser type before the shot object is created.
Cancelling prevents the shot from being created at all.

PlayerActionPostShootEvent

Fires after the engine has created the PlayerShot object. Use this to modify properties of the shot — such as range, speed, or damage — without preventing the shot from firing.
player
Player*
required
Pointer to the shooting Player struct.
shot
PlayerShot*
required
Pointer to the newly created PlayerShot struct. Modify shot->timer to change how long the shot travels before expiring, effectively adjusting laser range.
PlayerActionPostShootEvent is not cancellable — the shot has already been committed to the object pool. Use PlayerActionPreShootEvent if you need to prevent the shot from being created.

Laser range multiplier example

This pattern mirrors how the built-in gLaserRangeMult CVar works in PortEnhancements.c:
laser_range.c
#include "port/hooks/list/ActionEvent.h"

void OnPlayerShootPost(IEvent* evt) {
    PlayerActionPostShootEvent* event = (PlayerActionPostShootEvent*)evt;

    // Scale shot lifetime by CVar value (100 = 1× default range)
    event->shot->timer *= CVarGetInteger("gLaserRangeMult", 100) / 100.0f;
}

void MyMod_Register() {
    REGISTER_LISTENER(PlayerActionPostShootEvent, OnPlayerShootPost, EVENT_PRIORITY_NORMAL);
}

Upgrading laser type before firing

force_hyper_laser.c
#include "port/hooks/list/ActionEvent.h"

void OnPlayerPreShoot(IEvent* evt) {
    PlayerActionPreShootEvent* event = (PlayerActionPreShootEvent*)evt;

    if (CVarGetInteger("gHyperLaser", 0) == 1) {
        // Upgrade to hyper laser regardless of current pickup state
        event->laser = LASERS_HYPER;
    }
}

void MyMod_Register() {
    REGISTER_LISTENER(PlayerActionPreShootEvent, OnPlayerPreShoot, EVENT_PRIORITY_HIGH);
}

Charged Shot Events

PlayerActionPreShootChargedEvent

Fires before the engine creates a charged shot. Cancel to prevent the charged shot from firing.
player
Player*
required
Pointer to the Player struct that is releasing the charged shot.

PlayerActionPostShootChargedEvent

Fires after the charged shot has been created. The shot object is already in the pool.
player
Player*
required
Pointer to the Player struct that released the charged shot.
charged_shot_hook.c
#include "port/hooks/list/ActionEvent.h"

void OnPreChargedShoot(IEvent* evt) {
    PlayerActionPreShootChargedEvent* event = (PlayerActionPreShootChargedEvent*)evt;

    // Cancel the charged shot if the player's shields are critical
    if (event->player->shields <= 10) {
        event->event.cancelled = true;
    }
}

void MyMod_Register() {
    REGISTER_LISTENER(PlayerActionPreShootChargedEvent, OnPreChargedShoot, EVENT_PRIORITY_NORMAL);
}

Bomb Events

PlayerActionPreBombEvent

Fires before the engine drops a bomb. Cancel to consume the input without spending a bomb, or to redirect the bomb drop to custom logic.
player
Player*
required
Pointer to the Player struct about to drop a bomb.

PlayerActionPostBombEvent

Fires after the bomb has been placed in the world. The bomb object is already active.
player
Player*
required
Pointer to the Player struct that dropped the bomb.
bomb_hook.c
#include "port/hooks/list/ActionEvent.h"

void OnPreBomb(IEvent* evt) {
    PlayerActionPreBombEvent* event = (PlayerActionPreBombEvent*)evt;

    // Prevent bomb drop if a custom condition is active
    if (CVarGetInteger("gMyModNoBombs", 0) == 1) {
        event->event.cancelled = true;
    }
}

void OnPostBomb(IEvent* evt) {
    PlayerActionPostBombEvent* event = (PlayerActionPostBombEvent*)evt;
    printf("[MyMod] Bomb dropped by player %p\n", (void*)event->player);
}

void MyMod_Register() {
    REGISTER_LISTENER(PlayerActionPreBombEvent, OnPreBomb, EVENT_PRIORITY_NORMAL);
    REGISTER_LISTENER(PlayerActionPostBombEvent, OnPostBomb, EVENT_PRIORITY_NORMAL);
}

Complete Example — Laser Behaviour Mod

The following mod combines pre- and post-shoot events to both upgrade the laser type and extend its range, replicating and extending the pattern from PortEnhancements.c:
laser_mod.c
#define INIT_EVENT_IDS
#include "port/hooks/Events.h"
#include "port/hooks/list/ActionEvent.h"

// Pre-shoot: upgrade to hyper laser if the CVar is active
void OnPlayerPreShoot(IEvent* evt) {
    PlayerActionPreShootEvent* event = (PlayerActionPreShootEvent*)evt;

    if (CVarGetInteger("gHyperLaser", 0) == 1) {
        event->laser = LASERS_HYPER;
    }
}

// Post-shoot: scale laser range by a percentage multiplier
// 100 = default, 200 = double range, 50 = half range
void OnPlayerShootPost(IEvent* evt) {
    PlayerActionPostShootEvent* event = (PlayerActionPostShootEvent*)evt;

    int mult = CVarGetInteger("gLaserRangeMult", 100);
    if (mult != 100) {
        event->shot->timer *= mult / 100.0f;
    }
}

// Cancel all shots while a "no shoot" mode is active
void OnPlayerPreShootCancel(IEvent* evt) {
    if (CVarGetInteger("gMyModNoShoot", 0) == 1) {
        evt->cancelled = true;
    }
}

void LaserMod_Register() {
    REGISTER_LISTENER(PlayerActionPreShootEvent,  OnPlayerPreShoot,       EVENT_PRIORITY_HIGH);
    REGISTER_LISTENER(PlayerActionPreShootEvent,  OnPlayerPreShootCancel, EVENT_PRIORITY_NORMAL);
    REGISTER_LISTENER(PlayerActionPostShootEvent, OnPlayerShootPost,      EVENT_PRIORITY_NORMAL);
}
Multiple listeners on PlayerActionPreShootEvent run in priority order. If a HIGH listener cancels the event, NORMAL listeners still execute — but the shot will not be created regardless. If you need to skip remaining listeners after cancellation, check evt->cancelled at the top of your callback and return early.
Prefer PlayerActionPostShootEvent over PlayerActionPreShootEvent when you only need to tweak properties of an existing shot (range, speed, visual). Reserve the pre-shoot event for cases where you need to change the laser type or prevent the shot entirely.

Build docs developers (and LLMs) love