Starward Rogue:XML - BulletPattern Definitions

From Arcen Wiki
Jump to: navigation, search


Unlike the other things being read in (GameEntity, EntitySystem, etc) this is more of a programming language than a chunk of data. That is, it's imperative as much as it is descriptive.

It's also different in that it relies heavily on template "variables" to avoid duplicating a lot of xml when you want your bullets to do the same or similar thing a lot.

Here's an example usage:

In a file in Configuration/EntitySystem/

<system name="TestWeapon"

And in a file in Configuration/BulletPattern/

<bullet_pattern name="TestVariablePattern">
	$TestOuterVar SPEED=500

And in a file in Configuration/BulletPatternVariables/

<var name="TestOuterVar">
  <bullet angle="0" speed="0">
    <loop iterations="5">
          $TestInnerVar A1=45 A2=-45
          $TestInnerVar A1=-45 A2=45
      <wait time="0.1" />
    <die />

<var name="TestInnerVar">
  <bullet angle="[A1]" speed="[SPEED]" shot_type="BulletBentEnergyRed">
    <wait time="0.5" />
    <loop iterations="5">
      <change angle="[A2]" time="0.0001" />
      <wait time="1" />
      <change angle="[A1]" time="0.0001" />
      <wait time="1" />
    <die />

Now let's trace what's going on:

Massively more confusing than the examples of the data-chunk files, right? Well, welcome to the neighborhood, it's at least as bad as it sounds.

  • There's a system (TestWeapon) with special_bullet_patterns="TestVariablePattern" . This tells it to ignore a lot of its normal firing logic and instead "do whatever that pattern tells you" whenever it fires.
  • In TestVariablePattern's definition, it references the template "TestOuterVar" and passes in the value "500" for the parameter "SPEED"
    • the 500 could have been hardcoded later on, but this allows multiple patterns to use this behavior with different bullet velocities without extra copy-and-paste
  • In TestOuterVar's definition, it:
    • creates a single "starter" bullet with no velocity (it's still visible in this example, but that's probably not desirable), which does the following five times:
      • spawn another bullet pattern, whose description references the template "TestInnerVar" twice, each time with different parameter values for A1 and A2
        • Again, the angles could have been hardcoded, but then the code in TestInnerVar would have to be written twice: once for each different set of values. Code duplication is the enemy!
      • waits a tenth of a second (before going back to the beginning of the loop)
    • and then the starter bullet dies
  • In TestInnerVar's definition, it:
    • creates a BulletBentEnergyRed bullet with angle = A1 and speed = SPEED (note: TestOuterVar doesn't have to pass SPEED in, that's already defined), which then:
      • travels at that course and speed for half a second
      • changes angle to A2 (instantly, that's the time="0.0001"
      • travels at that course and speed for a full second
      • changes back to A1
      • travels at that course and speed for a full second
      • switches back and forth four more times
      • dies

The overall result is that it fires two strings of bullets 45 degrees on either side of the line to the target, and the lines "crisscross" five times before disappearing.

IMPORTANT NOTE: the use of the template variables was completely optional and has no bearing on the actual xml schema: those just cause a bunch of text replacement when the xml is being read in. Here's how the file in Configuration/BulletPattern/ would have looked without any template vars, and also the actual xml text that the game would have _seen_ in the main parsing in the TestVariablePattern case:

<bullet_pattern name="TestVariablePattern">
  <bullet angle="0" speed="0">
    <loop iterations="5">
  <bullet angle="45" speed="500" shot_type="BulletBentEnergyRed">
    <wait time="0.5" />
    <loop iterations="5">
      <change angle="-45" time="0.0001" />
      <wait time="1" />
      <change angle="45" time="0.0001" />
      <wait time="1" />
    <die />
  <bullet angle="-45" speed="500" shot_type="BulletBentEnergyRed">
    <wait time="0.5" />
    <loop iterations="5">
      <change angle="45" time="0.0001" />
      <wait time="1" />
      <change angle="-45" time="0.0001" />
      <wait time="1" />
    <die />
      <wait time="0.1" />
    <die />

Node Types and Attributes


this is a template variable that can be referenced elsewhere to save effort. Can only be defined in files in Configuration/BulletPatternVariables/

  • name (string)
    • must be unique, this is how it's referenced
  • any
    • the game just basically copy-and-pastes it into whatever referenced it, after replacing any parameter names (enclosed in square brackets) with their values in that context

the syntax for referencing a variable is to have:

  • a $ as the first non-whitespace on a line
  • followed immediately by the name of the variable
  • followed by zero or more:
    • one space
    • followed immediately by a parameter name (alpha-numeric, starts with a letter)
    • followed immediately by an equal sign
    • followed immediately by the parameter value (alpha-numeric, most symbols probably work, no spaces)


this is the main thing being defined, but the node can also be used within a bullet's spawn sub-node

  • name (string, required unless this is within a spawn node)
    • Must be unique across all BulletPattern's being loaded by the game
  • non_sim_interval (float)
    • for checked-every-frame non-sim bullet patterns (like how entity's invincibility_non_sim_special_bullet_patterns is used), this causes the pattern to fire every (interval) seconds rather than simply every frame
  • bullet


this is the main point of defining anything, and corresponds to a single GameEntity spawned by the firing system

  • angle (float)
    • the angle of the shot's initial course, relative to the angle to the target (or to the angle of the shot spawning this, if this is a nested pattern)
  • speed (float)
    • the speed of the shot's initial course
  • dumbfire (bool, optional)
    • the shot's initial angle is relative to the firing ship's rotation, rather than to the angle to the target
  • shot_type (GameEntity name, optional)
    • if set, makes this shot this entity type, rather than whatever the system normally produces
  • damage_type (DamageType, optional)
    • if set, makes this shot do this kind of damage, rather than what the system specifies
  • interval_mult (int, optional)
    • if > 1, makes the pattern only do this bullet every N times. So if it this is 3 then this bullet entry will be processed on the system's 1st firing, 4th firing, 7th firing, etc
  • requires_difficulty_at_least (DifficultyType, optional)
    • If the difficulty is set lower than the specified one, this bullet does not fire.
  • requires_difficulty_at_most (DifficultyType, optional)
    • If the difficulty is set higher than the specified one, this bullet does not fire.
  • damage_mult (float, optional)
    • multiplies the damage done by the shot
  • behavior (FlockBehaviorType, optional)
    • makes the shot use the designated behavior, for example PathfindingAttacker, instead of normal shot movement logic; note that this will tend to override any angle/etc setting in the pattern
  • requires_parent_scale_less_than_or_equal_to (float, optional)
    • if the firing entity's scale (visual size multiplier) is currently greater than this, the bullet entry is not processed
  • requires_parent_scale_greater_than_or_equal_to (float, optional)
    • if the firing entity's scale is currently less than this, the bullet entry is not processed
  • requires_parent_health_percent_less_than_or_equal_to (float, optional)
    • if the firing entity's health percent (expressed between 0 and 100) is greater than this, the bullet entry is not processed
  • requires_parent_health_percent_greater_than_or_equal_to (float, optional)
    • if the firing entity's health percent (expressed between 0 and 100) is less than this, the bullet entry is not processed
  • requires_firing_system_range_to_player_less_than_or_equal_to (float, optional)
    • if the firing entity's range to the player is greater than this, the bullet entry is not processed
  • requires_firing_system_range_to_player_greater_than_or_equal_to (float, optional)
    • if the firing entity's range to the player is less than this, the bullet entry is not processed
  • is_invisible_and_does_not_collide_with_ships (bool, optional)
    • the shot does not render and does not collide with ships; still moves normally and can collide with terrain
      • this would be useful for a bullet that should spawn something when it either expires from duration or hits a wall
  • on_death_ability_explosion_radius (int, optional)
    • if > 0, the size of the shot-clearing pulse when this shot is removed from the game
  • spawns_offset_from_firing_entity (ArcenPoint, optional)
    • sets the origin of the shot relative to the center of the firing entity. Primarily useful for quasi-melee weapons
  • moves_with_firing_entity (bool, optional)
    • the shot maintains its position relative to the firing entity, as that entity moves around. Primarily useful for quasi-melee weapons
  • rotates_around_firing_entity (bool, optional)
    • the shot rotates with the firing entity, combined with the previous flag it updates its position to also stay constant relative to the firing ship's central axis (so if it's to the upper right, and you do a 180, it will be to the lower left on the screen)
  • angle_delta_rand_min (float), angle_delta_rand_max (float)
    • For each bullet spawned by this definition, applies a rand(min,max) degrees delta to angle
  • non_sim_rotation_speed_rand_min (float), non_sim_rotation_speed_rand_max (float)
    • For each NonSim-only entity spawned by this definition, applies an ongoing rand(min,max) degrees-per-second rotation
  • conditional_spawn_rule (BulletConditionalSpawnRule)
    • whether the bullet should only spawn if the location is in a window area, etc.
  • spawn_offset (ArcenPoint)
    • offsets the initial position of this bullet by this X, Y (this does not consider any angles, rotations, etc)
  • follows_previously_spawned_entity (bool)
    • makes this entity follow the last entity spawned by this operation
    • for now only works with a pattern triggered by an entity's spawning_patterns attribute
    • also, only takes effect on an entity after the spawning bullet behavior has concluded (without a die tag); before that point the bullet pattern itself decides where it moves
  • do_not_do_rest_of_logic_on_early_death (bool)
    • normally when a bullet dies early (hits a ship, a wall, whatever) it runs all the rest of its logic immediately to avoid bullets-that-spawn-bullets getting "eaten" by such a circumstance
    • but some bullet logic is "infinite" or at least intended to loop for a very long time but not with the intention that the bullet actually survive that long; if one of these does the "do all the rest of your logic" thing it can seriously impact performance
    • so this flag allows that kind of long-looping logic and avoids it trying to spawn like six thousand bullets in one frame if the bullet dies
  • offset_is_relative_to_parent_bullet_instead_of_firing_entity (bool)
    • makes the various spawns_offset_from_firing_entity and various other "firing_entity" bullet pattern logic for this bullet refer to the bullet which spawned it rather than the entity holding the gun
    • if it ever can't find the parent bullet, it reverts to referring to the entity holding the gun
  • immune_to_interception_and_aoe (bool)
    • makes this bullet not be considered for interception by other shots, or for being cleared by a normal aoe explosion (like the player missiles)
  • never_collides_with_terrain (bool)
    • for performance reasons, if you're just spraying hundreds or thousands of bullets out in a pattern for the player to weave through without meaning them to ricochet around or whatever, please use this flag
    • basically, if what you're doing is more "wall" than "projectile", this here's your flag
  • uses_direct_location_control (bool)
    • necessary for change nodes that use the "location" attribute to work correctly
  • use_performance_sensitive_logic (bool)
    • like never_collides_with_terrain, use this when you've got sheets and sheets of bullets whose main purpose is to make the players dodge the pattern
    • this does not set never_collides_with_terrain
    • what it does do is not even check for the logic that uses:
      • ShotsDriftTowardsHostileShips_Range
      • ShotIsNotStoppedByHitting
      • ShotsDestroyOtherShots
      • MovesTowardReticule
      • GravityPullPerSecond
      • so if you need those behaviors, don't use this flag, but if you need curtains of bullets that ALL use these behaviors then perhaps you should revisit some design decisions
      • all this is what happens from the shot itself; other things with gravity/etc will still look at and affect the shot normally
  • initial_angle_is_absolute (bool)
    • makes the initial angle of the bullet not be relative either to the angle from the firing entity to the target entity or to the rotation of the firing entity
  • modifier, wait, die, change, spawn, spawn_entity, loop

Note: all of these except "modifier" are "actions", and happen in sequence after the shot's creation. "modifier" nodes can be anywhere in the list, but their order is not significant (for consistency please put them all at the top of the node body, above all actions)

All action nodes can take this attribute:

  • sounds_to_pick_from_when_action_starts (List<string>)
    • when this action starts, if this list has any elements, it picks one at random and plays it


this just makes the shot continue whatever it was doing for a certain period of time before proceeding to the next action

  • time (float)
    • seconds until the bullet moves to the next step


this makes the shot go away

  • instant (bool, optional)
    • if set, the bullet disappears instantly when it hits this step. Otherwise it starts the scaling-down process that removes it shortly thereafter.


this changes some aspect of the shot's movement, often doing that over a period of time.

  • time (float)
    • how long the change takes, in seconds. During the change the shot will interpolate (lerp) between its state at the beginning of the change and the target state
      • a value of 0, or simply omitting this attribute, can be used for "instant" changes
  • time_rand (two floats separated by a comma, optional)
    • the random component for the time attribute
      • so if you have <change time="4" time_rand="-1,1" /> then the change will actually take some time between 3 and 5 seconds
  • relative (bool or special string, optional)
    • if this is "ToPlayer" then the angle is set relative to the angle to the player at the beginning of this change
      • this doesn't make sense combined with changes other than angle
    • if this is "true" then all changes in this action are considered relative to the shot's state at the beginning of the action. That way you can speed up by 10 by just putting speed="10" rather than computing it in your head while writing the pattern
  • speed (float, optional)
    • the target speed (or delta, if relative is true)
  • speed_rand (two floats separated by a comma, optional)
    • the random component for the speed attribute
  • angle (float, optional)
    • the target angle (or delta, if relative is true)
  • angle_rand (two floats separated by a comma, optional)
    • the random component for the angle attribute
  • offset_from_parent (ArcenPoint, optional)
    • the target offset from the parent (or delta, if relative is true), useful with on bullet entries with the moves_with_firing_entity flag when making quasi-melee weapons swing around
  • parent_scale (float)
    • the target scale of the parent entity (or delta, if relative is true); 1 is normal, 0 is basically-not-there
      • "parent" means the ship with the system that fired this bullet (or the shot that spawned this bullet)
  • self_scale (float)
    • the target scale of this bullet (or delta, if relative is true); 1 is normal, 0 is basically-not-there
  • non_sim_diffuse (Color)
    • the target diffuse of this NonSim-only bullet (or delta, if relative is true)
  • angle_from_parent (float), distance_from_parent (float)
    • these can be used instead of offset_from_parent to move the bullet in "orbital" terms
    • maintaining distance while changing angle will result in the bullet moving in something like an arc around the firing entity
  • location (Vector2)
    • the location in pixels, relative to the center of the room, for the bullet to move toward during this action
    • if the change is relative, this is relative to the entity's location at the beginning of this change
    • basically overrides any speed or collision or whatever the entity may have: it will definitely reach the target point exactly at the end of the duration of this action (instantly, if no duration set)


this creates more shots

  • must contain exactly one bullet_pattern node


this creates something... anything really, though the game will get really weird if you spawn another player-type hull or something like that

  • name (GameEntity type name)
    • the name of the entity type to spawn
  • spawn_at_parent (bool, optional)
    • the entity will spawn at the firing ship's location; otherwise it spawns at the shot's current location


this makes the shot do the same thing multiple times in a row, without you having to repeat yourself. Code duplication is the enemy!

  • iterations (int)
    • the number of times to perform the body
    • if this is negative, the loop goes forever
  • take_pants_off_head (bool)
    • suppresses, for this loop tag, the longstanding (all the way back to TLF) bug where on iterations after the first it would often skip the first action in the loop
    • for the less sarcastic content contributors, you can use an alias for this new attribute: suppress_loop_bug
    • this defaults to false because many of the existing loop tags would cause infinite-loop behavior if the bug were fixed for them
  • wait, die, change, spawn, spawn_entity, loop