Skip to content
| Marketplace
Sign in
Visual Studio Code>Programming Languages>Steelpinion Yaml Mod HelperNew to Visual Studio Code? Get it now.
Steelpinion Yaml Mod Helper

Steelpinion Yaml Mod Helper

Rare Gentlemen

|
10 installs
| (0) | Free
typescript syntax and keyword tooltips within Steelpinion mod files
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

Steelpinion VSCode Mod Helper

Steelpinion is a turnbased multiplayer steampunk tactical combat simulator.
It supports modding equipable items on ships, personnel, and more.
This extension includes very useful context help for mod files found in https://github.com/steel-pinion/mods (the mod repo isn't public yet, but will be set to public eventually).

hover-tips

Features

While not a full blown language server with autocomplete and full type signatures, we are still enhancing the mod file code blocks with typescript syntax highlighting.

The game was originally written in javascript, then later converted to typescript; because of this history, we take advantage of certain naming conventions to predict types using an annotated json schema (exported from typescript definitions).

Requirements

Presently, this is only for internal engineers or modders on the project.


Guidance

This section will include some of the typescript definitions as-is from the game engine. There will also be a table which explains each standard rule's suggested keywords to use for tooltip guidance.

Since modders are required to declare a before/after clause for each IUserInjectedMod (aka ability.on), each event and subject can be a slightly different child entity type. event will always at least be an IEvent, but it is nice to know when working with specifically IDeployEntityEvent, IMoveEntityEvent, ICollideEntityEvent, ISalvoEvent, etc.

Similarly for subject; each subject is always an IEntityState, but could more specifically be IShipState, IObstacleState, IAuxOrdnanceState, or IPersonState.

A IUserInjectedMod may look like this in the mod files:

...
ability:
  on:
    - after: executes a maneuver
      when: iRandom.chance(0.50);
      then: |
        let moveEvent = event
        let ship = subject

        let oneSizeLess = {
          'tiny': 'tiny',
          'small': 'tiny',
          'medium': 'small',
        }[ship.size]

        addEvent(EVENT_NAME.DEPLOY_OBSTACLE, {
          defId: `dense-smog-${oneSizeLess}`,
          position: event.lastPosition,
          rotation: event.rotation
        });
...

tip 1: Take special note of the let moveEvent = event and let ship = subject which are how you can get the javascript snippet to offer some hover tooltips for specific keywords.

tip 2: Syntax highlighting will act wacky if you are missing EXACTLY ONE semicolon ;. Yes only one at the very end of the code block to signal we are done with our javascript block. So for each when, then statements, you will have one semicolon each. Do not put a semicolon at the end of each line or else the embedded syntax highlighting in yaml will start to fail and act wacky.

Standard Rules for engine v5.1.0

