xiv_emote_parser/log_message/ast/
condition.rs1use std::borrow::Cow;
6
7use thiserror::Error;
8
9pub use crate::log_message::types::Gender;
10
11use super::types::{FuncName, Function, IfParam, Obj, Param, Tag, TagName};
12
13#[derive(Debug, Clone, Copy)]
16pub enum Condition {
17 IsSelfOrigin,
20 IsSelfTarget,
23 IsOriginFemale,
26 IsOriginFemaleNpc,
29 IsOriginPlayer,
32 IsTargetPlayer,
35}
36
37#[derive(Debug, Clone, Copy)]
40pub enum DynamicText {
41 NpcOriginName,
44 NpcTargetName,
47 PlayerOriginNameEn,
50 PlayerTargetNameEn,
53 PlayerOriginNameJp,
56 PlayerTargetNameJp,
59}
60
61pub trait ConditionAnswer {
62 fn as_bool(&self, cond: &Condition) -> bool;
63}
64
65pub trait DynamicTextAnswer {
66 fn as_str(&self, text: &DynamicText) -> Cow<'static, str>;
67}
68
69pub trait Answers: ConditionAnswer + DynamicTextAnswer {}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct Character {
73 pub name: Cow<'static, str>,
74 pub gender: Gender,
75 pub is_pc: bool,
76 pub is_self: bool,
77}
78
79impl Character {
80 pub const fn new(name: &'static str, gender: Gender, is_pc: bool, is_self: bool) -> Character {
81 Character {
82 name: Cow::Borrowed(name),
83 gender,
84 is_pc,
85 is_self,
86 }
87 }
88
89 pub fn new_from_string(name: String, gender: Gender, is_pc: bool, is_self: bool) -> Character {
90 Character {
91 name: Cow::from(name),
92 gender,
93 is_pc,
94 is_self,
95 }
96 }
97}
98
99#[derive(Debug, Clone)]
100pub struct LogMessageAnswers {
101 origin_character: Character,
102 target_character: Character,
103}
104
105#[derive(Debug, Clone, Error)]
106pub enum LogMessageAnswersError {
107 #[error("Only one character can be self")]
108 MultipleSelves,
109}
110
111impl LogMessageAnswers {
112 pub fn new(
113 origin_character: Character,
114 target_character: Character,
115 ) -> Result<LogMessageAnswers, LogMessageAnswersError> {
116 if origin_character.is_self
117 && target_character.is_self
118 && origin_character != target_character
119 {
120 Err(LogMessageAnswersError::MultipleSelves)
121 } else {
122 Ok(LogMessageAnswers {
123 origin_character,
124 target_character,
125 })
126 }
127 }
128
129 pub fn origin_character(&self) -> &Character {
130 &self.origin_character
131 }
132
133 pub fn target_character(&self) -> &Character {
134 &self.target_character
135 }
136}
137
138impl ConditionAnswer for LogMessageAnswers {
139 fn as_bool(&self, cond: &Condition) -> bool {
140 match cond {
141 Condition::IsSelfOrigin => self.origin_character.is_self,
142 Condition::IsSelfTarget => self.target_character.is_self,
143 Condition::IsOriginFemale | Condition::IsOriginFemaleNpc => {
144 matches!(self.origin_character.gender, Gender::Female)
145 }
146 Condition::IsOriginPlayer => self.origin_character.is_pc,
147 Condition::IsTargetPlayer => self.target_character.is_pc,
148 }
149 }
150}
151
152impl DynamicTextAnswer for LogMessageAnswers {
153 fn as_str(&self, text: &DynamicText) -> Cow<'static, str> {
154 match text {
155 DynamicText::NpcOriginName
158 | DynamicText::PlayerOriginNameEn
159 | DynamicText::PlayerOriginNameJp => self.origin_character.name.clone(),
160 DynamicText::NpcTargetName
161 | DynamicText::PlayerTargetNameEn
162 | DynamicText::PlayerTargetNameJp => self.target_character.name.clone(),
163 }
164 }
165}
166
167impl Answers for LogMessageAnswers {}
168
169#[derive(Debug, Clone)]
170pub enum Origin {
171 Function(Function),
172 Tag(Tag),
173}
174
175#[derive(Debug, Clone, Error)]
176#[error("Unknown condition ({0:?})")]
177pub struct ConditionError(Origin);
178
179impl TryFrom<&Function> for Condition {
182 type Error = ConditionError;
183
184 fn try_from(fun: &Function) -> Result<Self, Self::Error> {
185 #[allow(clippy::match_single_binding)]
186 match fun.name {
187 FuncName::Equal => match &fun.params[..] {
188 [Param::Function(Function {
189 name: FuncName::ObjectParameter,
190 params: p1,
191 }), Param::Function(Function {
192 name: FuncName::ObjectParameter,
193 params: p2,
194 })] if matches!(&p1[..], [Param::Num(1)]) && matches!(&p2[..], [Param::Num(2)]) => {
195 Ok(Condition::IsSelfOrigin)
196 }
197 [Param::Function(Function {
198 name: FuncName::ObjectParameter,
199 params: p1,
200 }), Param::Function(Function {
201 name: FuncName::ObjectParameter,
202 params: p2,
203 })] if matches!(&p1[..], [Param::Num(1)]) && matches!(&p2[..], [Param::Num(3)]) => {
204 Ok(Condition::IsSelfTarget)
205 }
206 _ => Err(ConditionError(Origin::Function(fun.clone()))),
207 },
208 FuncName::ObjectParameter => match &fun.params[..] {
209 _ => Err(ConditionError(Origin::Function(fun.clone()))),
210 },
211 FuncName::PlayerParameter => match &fun.params[..] {
212 [Param::Num(7)] => Ok(Condition::IsOriginPlayer),
213 [Param::Num(8)] => Ok(Condition::IsTargetPlayer),
214 [Param::Num(5)] => Ok(Condition::IsOriginFemaleNpc),
215 _ => Err(ConditionError(Origin::Function(fun.clone()))),
216 },
217 }
218 }
219}
220
221impl TryFrom<&Tag> for Condition {
222 type Error = ConditionError;
223
224 fn try_from(tag: &Tag) -> Result<Self, Self::Error> {
225 #[allow(clippy::match_single_binding)]
226 match tag.name {
227 TagName::Clickable => Err(ConditionError(Origin::Tag(tag.clone()))),
228 TagName::Sheet => match &tag.params[..] {
229 [Param::Obj(Obj::BNpcName), Param::Function(Function {
230 name: FuncName::PlayerParameter,
231 params: p1,
232 }), Param::Num(6)]
233 if matches!(&p1[..], [Param::Num(7)]) =>
234 {
235 Ok(Condition::IsOriginFemale)
236 }
237 _ => Err(ConditionError(Origin::Tag(tag.clone()))),
238 },
239 TagName::SheetEn => match &tag.params[..] {
240 _ => Err(ConditionError(Origin::Tag(tag.clone()))),
241 },
242 }
243 }
244}
245
246impl TryFrom<&IfParam> for Condition {
247 type Error = ConditionError;
248
249 fn try_from(value: &IfParam) -> Result<Self, Self::Error> {
250 match value {
251 IfParam::Function(f) => Condition::try_from(f),
252 IfParam::Tag(t) => Condition::try_from(t),
253 }
254 }
255}
256
257#[derive(Debug, Clone, Error)]
258#[error("Unknown dynamic text ({0:?})")]
259pub struct DynamicTextError(Origin);
260
261impl TryFrom<Function> for DynamicText {
262 type Error = DynamicTextError;
263
264 fn try_from(fun: Function) -> Result<Self, Self::Error> {
265 match fun.name {
266 #[allow(clippy::match_single_binding)]
267 FuncName::Equal => match &fun.params[..] {
268 _ => Err(DynamicTextError(Origin::Function(fun))),
269 },
270 FuncName::ObjectParameter => match &fun.params[..] {
271 [Param::Num(2)] => Ok(DynamicText::NpcOriginName),
272 [Param::Num(3)] => Ok(DynamicText::NpcTargetName),
273 _ => Err(DynamicTextError(Origin::Function(fun))),
274 },
275 #[allow(clippy::match_single_binding)]
276 FuncName::PlayerParameter => match &fun.params[..] {
277 _ => Err(DynamicTextError(Origin::Function(fun))),
278 },
279 }
280 }
281}
282
283impl TryFrom<Tag> for DynamicText {
284 type Error = DynamicTextError;
285
286 fn try_from(tag: Tag) -> Result<Self, Self::Error> {
287 match tag.name {
288 TagName::Clickable => Err(DynamicTextError(Origin::Tag(tag))),
289 TagName::Sheet => match &tag.params[..] {
290 [Param::Obj(Obj::ObjStr), Param::Function(Function {
291 name: FuncName::PlayerParameter,
292 params: p1,
293 }), Param::Num(0)]
294 if matches!(&p1[..], [Param::Num(7)]) =>
295 {
296 Ok(DynamicText::PlayerOriginNameJp)
297 }
298 [Param::Obj(Obj::ObjStr), Param::Function(Function {
299 name: FuncName::PlayerParameter,
300 params: p1,
301 }), Param::Num(0)]
302 if matches!(&p1[..], [Param::Num(8)]) =>
303 {
304 Ok(DynamicText::PlayerTargetNameJp)
305 }
306 _ => Err(DynamicTextError(Origin::Tag(tag))),
307 },
308 TagName::SheetEn => match &tag.params[..] {
309 [Param::Obj(Obj::ObjStr), Param::Num(2), Param::Function(Function {
310 name: FuncName::PlayerParameter,
311 params: p2,
312 }), Param::Num(1), Param::Num(1)]
313 | [Param::Obj(Obj::ObjStr), Param::Num(2), Param::Function(Function {
315 name: FuncName::PlayerParameter,
316 params: p2,
317 }), Param::Num(2), Param::Num(1)]
318 if matches!(&p2[..], [Param::Num(7)]) =>
319 {
320 Ok(DynamicText::PlayerOriginNameEn)
321 }
322 [Param::Obj(Obj::ObjStr), Param::Num(2), Param::Function(Function {
323 name: FuncName::PlayerParameter,
324 params: p2,
325 }), Param::Num(1), Param::Num(1)]
326 if matches!(&p2[..], [Param::Num(8)]) =>
327 {
328 Ok(DynamicText::PlayerTargetNameEn)
329 }
330 _ => Err(DynamicTextError(Origin::Tag(tag))),
331 },
332 }
333 }
334}