1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
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)
}
}