after/before Rule Name event keyword subject keyword event type subject type
pinion is taking flight deployEvent ship IDeployEntityEvent IShipState
pinion has compliment deployEvent ship IDeployEntityEvent IShipState
launched aux ordnance deployEvent aux IDeployEntityEvent IAuxOrdnanceState
produced an obstacle deployEvent obstacle IDeployEntityEvent IObstacleState
it is raining tapEvent subject TTapEvent IMacroEntityState
executes a maneuver moveEvent ship IMoveEntityEvent IShipState
cannot execute maneuver when being tugged moveEvent subject IMoveEntityEvent IMacroEntityState
changed facing direction moveEvent ship IMoveEntityEvent IShipState
cannot be rotated freely moveEvent subject IMoveEntityEvent IMacroEntityState
shifts to moveEvent obstacle IMoveEntityEvent IObstacleState
spins around moveEvent obstacle IMoveEntityEvent IObstacleState
yanked on deck moveEvent ship IMoveEntityEvent IShipState
vessel risks crash collisionEvent subject ICollideEntityEvent IMacroEntityState
boarding party raids collisionEvent subject ICollideEntityEvent IMacroEntityState
hidden by vapor collisionEvent subject ICollideEntityEvent IMacroEntityState
loosed blind round ballisticEvent subject IBallisticEvent IMacroEntityState
blind round trajectory ballisticEvent subject IBallisticEvent IMacroEntityState
blind round collateral trajectory ballisticEvent subject IBallisticEvent IMacroEntityState
loosed piercing round laserEvent subject ILaserEvent IMacroEntityState
evaded piercing round laserEvent subject ILaserEvent IMacroEntityState
piercing round trajectory laserEvent subject ILaserEvent IMacroEntityState
spreads ordnance fanEvent subject IFanstackEvent IMacroEntityState
misspreads ordnance fanEvent subject IFanstackEvent IMacroEntityState
blasts ordnance aoeEvent subject IAoeEvent IMacroEntityState
misblasts ordnance aoeEvent subject IAoeEvent IMacroEntityState
pinion salvos salvoEvent subject ISalvoEvent IMacroEntityState
pinion missed salvoEvent subject ISalvoEvent IMacroEntityState
pinion grazed salvoEvent subject ISalvoEvent IMacroEntityState
pinion evaded salvoEvent subject ISalvoEvent IMacroEntityState
armor blocked crash collisionEvent subject ICollideEntityEvent IMacroEntityState
hull smashed by collisionEvent subject ICollideEntityEvent IMacroEntityState
armor broken to block salvoEvent subject ISalvoEvent IMacroEntityState
hull is devastated salvoEvent subject ISalvoEvent IMacroEntityState
hull is hit salvoEvent subject ISalvoEvent IMacroEntityState
structure is hit tapEvent subject TTapEvent IMacroEntityState
materials reduce effect tapEvent TTapEvent IEntityState
materials amplify effect tapEvent TTapEvent IEntityState
frost and fire negate tapEvent TTapEvent IEntityState
intense shock disables tapEvent TTapEvent IEntityState
intense flame consumes tapEvent TTapEvent IEntityState
captain is pressured tapEvent subject TTapEvent IMacroEntityState
in a groove salvoEvent subject ISalvoEvent IMacroEntityState
slowly destealth tapEvent subject TTapEvent IMacroEntityState
flame burns IEvent IEntityState
frost bites IEvent IEntityState
debuff fixed tapEvent TTapEvent IEntityState
morphing into tapEvent subject TTapEvent IMacroEntityState
has new affix tapEvent subject TTapEvent IMacroEntityState
nerfed tapEvent TTapEvent IEntityState
structure is destroyed tapEvent subject TTapEvent IMacroEntityState
pinion is sunk tapEvent subject TTapEvent IMacroEntityState
daydreaming tapEvent TTapEvent IEntityState
exerts effort IEvent IEntityState
braces self IEvent IEntityState
treks IEvent IEntityState
knockbacked IEvent IEntityState
nearly controls IEvent IEntityState
snatches control IEvent IEntityState
subverts ordnance IEvent IEntityState
intends to harm IEvent IEntityState
resists harm IEvent IEntityState
is harmed IEvent IEntityState
ko'd tapEvent TTapEvent IEntityState
hyperventilates IEvent IEntityState

Engine Typescript for v5.1.0

note: this is not all of the typescript, just those annotated with a @modKeyword

/**
 * @modKeyword ability
 * Various actions that are extended to the parent entity.
 * Usually attached to a ship, and grants that ship extra actions.
 * Informs the behaviours within solvers.
*/
interface IAbilityDef {
  create?: ICreateAbility
  assault?: IAssaultAbility
  on?: IUserInjectedMod | IUserInjectedMod[]
}

/**
 * @modKeyword addEffect
 * Each effect may have varying parameters,
 * but most typically honor `intensity`
 * consistently as a number value signifying "more"
 * of the effect in queston.
 * 
 * A guide will likely need to be published for the baseline 
 * effects and events so that modders are aware of the various
 * ways they are used.
*/
interface IAddEffectParams {
  to: IEntityState
  effectName: TEffectName
  effect: Partial<IEffectState>
}

