df_ls_structure 0.3.0-rc.1

A language server for Dwarf Fortress RAW files
Documentation
use crate::{
    BpCriteriaTokenArg, InteractionToken, MaterialStateEnum, MaterialTokenArgWithLocalCreatureMat,
    SkillEnum,
};
use df_ls_core::{Clamp, Reference, ReferenceTo};
use df_ls_diagnostics::DiagnosticsInfo;
use df_ls_syntax_analysis::{Token, TokenDeserialize, TryFromArgumentGroup};
use serde::{Deserialize, Serialize};

/// Begin defining a new attack this creature/caste can use, including its name, and the body
/// part(s) used to perform the attack.
#[derive(Serialize, Deserialize, Clone, Debug, Default, TokenDeserialize, PartialEq, Eq)]
pub struct Attack {
    /// Arguments of the `ATTACK` token
    #[token_de(token = "ATTACK", on_duplicate_to_parent, primary_token)]
    pub reference_and_bp: Option<(Reference, AttackPerformerTokenArg)>, // TODO ref is the attack name
    /// The contact area of the attack, measured in % of the body part's volume. Note that all
    /// attack percentages can be more than 100%.
    #[token_de(token = "ATTACK_CONTACT_PERC")]
    pub attack_contact_perc: Option<u32>,
    /// Multiple strikes with this attack cannot be performed effectively.
    #[token_de(token = "ATTACK_FLAG_BAD_MULTIATTACK")]
    pub attack_flag_bad_multiattack: Option<()>,
    /// Attacks that damage tissue have the chance to latch on in a wrestling hold. The grabbing
    /// bodypart can then use the "shake around" wrestling move, causing severe, armor-bypassing
    /// tensile damage according to the attacker's body volume.
    #[token_de(token = "ATTACK_FLAG_CANLATCH")]
    pub attack_flag_canlatch: Option<()>,
    /// The attack is edged, with all the effects on physical resistance and contact area that it
    /// entails.
    #[token_de(token = "ATTACK_FLAG_EDGE")]
    pub attack_flag_edge: Option<()>,
    /// Multiple strikes with this attack can be performed with no penalty. The creature will use
    /// all attacks with this token at once.
    #[token_de(token = "ATTACK_FLAG_INDEPENDENT_MULTIATTACK")]
    pub attack_flag_independent_multiattack: Option<()>,
    /// Displays the name of the body part used to perform an attack while announcing it, e.g. "The
    /// weaver punches the bugbat with his right hand".
    #[token_de(token = "ATTACK_FLAG_WITH")]
    pub attack_flag_with: Option<()>,
    /// The penetration value of the attack, measured in % of the body part's volume. Requires
    /// `ATTACK_FLAG_EDGE`.
    #[token_de(token = "ATTACK_PENETRATION_PERC")]
    pub attack_penetration_perc: Option<Clamp<u16, 0, 15_000>>,
    /// Determines the length of time to prepare this attack and until one can perform this attack
    /// again. Values appear to be calculated in adventure mode ticks.
    #[token_de(token = "ATTACK_PREPARE_AND_RECOVER")]
    pub attack_prepare_and_recover: Option<(u32, u32)>,
    /// Usage frequency. `MAIN` attacks are 100 times more frequently chosen than `SECOND`.
    /// Opportunity attacks ignore this preference.
    #[token_de(token = "ATTACK_PRIORITY")]
    pub attack_priority: Option<AttackPriorityEnum>,
    /// Defines the skill used by the attack.
    #[token_de(token = "ATTACK_SKILL")]
    pub attack_skill: Option<SkillEnum>,
    /// The velocity multiplier of the attack, multiplied by 1000.
    #[token_de(token = "ATTACK_VELOCITY_MODIFIER")]
    pub attack_velocity_modifier: Option<u32>,
    /// Descriptive text for the attack.
    #[token_de(token = "ATTACK_VERB")]
    pub attack_verb: Option<(String, String)>,
    /// When added to an attack, causes the attack to inject the specified material into the
    /// victim's bloodstream.
    ///
    /// Once injected, the material will participate in thermal exchange within the creature - injecting
    /// something like molten iron (`INORGANIC:IRON:LIQUID`) would cause most unmodded creatures to
    /// melt (note that some of the injected material also splatters over the bodypart used to carry
    /// out the attack, so it should be protected appropriately).
    ///
    /// If the injected material has an associated syndrome with the `[SYN_INJECTED]` token, it will
    /// be transmitted to the victim. If the attack is blunt, the injected material lacks the
    /// `[ENTERS_BLOOD]` token, the attacked bodypart has no `[VASCULAR]` tissues, or the victim is
    /// bloodless, the material will splatter over the attacked body part instead.
    #[token_de(token = "SPECIALATTACK_INJECT_EXTRACT")]
    pub specialattack_inject_extract: Vec<(
        MaterialTokenArgWithLocalCreatureMat,
        MaterialStateEnum,
        u32,
        u32,
    )>,
    /// When this attack lands successfully, a specified interaction will take effect on the target
    /// creature. The attack must break the target creature's skin in order to work. This will take
    /// effect in worldgen as well. If the attack would break skin, the interaction will occur
    /// before the attack actually lands.
    #[token_de(token = "SPECIALATTACK_INTERACTION")]
    pub specialattack_interaction: Vec<ReferenceTo<InteractionToken>>,
    /// Successful attack draws out an amount of blood randomized between the min and max value.
    /// Beware that this will trigger any ingestion syndromes attached to the target creature's
    /// blood - for example, using this attack on a vampire will turn you into one too.
    #[token_de(token = "SPECIALATTACK_SUCK_BLOOD")]
    pub specialattack_suck_blood: Option<(u32, u32)>,
}

