Introduction

<ShipClass> types are used to define NPCs, wingmen, and playable ship classes. For example, the Wolfen-class ships that guard Commonwealth stations are NPCs defined in scWolfen. Volkov is defined in the scWolfenVolkov ship class, and the playable ship class is scWolfenPlayer.

In previous versions, these three ship classes were independent, but this led to some problems: (1) it was impossible to share common attributes, such as thruster-effect definitions, (2) it was impossible for wingmen to switch ship classes.

This article contains guidelines for defining ship classes to overcome those problems. In API 40 and above we recommend using type inheritance to share ship class configurations.

NPC Ships

NPC ships are non-wingman ships usually assigned to defend a station (friendly or enemy). These have generic behavior (e.g., guard a station) and generally do not have custom code (such as event handlers).

In many cases, the NPC ship of a given ship class can serve as the base class for all other ships (including playable ships). We recommend the following:

  • Define a ship class with the standard configuration. For example, the scWolfen class should define the standard configuration for a Wolfen.
  • Part I ships should inherit from baHumanTechShip;; Part II ships should inherit from their specific tech base class.
  • Add the genericClass to indicate that this <ShipClass> should be used as the exemplary class. This is used to spawn the ship in the intro and to include the ship in various TransData outputs.
  • Define all classes limits such as stdArmor=, maxArmor=, maxDevices=, etc. Define these in the <Hull> element so they can be inherited by other classes.
  • Define any device slot settings in the <DeviceSlots> element. For example, if launchers have a specific position, they should be defined here in the NPC class (so other classes can inherit it).
  • Player HUD settings should be defined in this class (because we'll inherit from this).
  • In general, any custom logic (such as the code in freighters to report piracy) should be done in a derived class. Again, this is because other uses (such as player ship) need a base class to descend from and the custom logic is not desired.

Example

<ShipClass UNID="&scWolfen;"
      manufacturer="Pacific Defense"
      class="Wolfen"
      type="gunship"

      defaultSovereign="&svCommonwealth;"
      attributes="commonwealth, genericClass, pacificDefense"

      inherit="&baHumanTechShip;"
      >

   <Hull
      size="26"
      mass="30"
      cargoSpace="35"
      maxCargoSpace="100"
      ...
      />

   <Drive
      thrust="600"
      maxSpeed="25"
      powerUse="20"
      />

   <Maneuver
      maxRotationRate="12.0"
      rotationAccel="3.0"
      rotationStopAccel="12.0"
      />

   <DeviceSlots>
      <DeviceSlot criteria="p +property:omnidirectional;" ... />
      <DeviceSlot criteria="w" ... />
   </DeviceSlots>

   <Armor
      ...
      />

   <Devices>
      ...
   </Devices>

   <Image imageID="&rsWolfenGunshipHD;" ... />
   <HeroImage imageID="&rsWolfenLarge;" ... />

   <Effects>
      ...
   </Effects>

   <PlayerSettings>
      <ArmorDisplay>
         ...
      </ArmorDisplay>
   </PlayerSettings>

   <AISettings
      ...
      />

</ShipClass>

Ships with Special Behavior

We often need to create ships with custom behavior. For example, in a mission we might need a ship that behaves in a programmed way. We recommend the following techniques:

  • In general, we recommend creating ships using the plain NPC class. If possible, implement the custom behavior with a set of orders. For example, you issue several orders to a ship and the ship will carry them out in sequence.
  • If a list of orders is not enough, we recommend using either overlays or event handlers to modify ship behavior. Event handlers in particular can handle custom events and do almost anything that a custom ship class can do.
  • If the ship is a custom configuration of an NPC ship, then we recommend creating a new <ShipClass> that inherits from the plain NPC class. For example, if you need a Wolfen-class ship with a Makayev launcher, then you probably should create a new <ShipClass> that inherits from scWolfen and specify the devices. Then you can add a custom event handler, if necessary. These custom configurations should not have the genericClass attribute.

Wingmen

Permanent wingmen like Jenna, Rama, and Volkov have special requirements. Starting in version 1.8, you will be able to upgrade their ships to completely different classes. This implies that we cannot have any custom behavior on their original ship classes.

We recommend the following:

  • Define a new <ShipClass> type for the wingman and inherit from some NPC class. For example, Volkov's Wolfen inherits from scWolfen. Use the <Armor> and <Devices> elements to customize the ship.
  • Define a new <Type> to represent the character (as opposed to their ship). This type should inherit from baStdWingmanBase (or equivalent) and serve as an event handler. For example, Volkov defines a type called chVolkov.
  • The wingman's ship class should set its character and event handler to the newly defined type. Essentially, any special behavior should be contained in something like chVolkov instead of the ship class.
  • When the player buys a new ship for a wingman, the ship broker code will automatically transfer the character and event handler to the new ship, thus preserving the behavior of the wingman, even though they are in a new ship. The old ship could even be used by a different wingman or even the player.

Player Ships

Player ships need a new <ShipClass> type than inherits from the plain NPC class. Note, however, that almost all player-specific settings (e.g., HUD definitions and device limits) should be done in the NPC class.

The player class needs some attributes to define its starting location and its character class (e.g., unidPilgrimClass).

Ships for Purchase at Ship Brokers

You may define new <ShipClass> types for ships available at ship brokers. These classes should adhere to the following rules:

  • The new ship class should inherit from the plain NPC class. In general, the new ship class should only define new armor and device configurations (and inherit the rest from the NPC class).
  • The new ship class should include the shipBroker attribute to be potentially become available at ship brokers.
  • Different stations have different requirements for ships they buy and sell. Make sure the new ship class meets those requirements. For example, Commonwealth dry docks sell ships of levels 3-7 that have the commonwealth attribute but not the military attribute.
  • Do not add any custom behavior on these classes; otherwise they may not be usable by wingmen.

CanUpgradeTo

When the player decides to buy a new ship, either for a wingman or for themselves, we raise this event on the old ship. This must be handled by the wingman or player, generally in the event handler. If the event returns True then the we accept the new ship. Otherwise, we expect a string describing why the upgrade is unavailable.

This event is useful if the player is flying a mission-specific ship that should not be changed out. It also allows wingmen to have preferences for ships. For example, some wingmen might only accepts ships that are faster than .2 c.

CanUpgradeFrom

In addition, we send this event to the new ship. This allows us to design new ship classes that cannot be used by certain wingmen or the player. For example, we might create a ship class that only Rama likes, without having to change Rama's code.

If the event returns True then we accept the new ship. Otherwise, we expect a string describing why the upgrade is unavailable.

See Also

nms 2 Feb 2018:

Maybe the upgrade checks should return a string explaining why the upgrade isn't allowed, or a non-string if OK?

george moromisato 6 Feb 2018:

@nms: Good point! Fixed!

nms 6 Feb 2018:

Also, the proposed names of the events are confusing. It's unclear whether the current ship should be the subject or object of the phrase. Maybe just have the same event on each type? You have to pass both the old and new ships to each event so that, e.g. an event handler on a wingman can compare them.

relanat 6 Feb 2018:

I agree the proposed names are confusing.

george moromisato 8 Feb 2018:

The default wingman class refuses to upgrade to a cheaper ship. The code looks something like this:

<CanUpgradeTo>
   ; This event is called on the wingman when the player wants to give
   ; the wingman a new ship.

   (block (
      ; This is the new ship that the player wants us to use.
      (newShipObj (@ gData 'newShipObj))
      )

      (switch
         ; If the price of the new ship is less than our current ship, 
         ; then the wingman refuses.

         (leq (objGetProperty newShipObj 'price) (objGetProperty gSource 'price))
            (objTranslate gSource 'msgRefusesToDowngradeShip {
               name:(objGetName gSource)
               })

         ; Otherwise, we allow the upgrade
         True
         )
      )
</CanUpgradeTo>

If we were to combine both events into one, then it would have to look something like this:

<CanUpgrade>
   ; This event is called on the wingman when the player wants to give
   ; the wingman a new ship.

   (block (
      ; This is the old ship that the wingman has.
      (oldShipObj (@ gData 'oldShipObj))

      ; This is the new ship that the player wants us to use.
      (newShipObj (@ gData 'newShipObj))
      )

      (switch
         ; If we are the old ship that is being upgraded, then we allow the
         ; upgrade (the other ship will check to see if it is better).

         (= gSource oldShipObj)
            True

         ; If the price of the new ship is less that our current ship, 
         ; then the wingman refuses.

         (leq (objGetProperty newShipObj 'price) (objGetProperty gSource 'price))
            (objTranslate gSource 'msgRefusesToDowngradeShip {
               name:(objGetName gSource)
               })

         ; Otherwise, we allow the upgrade
         True
         )
      )
</CanUpgrade>

As you can see, it is slightly more complicated. What do you think?

nms 8 Feb 2018:

If the event is in a wingman's event handler, won't it only be run on the old ship? In this case you only want to run it once, and if you pass both ships you don't care which one it runs on. But I see that it's less convenient to only have one event when you're putting it on a ship class. Also, you probably don't want to override an existing event on a ship class. You want to check both. So maybe there should be three events. And maybe "Upgrade" isn't the right word, since it usually refers to equipment. So something like <CanSwapFromThisShip>, <CanSwapToThisShip>, and <CanSwapShips>.