/**
 * @modKeyword aoeEvent
 * An aoe event has two collision checks which determine if
 * anyone in the blast is susceptible to more damage via `isHotshot`
 * flag. The inner ring of the blast can optionally do more things.
*/
interface IAoeEvent extends IEvent { 
  hitId: string
  ordnanceId: TDefId
  attacker: IMacroEntityState
  intensity: number
  isHotshot: boolean
  distance: number
  damageType: TDamageType

/**
 * @modKeyword missEvent
 * An explosion wouldn't exactly "miss" explicitly
 * in a pure physics engine. Since we have a notion of
 * `evasion` via `speed` dice rolls, we therefore also
 * have to register a collision with the explosion, yet
 * flagged as safe.
*/
interface IAoeMissEvent extends IEvent {
  ordnanceId: TDefId
  defender: null
}

/**
 * @modKeyword assault
 * Alters specific physics solver algorithm
 * for an attack action.
*/
interface IAssaultAbility {
  solver: TSolverAlgorithmName
  power: number
  ammo: number
  range: {
    min: number
    max: number
  }
}

/**
 * @modKeyword ballisticEvent
 * Is effectively the same as `ILaserEvent`,
 * but is designed to NOT pierce through.
 * It will only hit one target after all evades
 * ricochet, skilled shots are calculated.
*/
interface IBallisticEvent extends IEvent {
  hitId: string
  ordnanceId: TDefId
  distance: number
  damageType: TDamageType
  attacker: IMacroEntityState
  defender: IMacroEntityState
  stacks: TAngleMask[]
  evades: TDiceDigit[]
  collateral: IBallisticCollateralHit[]
  hitAngleMask: TAngleMask
  attackAngleMask: TAngleMask
}

/**
 * @modKeyword collisionEvent
 * Units that try to occupy the same phyical space produce this event.
 * intensity is a general calculation of how hard the impact is.
 * Intensity isn't well defined unit, but generally can be trusted that the ship hit
 * something at a certain speed and of a certain mass.
 * Smaller ships are at a disadvantage during collisions since they have less mass
 * and are more likely to encounter higher intensities.
*/
interface ICollideEntityEvent extends IEvent { 
  with: TEntityId
  atWaypoint: number
  angleMask: TAngleMask
  intensity: number
  moveSpeed: TSpeed
}

/**
 * @modKeyword create
 * Describes how an auxOrdnance will bahave after it is spawned.
*/
interface ICreateAbility {
  angle: TAngleMask
  fuel: number
  trajectory: TManeuverName
}

/**
 * @modKeyword deployEvent
 * This is a unit spawn or instantiation.
 * The map is 2d on the server.
*/
interface IDeployEntityEvent extends IEvent {
  position: TVector3
  rotation: TEulerRotation
}

/**
 * @modKeyword effect
 * These are not super well defined yet,
 * and are meant to be a flexible shared parameter space.
 * The parameters are used by a rule 
 * and can have different meanings depending on the implementation.
 */
interface IEffectState {
  id?: string
  eventId: number
  intensity: number
  cause?: string
  decision: TDecisionIndex
  decisionExpiration: TDecisionIndex
  [concept: string]: string | number | undefined
}

/**
 * @modKeyword consts
 * Isolated modable keys which make sense to mod.
 * Comes from {@link IModConstants},
 * but can these can be extended as well to have new keys.
 */
interface IEntityConstants {
  DAMAGE: IModDamageConsts
  DICE: IModDiceConsts
  RARE_CRIT: IModRareCritConsts
  [category: string]: Partial<IModFile>
}

/**
 * @modKeyword subject
 * Base class for all game objects with state tracking needs.
 * It was imagined that there would be
 * ships, projectiles, obstacles, and people that need be managed.
 * Ideally all of those entities would have reactions
 * to the same effects, even if they reacted differently.
 * This base entity is meant to encapsulate
 * the most common functionality shared between all entities.
 */
interface IEntityState {
  def: IEntityDef
  defId: TDefId
  entityId: TEntityId
  type: TEntityTypeName
  slot: TSlotName
  index: number
  owner: TOwnerId
  size: TSizeName
  dimensions: TVector2
  intent: TIntentName
  position: TVector3
  rotation: TEulerRotation
  stats: IBaseStats
  affix: TAffix[]
  report: IPreviousMutationReport
  consts: IEntityConstants
  isTouchingPoint: (point: TVector2) => boolean
  hasTrait: (trait: TTraitName, specificValue?: string) => boolean
  getTrait: (trait: TTraitName) => string
  hasEffect: (effectName: TEffectName, truthyPropertyName?: string) => boolean
  hasEffects: (effect: TEffectName, filterBy: TEffectFilter) => boolean
  getEffects: (effectName: TEffectName, filterBy?: TEffectFilter, limit?: number) => IEffectState[]
  setDefinition: (def: IEntityDef) => void
  setIntent: (intent?: TIntentName) => void
  setPosition: (newPos: TVector3) => void
  setRotation: (newRot: TEulerRotation) => void
  setReportEntry: <T>(name: string, value: T) => void
  mutateStats: <T>(statName: TNumericStatName, modifyBy: number) => T
  addPendingEffect: (effectName: TEffectName, effectSettings) => number
  nextPendingEffect: (effectName, removalCheck: TEffectFilter) => IEffectState | undefined
  popPendingEffects: (effectName: TEffectName, filterBy?: TEffectFilter, limit?: number) => IEffectState[]
  removePendingEffect: (effectName: TEffectName, filter: TEffectFilter, limit?: number) => number
  alterPendingEffect: (effectName: TEffectName, mapFunc: TEffectStateAlteration, filter?: TEffectFilter, limit?: number) => number
  effectIntensity: (effectName: TEffectName, filterBy?: TEffectFilter) => number
  resolve: <T>() => T
}

/**
 * @modKeyword event
 * This is the baseline building block used to create visible effects,
 * moving the progression of the battle forward.
 * There are several kinds of events depending on the solver,
 * and those events are passed through any number of rules
 * which finally create effects.
 * 
 * A diagram and article was created to better illustrate how this happens.
 * https://steelpinion.com/steel-saturday/article-10
*/
interface IEvent {
  eventId: TEventId
  entityId: TEntityId
  type: TEventName
  touchCount?: number
  [eventProp: string]: unknown
}

/**
 * @modKeyword required
 * A list of traits with corresponding acceptable values for those traits which allow this component to be attached legally.
*/
interface IExpectOneOfCollection {
  [traitName: TTraitName]: string[]
}

/**
 * @modKeyword missEvent
 * Laser's are only aimed with an angle.
 * Any damage or "hits" are side effects.
 * I think because of this, the default is that
 * laser fires are considered the same as a missEvent structurally.
 * Though there is a corresponding 
 * LaserEvent on a detected beam collision.
*/
interface ILaserBeginEvent extends IEvent {
  ordnanceId: TDefId
  defender: null // why did i do this?
  attackAngleMask: TAngleMask
}

/**
 * @modKeyword subject
 * Large entity are {@link IObstacleState} and {@link IShipState}.
*/
interface IMacroEntityState extends IEntityState {
  stats: IMacroEntityStats
  gunnerRank: number
  aboard: IPersonState[]
  layout: ILayoutBlueprint
  evadeDicePool: TDiceName[]
  assaultDicePool: TDiceName[]
  hasArmorAngle: (angleMask: TAngleMask) => boolean
  getPersons: (filter?: TPersonFilter) => IPersonState[]
  hitArmorAngle: (angleMask: TAngleMask) => boolean
  reduceDamageWithArmor: (incomingDamage: number, angleMask: TAngleMask) => number
  addPersons: (personStates: IPersonState[]) => void
  alterPersons: (filter: TPersonFilter, alteration: TPersonAlteration) => IPersonState[]
  /* has side effects which allows up to 4 bypasses per engine step - will return true up to 4 times */
  tryCollateralBypass: () => boolean
  getManeuverDifficulty: (maneuverName: TManeuverName) => TManeuverDifficulty
}

/**
 * @modKeyword stats
 * Typically a reference to quality of "big" entity like ship or obstacle.
 * Some concepts may not make sense like speed for a building.
 * Although every macro entity uses `hull` to represent hitpoints (HP).
*/
interface IMacroEntityStats extends IBaseStats {
  power: number
  speed: number
  hull?: number
  armor: TArmorLayer[]
}

/**
 * @modKeyword consts
 * Specific TDefIds that are used to drive varying downstream rules.
 */
interface IModConstants {
  [category: string]: Partial<IModFile>
  BRAWL: IModBrawlConsts
  DAMAGE: IModDamageConsts
  DECK_PLAN: IModDeckPlanConsts
  DICE: IModDiceConsts
  EFFECT: IModEffectConsts
  ENTITY: IModEntityConsts
  KEYWORD: IModKeywordConsts
  PHASE: IModPhaseConsts
  RARE_CRIT: IModRareCritConsts
  SIZE: TModSizeConsts
  SOLVER: IModSolverConsts
  SPEED: IModSpeedConsts
}

/**
 * @modKeyword then
 * Similar to the {@link IModMinimalGlobalScope}
 * as seen in the `when` condition checks,
 * except that additonal context is set for
 * modder convienience.
*/
interface IModLogicGlobalScope extends IModMinimalGlobalScope {
  world: IWorldState
  addEvent: TAddEventFunc
}

/**
 * @modKeyword when
 * A minimal set of context for the varying kinds of
 * subjects which may be involved for proc'ed events.
*/
interface IModMinimalGlobalScope {
  event: IEvent
  subject: IEntityState
  attacker: IShipState
  defender: IShipState
  other: IEntityState
  iRandom: IRandomHelper
}

/**
 * @modKeyword SPEED
 * Human readable version of TSpeed.
*/
interface IModSpeedConsts extends IModFile {
  'SLOWEST': 1
  'SLOW': 2
  'STEADY': 3
  'FAST': 4
  'FASTEST': 5
}

/**
 * @modKeyword moveEvent
 * A specific kind of event from the physics solver
 * regarding how entities are being moved around.
*/
interface IMoveEntityEvent extends IEvent {
  position: TVector3
  rotation: TEulerRotation
  moveId: TMoveId
  maneuverName: TManeuverName
  moveDirection: TDirectionName
  moveSpeed: TSpeed
}

/**
 * @modKeyword obstacle
 * Obstacles were stationary, until we decided to make them move.
 * Now they are basically ships without a crew, weapons, or armor.
 * Though, by design, obstacles can still have those things!
*/
interface IObstacleState extends IMacroEntityState {
  morph: (into: IEffectState) => void
}

/**
 * @modKeyword iRandom
 * A random number generator class for differing uses.
 * Over time I'll expose the full set of random utilities.
*/
interface IRandomHelper {
  /** percent should be between 0.0 - 1.0 */
  chance: (percent: number) => boolean
}

/**
 * @modKeyword when
 * This is the base context passed into a conditional check.
 * If the mod's condition resolves to `true` ,
 * mod's logic is executed.
*/
interface IRuleConditionContext<T extends IEvent> {
  event: T
  subject: IEntityState
  world: IWorldState
}

/**
 * @modKeyword salvoEvent
 * The standard attack which utilizes dice rolls,
 * range bonuses, and other collateral calculations
 * from the solver to mathematically damage opponents.
*/
interface ISalvoEvent extends IEvent {
  hitId: string
  entityId: TEntityId
  ordnanceId: TDefId
  attacker: IMacroEntityState
  attacks: TDiceDigit[]
  isHotshot: boolean
  distance: number
  damageType: TDamageType

/**
 * @modKeyword ship
 * The ship was the original base unit of the game.
 * The concepts where divided up and shared with other entity
 * like IObstacleState and even IPersonState.
 * 
 * For the most part, we are going to make ships and obstacles almost
 * indistinguishable from one another.
*/
interface IShipState extends IMacroEntityState {
  _construct: IShipConstruct
  captainPressure: number
}

/**
 * @modKeyword traits
 * Flags which may allow access to certain restricted equipment.
 * This is a loose structure which allows modders to define completely new flags.
 * You may see traits defined all over the default game files 
 * which can guide how they are typically used.
*/
interface ITraits {
  [traitName: string]: string
}

/**
 * @modKeyword on
 * Each mod rule can be injected before or after
 * another loaded rule by name.
 * 
 * A mod file can contain a list of rules.
*/
interface IUserInjectedMod {
  after?: TRuleTitle
  before?: TRuleTitle
  subject?: TSerializedSubjectQuery
  when: TSerializedRuleCondition
  then: TSerializedRuleLogic
}

/**
 * @modKeyword world
 * in-memory representation of all that exists within a match.
 * @todo
 * need to add arena
*/
interface IWorldState {
  seed: string
  name: string
  manager: IStateManager
  decision: number
  entityStates: IEntityStateCollection
  weather: string
  random: (seed: string) => number
  entityIds: (owner?: TOwnerId) => TEntityId[]
  entity: <T extends IEntityState>(entityId: TEntityId) => T
  instantiate: <T extends IEntityState>(blueprint: IRuntimeBlueprint) => T
  addEvent: (event: TEvent) => number
  nextEvent: (filter?: TEventFilter) => TEvent | null
  macroEntityEvadeRoll: (entityId: TEntityId, rollCount: number, reason: string) => TDiceDigit[]
  nullifyEffect: (nullification: IEffectNullification) => IEffectNullifyReport[]
}

/**
 * @modKeyword addEffect
 * Don't call `entity.addPendingEffect(TEffectName, Partial<IEffectState>)`
 * directly. Instead, just use this function instead to ensure effects are
 * propagated correctly.
 * 
 * Automatically includes a lexical/network broadcast of the effect
 * so that both logs and clients can see the effect.
 * 
 * The server will still be able to act on nonbroadcasted effects,
 * but the client not expressing every effect will appear to be buglike.
 * 
 * Other metadata like the eventId, world.decision, and effect.cause
 * are automatically set as well.
*/
type TAddEffectFunc = (params: IAddEffectParams) => void

/**
 * @modKeyword addEvent
 * Alternative to `world.addEvent(new PhaseEvent(number, Partial<IEvent>))`.
 * Automatically includes a sane `eventId` and present `subject.entityId`.
 * 
 * Adding an event will lengthen the ruleset resolution time a little.
 * It is possible to create circular events which would break the game.
 * This version of the addEvent will include protection against such
 * circular logic dangers.
 * 
 * By default, it is assumed that a mod should not add more than ~10000
 * events per solver phase.
 * @example
 * ```
 * addEvent(EVENT_NAME.MOVE_SHIP, {
 *   entityId: '1042-2',
 *   position: [112.5, 112.5],
 *   rotation: 180,
 *   ...
 * })
 * ```
*/
type TAddEventFunc = (eventName: TEventName, params: Record<string, unknown>) => number

/**
 * @modKeyword missEvent
 * Typically an implicit behaviour rather than an explicit event from the combat solver.
 * Certain exotic solvers such as fan solver or aoe solver may still perform
 * rule based calculations to determine if a "hit" occured.
 * Additionally, the broadcast of an exotic miss is more clear this way
 * so that people aren't like "i attacked, but nothing happened" on a miss.
*/
type TCombatMissEvent = ILaserBeginEvent
  | IFanstackMissEvent
  | IBallisticMissEvent
  | IAoeMissEvent

/**
 * @modKeyword EFFECT_NAME
 * `EFFECT`s directly alter the game state in various ways
 * influencing victory/defeat conditions.
 * This is where most of the _fun_ in designing the engine occurs.
 * These keys are used to inform the {@link TRuleImplementer}
 * what logic to use for a given circumstance.
 * Note, that these effects here are compiletime constants only.
 * Mod files can introduce new effects at runtime.
 * This set of enums merely aid in building the mod/base version of the game.
 */
type TEffectName = 'NEGATE'
  | 'NONE'
  | 'SUMMON'
  | 'MOVE'
  | 'ANGLE'
  | 'ATTACK'
  | 'MISS'
  | 'GRAZE'
  | 'BLOCK'
  | 'HIT'
  | 'CRIT'
  | 'BREAK'
  | 'FIX'
  | 'FIRE'
  | 'SHOCK'
  | 'FROST'
  | 'CHEM'
  | 'HARM'
  | 'SPIN'
  | 'TUG'
  | 'DISARM'
  | 'SINK'
  | 'FLEE'
  | 'PRESSURE'
  | 'DEPRESSURE'
  | 'COLLIDE'
  | 'EVADE'
  | 'UNLOAD'
  | 'RELOAD'
  | 'MORPH'
  | 'HIDE'
  | 'COLLATERAL'
  | 'SABOTAGE'
  | 'BUFF'
  | 'DEBUFF'
  | 'EQUIP'
  | 'UNEQUIP'
  | 'DISABLE'
  | 'AMPLIFY'
  | 'GROOVE'
  | 'SHOOT'
  | 'VULN'
  | 'RAID'
  | 'BOARDED'
  | 'FREEFALL'
  | 'STRIKE'
  | 'DODGE'
  | 'USE'
  | 'MISUSE'
  | 'ALMOST'
  | 'ENDURE'
  | 'KO'

/**
 * @modKeyword EVENT_NAME
 * An event spawns a bunch of effects that alter the game state.
 * This is a critical juncture in the headless engine.
 * It encapsulates at the highest level all gameplay features.
 * Before a gameplay feature is added,
 * one must consider which event it belongs to,
 * or if it needs a new event.
 * This is not to be confused with {@link TEffectName}.
 * 
 * DEPLOY_BEGIN - Fires once for every entity at the beginning of each match.  
 * DEPLOY_END - Fires once for every entity at the beginning of each match after deploy selections are finalized.  
 * REPOSITION_BEGIN - Fires once for every ship at the beginning of each match when matchmaking is complete.  
 * REPOSITION_END - Fires once for every ship after the matchmaking grace period _(typically 30s)_.  
 * MANEUVER_BEGIN - Fires every turn for every movable entity before the {@link IManeuverProcess} runs.  
 * MANEUVER_END - Fires every turn for every movable entity after the {@link IManeuverProcess} runs.  
 * COMBAT_BEGIN - Fires every turn for every armed entity before the {@link ICombatProcess} runs.  
 * COMBAT_END - Fires every turn for every armed entity after the {@link ICombatProcess} runs.  
 * DEPLOY - Fires anytime an entity is spawned.  
 * MOVE - Fires anytime an entity is moved.  
 * COLLISION - Fires anytime {@link IManeuverProcess} detects a collision.  
 * SALVO - Fires anytime an armed entity assaults something with `standard` attack.  
 * 
 * @todo
 * REPOSITION_BEGIN, REPOSITION_END do not appear to be honored by {@link IStateManager} at this time
 */
type TEventName = 'deploy-start'
  | 'deploy-ship'
  | 'deploy-obstacle'
  | 'deploy-end'
  | 'reposition-start'
  | 'reposition-end'
  | 'maneuver-start'
  | 'move-ship'
  | 'move-obstacle'
  | 'collision'
  | 'maneuver-end'
  | 'combat-start'
  | 'salvo'
  | 'fan-stack'
  | 'fan-stack-miss'
  | 'laser'
  | 'laser-begin'
  | 'ballistic'
  | 'ballistic-miss'
  | 'aoe'
  | 'aoe_miss'
  | 'spawn-ordnance'
  | 'combat-end'
  | 'raid-turn-begin'
  | 'raid-invade'
  | 'raid-pos'
  | 'raid-use'
  | 'raid-cost'
  | 'raid-push'
  | 'raid-harm'
  | 'raid-tinker'
  | 'raid-falloff'
  | 'raid-turn-end'

/**
 * @modKeyword MANEUVER_DIFFICULTY
 * When a ship moves, it has effects on those on deck.
 * Pressure can be removed or applied based on how the ship
 * moves.
*/
type TManeuverDifficulty = 'simple' | 'standard' | 'complex'

/**
 * @modKeyword size
 * In meters;
 * slender 0.5, wide 1, bulbous 2
*/
type TMissleSizeName = 'slender' | 'wide' | 'bulbous'

/**
 * @modKeyword size
 * In meters;
 * this 0.15, average 0.22, thick 0.35
*/
type TPersonSizeName = 'thin' | 'average' | 'thick'

/**
 * @modKeyword when
 * Used to filter out events/effects from a rulset algorithm.
 */
type TRuleCondition<T extends IEvent> = (context: IRuleConditionContext<T>) => boolean | IRuleConditionOutput

/**
 * @modKeyword after
 * Will cause this rule to take place right after
 * the named rule here.
*/
type TRuleTitleAfter = string

/**
 * @modKeyword before
 * Will cause this rule to take place right before
 * the named rule here.
*/
type TRuleTitleBefore = string

/**
 * @modKeyword size
 * Default macro entity sizes measure in meters;
 * tiny 5, small 10, medium 15, large 20, giant 25, massive 30
*/
type TShipSizeName = 'tiny' | 'small' | 'medium' | 'large' | 'giant' | 'massive'

/**
 * @modKeyword size
 * A general word used to encapsulate ship and other volumes, 
 * including sizes of people and missles.
*/
type TSizeName = TShipSizeName
  | TPersonSizeName
  | TMissleSizeName

/**
 * @modKeyword slot
 * Refers to the customization slots available on a `IMacroEntity`, person.
 * 
 * @remarks
 * DEBT: This term has become overloaded in the definitions
 * since we now use `mods/definitions/.../file.slot` to isolate types of {@link IModFile}
*/
type TSlotName = 'ship' 
  | 'captain'
  | 'crew'
  | 'armor'
  | 'gyro'
  | 'engine'
  | 'ordnance'
  | 'auxOrdnance'
  | 'merit'
  | 'talent'
  | 'technology'
  | 'obstacle'

/**
 * @modKeyword SPEED
 * Controls how far something moves each simulation frame.
 * These aren't exactly units, 
 * but are relatively representative of slower to faster.
*/
type TSpeed = 1 | 2 | 3 | 4 | 5

/**
 * @modKeyword tapEvent
 * Tap events are ideal for triggering effects at the beginning/end of turns.
 * Presently isn't any different from the regular `IEvent`.
 * The tap events are reserved for future possible extra fields.
 */
type TTapEvent = IDeployTapEvent | IRepositionTapEvent | IManeuverTapEvent | ICombatTapEvent | IRaidTapEvent

Guidance Feedback?

It is mentioned above, but any issues understanding this can be questioned here https://github.com/steel-pinion/vscode-mod-helper/issues.
I'll try to add more clarity to future docs if possible. I'll also eventually learn how to make a full blown language server which will help reduce the need for this massive readme! For the time being, need to get back to making the game itself with the tool we have today :)

Known Issues

We plan to have a certain number of api versions active at any given time for smooth deprecation of older api patterns.

The engine publishes public api versions honoring semver rules. Regardless of major, minor, patch version changes in the exposed engine api, a mod is always fixed to a specific version.

This makes troubleshooting very very simple (since mod version, extension version, engine version always line up). The same pattern will be employed here. When an engine version is released, then a release for this extension will also be published automatically; matching the exact version string released by the engine code.

Because of this pattern, it is possible for this extension to show support for new contracts that do not exist in prior api versions.

If a mod author maintains an older engine version mod, then this inconvienience could be really confusing when you try to use a keyword that doesn't yet exist for your version.

(While possible, this is expected to happen infrequently; and if we do alter a contract, it'll be at least a major/minor version tick due to semver rules).

Any other issues, we can chat about here https://github.com/steel-pinion/vscode-mod-helper/issues.

Release Notes

2.0.0

  • Rework the readme to be autogenerated by engine build process
  • generate keyword table with typescript hints
  • export raw typescript defs as-is from engine source code

1.0.0

  • Initial release for internal engineers
  • learning how to make a vscode extension

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
© 2025 Microsoft