#[derive(Serialize, Deserialize, Clone, Debug, TokenDeserialize, PartialEq, Eq)]
#[token_de(enum_value)]
pub enum AttackPriorityEnum {
    #[token_de(token = "MAIN")]
    Main,
    #[token_de(token = "SECOND")]
    Second,
}
impl Default for AttackPriorityEnum {
    fn default() -> Self {
        Self::Main
    }
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub enum AttackPerformerTokenArg {
    /// Specifies the type of body part used to perform the attack; for example,
    /// `BODYPART:BY_CATEGORY:HORN` would mean 1 body part categorized as a horn is used to perform
    /// this attack (presuming the creature has such a body part).
    Bodypart(BpCriteriaTokenArg),
    /// Uses a group of many body parts attached to a "parent" bodypart to perform the attack, rather than
    /// just one; for example, `CHILD_BODYPART_GROUP:BY_CATEGORY:HEAD:BY_CATEGORY:TOOTH` will use all
    /// the teeth on 1 head the creature has.
    ChildBodypartGroup((BpCriteriaTokenArg, BpCriteriaTokenArg)),
    /// Uses all specific "sub-tissues" of a specific kind on a body part; for example,
    /// `CHILD_TISSUE_LAYER_GROUP:BY_TYPE:GRASP:BY_CATEGORY:FINGER:NAIL` means this attack will use
    /// all the nails, on all the fingers, of a specific "grasp" body part (ie, a hand).
    ChildTissueLayerGroup((BpCriteriaTokenArg, BpCriteriaTokenArg, Reference)),
}
impl Default for AttackPerformerTokenArg {
    fn default() -> Self {
        Self::Bodypart(BpCriteriaTokenArg::default())
    }
}

// Deserialize a token with following pattern: `[REF:attack_token_args:...]`
df_ls_syntax_analysis::token_deserialize_unary_token!(AttackPerformerTokenArg);

impl TryFromArgumentGroup for AttackPerformerTokenArg {
    fn try_from_argument_group(
        token: &mut Token,
        source: &str,
        diagnostics: &mut DiagnosticsInfo,
        add_diagnostics_on_err: bool,
    ) -> Result<Self, ()> {
        // Safe first argument (is not token_name) for error case
        let arg0 = match token.get_current_arg() {
            Ok(arg) => Ok(arg.clone()),
            Err(err) => Err(err),
        };
        let reference_arg0 =
            Reference::try_from_argument_group(token, source, diagnostics, add_diagnostics_on_err)?;
        let attack_performer = match reference_arg0.0.as_ref() {
            "BODYPART" => {
                let bodypart = BpCriteriaTokenArg::try_from_argument_group(
                    token,
                    source,
                    diagnostics,
                    add_diagnostics_on_err,
                )?;
                AttackPerformerTokenArg::Bodypart(bodypart)
            }
            "CHILD_BODYPART_GROUP" => {
                let child_bodypart_group =
                    <(BpCriteriaTokenArg, BpCriteriaTokenArg)>::try_from_argument_group(
                        token,
                        source,
                        diagnostics,
                        add_diagnostics_on_err,
                    )?;
                AttackPerformerTokenArg::ChildBodypartGroup(child_bodypart_group)
            }
            "CHILD_TISSUE_LAYER_GROUP" => {
                let child_tissue_layer_group =
                    <(BpCriteriaTokenArg, BpCriteriaTokenArg, Reference)>::try_from_argument_group(
                        token,
                        source,
                        diagnostics,
                        add_diagnostics_on_err,
                    )?;
                AttackPerformerTokenArg::ChildTissueLayerGroup(child_tissue_layer_group)
            }
            _ => {
                Self::diagnostics_wrong_enum_type(
                    &arg0?,
                    vec![
                        "BODYPART",
                        "CHILD_BODYPART_GROUP",
                        "CHILD_TISSUE_LAYER_GROUP",
                    ],
                    source,
                    diagnostics,
                    add_diagnostics_on_err,
                );
                return Err(());
            }
        };
        Ok(attack_performer)
    }
}