Modding:Active Parts

From Caves of Qud Wiki
Revision as of 22:30, 15 September 2022 by Sol (talk | contribs) (add missing parens to constructor definition)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
This page is about modding. See the modding overview for an abstract on modding.
This page is about modding. See the modding overview for an abstract on modding.

Many object parts (the entity components specified in ObjectBlueprints.xml with part tags) are based on IActivePart architecture. IActivePart provides standardized support functionality for parts that actively, conditionally enact behavior. Much of this support can be powerfully reconfigured at the object blueprint level without requiring scripting intervention, though whether a given variation in behavior will actually work depends on the quality of the final part's integration with IActivePart.

The most crucial concepts to how active parts work are subjects, the objects the part operates on, and status, the part's operational state. The basic form of a part's integration with its IActivePart support is for it to check its status, and if it is operational, to apply its behavior to its subjects.

Statuses

These are the possible active part statuses. Only the status Operational represents working functionality; other statuses are failure modes.

Statuses are evaluated in the order listed. If more than one status potentially applies, the earlier one is used.

Active Part Statuses
NeedsSubject
SwitchedOff
EMP
Broken
Rusted
Booting
NotHanging
LimbIncompatible
RealityStabilized
LocallyDefinedFailure
PrimarySystemOffline
Unpowered
Operational

Status-determining configuration points

The following configuration points control the determination of an active part's status. Defaults indicated are at the IActivePart level. Many individual parts set different defaults, and in particular see IPoweredPart.

Individual parts can also override the method public virtual bool GetActivePartLocallyDefinedFailure() to implement their own failure modes; if this method returns true, the part's status will be LocallyDefinedFailure. Generally public virtual string GetActivePartLocallyDefinedFailureDescription() should also be overridden; its return value will be used instead of "LocallyDefinedFailure" for status display (see below).

Name Type Behavior if enabled Status on failure Default
ChargeUse int An amount of charge the part consumes in order to operate. Fails if the parent object does not have at least this much charge available. The implementing part chooses when to actively consume charge; when it does, this amount will be consumed unless overridden. Unpowered 0
ChargeMinimum int An amount of charge the part requires to be available in order to operate. Fails if the parent object does not have at least this much charge available. Unpowered 0
IsBootSensitive bool Looks for a sibling BootSequence part; fails if it finds one and its boot sequence is currently in progress. Booting false
IsBreakageSensitive bool Fails if the parent object is Broken. Broken true
IsEMPSensitive bool Fails if the parent object is Pulsed. EMP false
IsHangingSensitive bool Looks for a sibling Hangable part; fails if it finds one and it is not hanging. NotHanging false
IsPowerSwitchSensitive bool Looks for a sibling PowerSwitch part; fails if it finds one and it is switched off. SwitchedOff false
IsRealityDistortionBased bool Fails if normality in the parent object's immediate context prevents reality distortion. RealityStabilized false
IsRustSensitive bool Fails if the parent object is Rusted. Rusted true
NeedsOtherActivePartOperational string Looks for a sibling active part of the name specified. Fails if it does not find one, or that part's status is not Operational. PrimarySystemOffline null
NeedsOtherActivePartEngaged string Looks for a sibling active part of the name specified. Fails if it does not find one, or that part returns false from the method IsActivePartEngaged(). (By default, this is the same as a check for Operational status, but particular parts can override the method to implement different behavior, for whatever usage of "engaged" is desired.) PrimarySystemOffline null
RequiresBodyPartCategory string Fails if the parent object is not equipped on a body part of the category specified. The body part categories are "Animal", "Arthropod", "Plant", "Fungal", "Protoplasmic", "Cybernetic", "Mechanical", "Metal", "Wooden", "Stone", "Glass", "Leather", "Bone", "Chitin", "Plastic", "Cloth", "Psionic", and "Extradimensional". LimbIncompatible null

Subject-determining configuration points

The following boolean configuration points control identification of an active part's subject or subjects. All default to false at the IActivePart level. Many individual parts set different defaults.

Individual parts can also override the method public virtual bool WorksFor(GameObject obj) in order to implement additional restrictions on what objects are valid subjects.

If an active part has no valid subjects, its status will be NeedsSubject.

Where the parent object's current cell is referred to, it only applies to a cell the parent object is directly present in (not a cell its carrier or equipper is in, for example).

Name Behavior if enabled
MustBeUnderstood Filters the results from other settings. The player is only a valid subject if they understand the parent object (i.e. they have it identified). Other objects are only valid subjects if they have an Intelligence stat.
WorksOnAdjacentCellContents Objects in cells adjacent to the parent object's current cell are valid subjects.
WorksOnCarrier The parent object's InInventory (i.e. an object containing the parent object as inventory) is a valid subject. An object that has the parent object equipped as a thrown weapon is also a valid subject.
WorksOnCellContents Objects in the parent object's current cell, other than the parent object, are valid subjects.
WorksOnEnclosed An object the parent object has Enclosed via the Enclosing part is a valid subject.
WorksOnEquipper An object that has the parent object "equipped properly" is a valid subject, which means being bound to a body part or body parts appropriate to the object's design. For an implant, this means being implanted. Other objects can have their body part binding altered, for example by being magnetized, in which case the object is properly equipped when bound in its new fashion (for magnetized items, this means being in a Floating Nearby slot). Otherwise, specific item categories define proper binding. Armor binds to body parts appopriately to its type. Shields normally bind to hands or arms. Missile weapons normally bind to missile weapon slots. Melee weapons (which anything that doesn't fall into one of these categories is by default) normally bind to hands.
WorksOnHolder An object that has the parent object equipped in a hand or missile weapon slot is a valid subject.
WorksOnImplantee An object that has the parent object implanted is a valid subject.
WorksOnInventory Objects in the parent object's inventory, if any, are valid subjects.
WorksOnSelf The parent object is a valid subject.
WorksOnWearer An object that has the parent object equipped on the appropriate body part for its armor or shield configuration is a valid subject.

Status display configuration points

These fields control the display of the active part's status in the parent object's short description.

Name Type Behavior if set Default
DescribeStatusForProperty string Status will be described if the player has a positive value for this int property. This field is not normally used directly; usually it is set via IsTechScannable. null
IsBioScannable bool Sets DescribeStatusForProperty to the property corresponding to bioscanning ("BioScannerEquipped") and StatusStyle to "bio". false
IsStructureScannable bool Sets DescribeStatusForProperty to the property corresponding to structural scanning ("StructureScannerEquipped") and StatusStyle to "structure". false
IsTechScannable bool Sets DescribeStatusForProperty to the property corresponding to techscanning ("TechScannerEquipped") and StatusStyle to "tech". false
NameForStatus string Replaces the default name shown for the part in status display. The default is the name of the part class (so this is particularly crucial for very generic parts like IntPropertyChanger). null
StatusStyle string The display style to use for status if it is displayed. The possible styles are "angry", "bio", "leet", "ooc", "plain", "structure", and "tech". This field is not normally used directly; usually it is set via IsTechScannable. "plain"

Other configuration points

Name Type Behavior if set Default
IsPowerLoadSensitive bool If true, the parent object becomes eligible to receive the Overloaded mod if it was not already, charge consumption processed via IActivePart support methods is increased as standard for that part, and the results of MyPowerLoadBonus() and MyPowerLoadLevel() are adjusted appropriately. false
ReadyColorString string When the part determines its status to be Operational, it sets the ColorString field on the parent object's Render part to this value. null
ReadyDetailColor string When the part determines its status to be Operational, it sets the DetailColor field on the parent object's Render part to this value. null
DisabledColorString string When the part determines its status to be other than Operational, it sets the ColorString field on the parent object's Render part to this value. null
DisabledDetailColor string When the part determines its status to be other than Operational, it sets the DetailColor field on the parent object's Render part to this value. null

IPoweredPart

The majority of active parts inherit IPoweredPart, which is a variant of IActivePart intended for more technical applications. It sets the following defaults:

Field Default
ChargeUse 1
IsBootSensitive true
IsEMPSensitive true
IsPowerSwitchSensitive true
IsTechScannable true

Scripting integration

For best results, it's recommended to have read the following topics before this one:

Introduction

The most common way for parts to use IActivePart support is the IsReady() method. The example part below heals its parent object every turn:

using System;

namespace XRL.World.Parts
{

    [Serializable]
    public class HealSelfEveryTurn : IActivePart
    {

        public HealSelfEveryTurn()
        {
            WorksOnSelf = true;
        }

        public override bool WantEvent(int ID, int cascade)
        {
            return
                base.WantEvent(ID, cascade)
                || ID == EndTurnEvent.ID
            ;
        }

        public override bool HandleEvent(EndTurnEvent E)
        {
            if (IsReady(UseCharge: true))
            {
                ParentObject.Heal(1);
            }
            return true;
        }

    }

}

In this very basic integration, the UseCharge: true parameter makes the part consume the amount of charge in its ChargeUse field. Without further configuration, this will have defaulted to 0. We want to call for charge to be used as the appropriate time to enable specific configurations that do use charge to be created.

The part above is directly acting on its parent object, without checking any information about subjects provided by IActivePart. It sets WorksOnSelf because without any identifiable subject, the part will not be operational. Instances could set other subject-determining configuration points, which could change when the part works (since it won't work without a subject), but won't change what object the healing is applied to. A more flexible integration could look like:

using System;

namespace XRL.World.Parts
{

    [Serializable]
    public class HealEveryTurn : IActivePart
    {

        public override bool WantEvent(int ID, int cascade)
        {
            return
                base.WantEvent(ID, cascade)
                || ID == EndTurnEvent.ID
            ;
        }

        public override bool HandleEvent(EndTurnEvent E)
        {
            if (IsReady(UseCharge: true))
            {
                foreach (GameObject obj in GetActivePartSubjects())
                {
                    obj.Heal(1);
                }
            }
            return true;
        }

    }

}

This part will need subject-determining configuration before it can operate. It will apply its healing to whatever subject or subjects are identified by that configuration, though, making it more powerful for designers to work with.

Methods

IsReady()

public bool IsReady(
    bool UseCharge = false,
    bool IgnoreCharge = false,
    bool IgnoreBootSequence = false,
    bool IgnoreBreakage = false,
    bool IgnoreRust = false,
    bool IgnoreEMP = false,
    bool IgnoreRealityStabilization = false,
    bool IgnoreSubject = false,
    bool IgnoreLocallyDefinedFailure = false,
    int MultipleCharge = 1,
    int? ChargeUse = null,
    bool UseChargeIfUnpowered = false
)
Parameter Behavior
UseCharge If true, an attempt will be made to consume any charge required by the part if it is not otherwise disabled. If false, charge will be checked for but not consumed.
IgnoreCharge If true, all considerations related to charge will be ignored.
IgnoreBootSequence If true, IsBootSensitive will be ignored.
IgnoreBreakage If true, IsBreakageSensitive will be ignored.
IgnoreRust If true, IsRustSensitive will be ignored.
IgnoreEMP If true, IsEMPSensitive will be ignored.
IgnoreRealityStabilization If true, IsRealityDistortionBased will be ignored.
IgnoreSubject If true, the part will not check for subjects or consider itself to be in SubjectNeeded status.
IgnoreLocallyDefinedFailure If true, GetActivePartLocallyDefinedFailure() will not be checked and the part will not consider itself to be in LocallyDefinedFailure status.
MultipleCharge Multiplies charge use in a fashion intended to represent operation over multiple turns. This means that ChargeMinimum is not multiplied, and that if charge is being consumed and not enough charge is available for the full set of multiples of charge use, charge will be consumed for however many multiples can be accommodated.
ChargeUse Overrides the part's ChargeUse setting for purposes of this call. The default of null means to use the part's ChargeUse setting.
UseChargeIfUnpowered With UseCharge: false, this parameter triggers consuming charge in the event that the part is otherwise operational but does not have enough charge. This is to allow certain process flows to ensure that they'll consume the last charge available in their parent object instead of leaving a tiny bit unused (with liquid-fueled energy cells, leaving a bit behind means leaving 1 dram of liquid, which creates confusing UX).

Returns true if the part's status is Operational. Triggers any appropriate render color changes. Updates the last known status.

IsDisabled()

public bool IsDisabled(
    bool UseCharge = false,
    bool IgnoreCharge = false,
    bool IgnoreBootSequence = false,
    bool IgnoreBreakage = false,
    bool IgnoreRust = false,
    bool IgnoreEMP = false,
    bool IgnoreRealityStabilization = false,
    bool IgnoreSubject = false,
    bool IgnoreLocallyDefinedFailure = false,
    int MultipleCharge = 1,
    int? ChargeUse = null,
    bool UseChargeIfUnpowered = false
)

The same as IsReady(), but with the opposite return value.

GetActivePartStatus()

public ActivePartStatus GetActivePartStatus(
    bool UseCharge = false,
    bool IgnoreCharge = false,
    bool IgnoreBootSequence = false,
    bool IgnoreBreakage = false,
    bool IgnoreRust = false,
    bool IgnoreEMP = false,
    bool IgnoreRealityStabilization = false,
    bool IgnoreSubject = false,
    bool IgnoreLocallyDefinedFailure = false,
    int MultipleCharge = 1,
    int? ChargeUse = null,
    bool UseChargeIfUnpowered = false
)

Like IsReady(), but returns an ActivePartStatus that can be used to determine what failure mode the part is in. Useful for constructing more helpful failure messaging.

Triggers any appropriate render color changes. Updates the last known status.

WasReady()

public bool WasReady()

Returns whether the active part's status was Operational the last time it was evaluated.

WasDisabled()

public bool WasDisabled()

Returns whether the active part's status was other than Operational the last time it was evaluated.

GetLastActivePartStatus()

public ActivePartStatus GetLastActivePartStatus()

Returns what the active part's status was the last time it was evaluated.

ConsumeCharge()

public bool ConsumeCharge(int? ChargeUse = null)

Triggers consumption of charge from the parent object, irrespective of operational status. The ChargeUse parameter can be used to override the part's ChargeUse setting; the default of null means do not override.

public bool ConsumeCharge(int MultipleCharge, int? ChargeUse = null)

As above, but MultipleCharge has the same behavior as for IsReady().

ConsumeChargeIfOperational()

public bool ConsumeChargeIfOperational(
    bool IgnoreBootSequence = false,
    bool IgnoreBreakage = false,
    bool IgnoreRust = false,
    bool IgnoreEMP = false,
    bool IgnoreRealityStabilization = false,
    bool IgnoreSubject = false,
    bool IgnoreLocallyDefinedFailure = false,
    int MultipleCharge = 1,
    int? ChargeUse = null,
    bool UseChargeIfUnpowered = false,
    bool NeedStatusUpdate = false
)

Triggers consumption of charge from the parent object if the part is operational. Parameters that are also present in IsReady() behave the same as there.

Normally, status will only be obtained (and therefore color changes triggered and last known status updated) if there is any charge consumption to be performed. NeedStatusUpdate can be set to true to indicate that status should always be obtained.

GetActivePartSubjects()

public virtual List<GameObject> GetActivePartSubjects()

Returns a list of the active part's current subjects.

GetActivePartFirstSubject()

public virtual GameObject GetActivePartFirstSubject()

Returns the active part's first subject, or null if it has none.

public virtual GameObject GetActivePartFirstSubject(Predicate<GameObject> Filter)

Returns the first of the active part's subjects that matches the filter specified, or null if none do.

IsObjectActivePartSubject()

public virtual bool IsObjectActivePartSubject(GameObject obj)

Returns whether the object specified is one of the active part's subjects.

ActivePartHasMultipleSubjects

public virtual bool ActivePartHasMultipleSubjects()

Returns whether the active part has at least two subjects.

GetActivePartSubjectCount()

public virtual int GetActivePartSubjectCount()

Returns how many subjects the active part has.

ForeachActivePartSubjectWhile()

public virtual bool ForeachActivePartSubjectWhile(Predicate<GameObject> pProc, bool MayMoveAddOrDestroy = false)

Calls pProc on each of the active part's subjects, terminating if pProc returns false.

MayMoveAddOrDestroy should be set to true if the code in pProc might result in any object being moved, created, or destroyed; otherwise, there is a risk of exceptions being thrown due to loop control disruption.

AnyActivePartSubjectWantsEvent()

public virtual bool AnyActivePartSubjectWantsEvent(int ID, int cascade)

For integration with the MinEvent system. Returns true if any of the active part's subjects want to receive the event designated.

ActivePartSubjectsHandleEvent()

public virtual bool ActivePartSubjectsHandleEvent(MinEvent E)

For integration with the MinEvent system. Sends the event to each of the active part's subject, terminating early and returning false if any of the event handlers return false.

GetActivePartLocallyDefinedFailure()

public virtual bool GetActivePartLocallyDefinedFailure()

Intended to be overridden by inheritors to define their own failure conditions. Returns false by default; a locally defined failure will be considered to exist if it returns true.

GetActivePartLocallyDefinedFailureDescription()

public virtual string GetActivePartLocallyDefinedFailureDescription()

Intended to be overridden by inheritors to define the description to provide in status display for their own failure conditions. Returns null by default; a non-null return value will be used in place of "LocallyDefinedFailure" when displaying a LocallyDefinedFailure status.

IsActivePartEngaged()

public virtual bool IsActivePartEngaged()

Intended to be overridden by inheritors to define whether the part is "engaged" for purposes of NeedsOtherActivePartEngaged. Returns IsReady() by default.

GetStatusSummary()

public virtual string GetStatusSummary(ActivePartStatus Status)

For the specified status value, returns a string summary intended for player visibility. Some statuses return null, generally because it makes less sense to present them to the player. The status summaries are:

Status Summary
EMP "{{W|EMP}}"
Unpowered "{{K|unpowered}}"
SwitchedOff "{{K|switched off}}"
Booting (only if BootSequence.IsObvious() is true) "{{b|warming up}}"
NeedsSubject null
Operational null
any other status "{{r|nonfunctional}}"

Can be overridden by inheritors to provide a status summary for LocallyDefinedFailure status or otherwise alter the default behavior.

public string GetStatusSummary()

Returns GetStatusSummary(GetActivePartStatus()).

AddStatusSummary()

public void AddStatusSummary(StringBuilder SB)

Retrieves GetStatusSummary() and, if it is not null or empty, appends " (", the summary, and ")" to the StringBuilder passed. This is to support code like this:

public override bool HandleEvent(GetShortDescriptionEvent E)
{
    E.Postfix.AppendRules(AppendRulesDescription, AddStatusSummary);
    return true;
}

GetOperationalScopeDescription()

public string GetOperationalScopeDescription()

Returns a readable general description of what objects the part operates on, based on its subject-determining configuration points. Example return values might be "its user" or "itself and its vicinity".

MyPowerLoadBonus()

public override int MyPowerLoadBonus(int Load = int.MinValue, int Baseline = 100, int Divisor = 150)

Returns a value intended to be used as a performance bonus based on power load, integrating with IsPowerLoadSensitive and ModOverloaded (and any other future features which may modify power load level). When the Load argument is set to the default int.MinValue, the current power load level will be automatically calculated and used, otherwise the level specified will be used (this is supported so that an already known level may be sent, for efficiency). The return value is ((WorkingLoadValue - Baseline) / Divisor). With IsPowerLoadSensitive set to true and ModOverloaded present, the return value of this method will be 2 (because ModOverloaded increases the power load value from 100 to 400, so ((400 - 100) / 150) = 2). Different Divisor numbers are typically used with different applications depending on what performance characteristics are desired. If IsPowerLoadSensitive is false, this method will return 0.

This method is an overload of the same method present in the base IPart, which normally returns values based on similar logic (without sensitivity to any configuration value like IsPowerLoadSensitive, since IPart has nothing similar).

MyPowerLoadLevel()

public override int MyPowerLoadLevel()

Returns the power load level the part is currently operating under. If IsPowerLoadSensitive is false, it will always be 100. If IsPowerLoadSensitive is true and ModOverloaded is present, it will be 400. Other functionality may potentially result in different power load levels in the future.

List of active parts

Active Parts
AccelerativeTeleporter
ActiveLightSource
AddsMutationOnEquip
AddsRep
AdjustSpecialEffectChances
AilingQuickness
AloePorta
AmbientCollector
AnimateObject
ArtifactDetection
ArtificialIntelligence
AutomatedExternalDefibrillator
Banner
Bed
BioAmmoLoader
BleedingOnHit
BootSequence
BroadcastPowerReceiver
BroadcastPowerTransmitter
BurnMe
Campfire
CannotBeInfluenced
Capacitor
CardiacArrestOnHit
CatacombsExitTeleporter
Chair
ChargeSink
Circuitry
Clockwork
CompanionCapacity
ComputeNode
CrossFlameOnStep
Cursed
CyberneticsAutomatedInternalDefibrillator
CyberneticsBiodynamicPowerPlant
CyberneticsEffectSuppressor
CyberneticsMedassistModule
CyberneticsMicromanipulatorArray
CyberneticsOnboardRecoilerImprinting
CyberneticsOnboardRecoilerTeleporter
CyberneticsPenetratingRadar
CyberneticsTerminal2
DamageContents
DecoyHologramEmitter
DeploymentMaintainer
DepositCorpses
DestroyMe
DiggingTool
DischargeOnHit
DischargeOnStep
DismemberAdjacentHostiles
Displacement
Displacer
Drill
DrinkMagnifier
ElectricalPowerTransmission
EmergencyTeleporter
EmitGasOnHit
Enclosing
EnergyAmmoLoader
EnergyCell
EnergyCellRack
EnergyCellSocket
Engulfing
EquipCharge
EquipIntProperties
EquipStatBoost
FabricateFromSelf
Fan
FeelingOnTarget
FireSuppressionSystem
FlareCompensation
Flywheel
FoliageCamouflage
FollowersGetTeleport
FoodProcessor
ForceEmitter
ForceProjector
FugueOnStep
FungalFortitude
FusionReactor
Gaslight
GasTumbler
GenericPowerTransmission
GenericTerminal
GlimmerAlteration
GrandfatherHorn
GritGateMainframeTerminal
GroundOnHit
HighBitBonus
HologramMaterialPrimary
HUD
HydraulicPowerTransmission
HydroTurbine
InductionCharger
InductionChargeReceiver
IntegralRecharger
IntegratedPowerSystems
IntPropertyChanger
ItemElements
LatchesOn
LeaksFluid
LifeSaver
LiquidFueledEnergyCell
LiquidFueledPowerPlant
LiquidProducer
LiquidPump
LiquidRepellent
LowStatBooster
MagazineAmmoLoader
MechanicalPowerTransmission
MechanicalWings
MentalScreen
Mill
MissilePerformance
Mod* (IModification, which all mods inherit, is an IActivePart)
MultiIntPropertyChanger
MultiNavigationBonus
NavigationBonus
NightSightInterpolators
NightVision
NoKnockdown
PartsGas
PointDefense
Pounder
PoweredFloating
PowerSwitch
ProgrammableRecoiler
PsychicMeridian
RadiusEventSender
RealityStabilization
RealityStabilizeOnHit
ReclamationCist
RecoilOnDeath
ReduceCooldowns
ReduceEnergyCosts
ReflectProjectiles
RemotePowerSwitch
RespondToEvent
RocketSkates
SapChargeOnHit
SaveModifier
SaveModifiers
SlipRing
SlottedCellCharger
Smartgun
SolarArray
Stopsvaalinn
StrideMason
StunOnHit
Suspensor
SwapOnHit
TattooGun
Teleporter
TeleportGate
TeleporterPair
TemperatureAdjuster
TemplarPhylactery
ThermalAmp
Toolbox
UniversalCharger
UrbanCamouflage
VampiricWeapon
VibroWeapon
Waldopack
WaterRitualDiscount
WindTurbine
Windup
ZeroPointEnergyCollector
ZoneAdjust