Skip to main content

rpfm_extensions/search/
anim_fragment_battle.rs

1//---------------------------------------------------------------------------//
2// Copyright (c) 2017-2026 Ismael Gutiérrez González. All rights reserved.
3//
4// This file is part of the Rusted PackFile Manager (RPFM) project,
5// which can be found here: https://github.com/Frodo45127/rpfm.
6//
7// This file is licensed under the MIT license, which can be found here:
8// https://github.com/Frodo45127/rpfm/blob/master/LICENSE.
9//---------------------------------------------------------------------------//
10
11use getset::{Getters, MutGetters, Setters};
12use serde_derive::{Deserialize, Serialize};
13
14use rpfm_lib::files::anim_fragment_battle::AnimFragmentBattle;
15
16use super::{find_in_string, MatchingMode, replace_match_string, Replaceable, SearchSource, Searchable};
17
18//-------------------------------------------------------------------------------//
19//                              Enums & Structs
20//-------------------------------------------------------------------------------//
21
22/// This struct represents all the matches of the global search within an Anim Fragment Battle File.
23#[derive(Debug, Clone, Getters, MutGetters, Setters, Serialize, Deserialize)]
24#[getset(get = "pub", get_mut = "pub", set = "pub")]
25pub struct AnimFragmentBattleMatches {
26
27    /// The path of the file.
28    path: String,
29
30    /// The search source that produced these matches.
31    #[serde(default)]
32    source: SearchSource,
33
34    /// The container name (pack file name) this file belongs to.
35    #[serde(default)]
36    container_name: String,
37
38    /// The list of matches within the file.
39    matches: Vec<AnimFragmentBattleMatch>,
40}
41
42/// Inner-row hit info for a fragment match.
43///
44/// Tuple shape: `(subrow, is_file_path, is_meta_file_path, is_snd_file_path)`.
45pub type AnimFragmentBattleAnimRefHit = (usize, bool, bool, bool);
46
47/// Per-entry hit info: outer row, optional inner sub-entry hit, and the five booleans
48/// telling which top-level entry columns produced the match.
49///
50/// Tuple shape: `(row, sub_entry, is_animation_id, is_blend_in_time, is_selection_weight, is_weapon_bone, is_uk_4)`.
51pub type AnimFragmentBattleEntryHit = (usize, Option<AnimFragmentBattleAnimRefHit>, bool, bool, bool, bool, bool);
52
53/// This struct represents a match within an Anim Fragment Battle File.
54#[derive(Debug, Clone, Eq, PartialEq, Getters, MutGetters, Serialize, Deserialize)]
55#[getset(get = "pub", get_mut = "pub")]
56pub struct AnimFragmentBattleMatch {
57
58    /// If the match corresponds to a skeleton name value.
59    skeleton_name: bool,
60
61    /// If the match corresponds to a table name value.
62    table_name: bool,
63
64    /// If the match corresponds to a mount table name value.
65    mount_table_name: bool,
66
67    /// If the match corresponds to a unmount table name value.
68    unmount_table_name: bool,
69
70    /// If the match corresponds to a locomotion table name value.
71    locomotion_graph: bool,
72
73    /// If the match corresponds to an entry in the table view.
74    entry: Option<AnimFragmentBattleEntryHit>,
75
76    /// Byte where the match starts.
77    start: usize,
78
79    /// Byte where the match ends.
80    end: usize,
81
82    /// The contents of the matched cell.
83    text: String,
84}
85
86//-------------------------------------------------------------------------------//
87//                             Implementations
88//-------------------------------------------------------------------------------//
89
90impl Searchable for AnimFragmentBattle {
91    type SearchMatches = AnimFragmentBattleMatches;
92
93    fn search(&self, file_path: &str, pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode) -> AnimFragmentBattleMatches {
94        let mut matches = AnimFragmentBattleMatches::new(file_path);
95
96        match matching_mode {
97            MatchingMode::Regex(regex) => {
98                for entry_match in regex.find_iter(self.skeleton_name()) {
99                    matches.matches.push(
100                        AnimFragmentBattleMatch::new(
101                            true,
102                            false,
103                            false,
104                            false,
105                            false,
106                            None,
107                            entry_match.start(),
108                            entry_match.end(),
109                            self.skeleton_name().to_owned()
110                        )
111                    );
112                }
113
114                for entry_match in regex.find_iter(self.table_name()) {
115                    matches.matches.push(
116                        AnimFragmentBattleMatch::new(
117                            false,
118                            true,
119                            false,
120                            false,
121                            false,
122                            None,
123                            entry_match.start(),
124                            entry_match.end(),
125                            self.table_name().to_owned()
126                        )
127                    );
128                }
129
130                for entry_match in regex.find_iter(self.mount_table_name()) {
131                    matches.matches.push(
132                        AnimFragmentBattleMatch::new(
133                            false,
134                            false,
135                            true,
136                            false,
137                            false,
138                            None,
139                            entry_match.start(),
140                            entry_match.end(),
141                            self.mount_table_name().to_owned()
142                        )
143                    );
144                }
145
146                for entry_match in regex.find_iter(self.unmount_table_name()) {
147                    matches.matches.push(
148                        AnimFragmentBattleMatch::new(
149                            false,
150                            false,
151                            false,
152                            true,
153                            false,
154                            None,
155                            entry_match.start(),
156                            entry_match.end(),
157                            self.unmount_table_name().to_owned()
158                        )
159                    );
160                }
161
162                for entry_match in regex.find_iter(self.locomotion_graph()) {
163                    matches.matches.push(
164                        AnimFragmentBattleMatch::new(
165                            false,
166                            false,
167                            false,
168                            false,
169                            true,
170                            None,
171                            entry_match.start(),
172                            entry_match.end(),
173                            self.locomotion_graph().to_owned()
174                        )
175                    );
176                }
177
178                for (row, entry) in self.entries().iter().enumerate() {
179                    for (subrow, anim_refs) in entry.anim_refs().iter().enumerate() {
180                        for entry_match in regex.find_iter(anim_refs.file_path()) {
181                            matches.matches.push(
182                                AnimFragmentBattleMatch::new(
183                                    false,
184                                    false,
185                                    false,
186                                    false,
187                                    false,
188                                    Some((row, Some((subrow, true, false, false)), false, false, false, false, false)),
189                                    entry_match.start(),
190                                    entry_match.end(),
191                                    anim_refs.file_path().to_owned()
192                                )
193                            );
194                        }
195
196                        for entry_match in regex.find_iter(anim_refs.meta_file_path()) {
197                            matches.matches.push(
198                                AnimFragmentBattleMatch::new(
199                                    false,
200                                    false,
201                                    false,
202                                    false,
203                                    false,
204                                    Some((row, Some((subrow, false, true, false)), false, false, false, false, false)),
205                                    entry_match.start(),
206                                    entry_match.end(),
207                                    anim_refs.meta_file_path().to_owned()
208                                )
209                            );
210                        }
211
212                        for entry_match in regex.find_iter(anim_refs.snd_file_path()) {
213                            matches.matches.push(
214                                AnimFragmentBattleMatch::new(
215                                    false,
216                                    false,
217                                    false,
218                                    false,
219                                    false,
220                                    Some((row, Some((subrow, false, false, true)), false, false, false, false, false)),
221                                    entry_match.start(),
222                                    entry_match.end(),
223                                    anim_refs.snd_file_path().to_owned()
224                                )
225                            );
226                        }
227                    }
228
229                    for entry_match in regex.find_iter(entry.filename()) {
230                        matches.matches.push(
231                            AnimFragmentBattleMatch::new(
232                                false,
233                                false,
234                                false,
235                                false,
236                                false,
237                                Some((row, None, true, false, false, false, false)),
238                                entry_match.start(),
239                                entry_match.end(),
240                                entry.filename().to_owned()
241                            )
242                        );
243                    }
244
245                    for entry_match in regex.find_iter(entry.metadata()) {
246                        matches.matches.push(
247                            AnimFragmentBattleMatch::new(
248                                false,
249                                false,
250                                false,
251                                false,
252                                false,
253                                Some((row, None, false, true, false, false, false)),
254                                entry_match.start(),
255                                entry_match.end(),
256                                entry.metadata().to_owned()
257                            )
258                        );
259                    }
260
261                    for entry_match in regex.find_iter(entry.metadata_sound()) {
262                        matches.matches.push(
263                            AnimFragmentBattleMatch::new(
264                                false,
265                                false,
266                                false,
267                                false,
268                                false,
269                                Some((row, None, false, false, true, false, false)),
270                                entry_match.start(),
271                                entry_match.end(),
272                                entry.metadata_sound().to_owned()
273                            )
274                        );
275                    }
276
277                    for entry_match in regex.find_iter(entry.skeleton_type()) {
278                        matches.matches.push(
279                            AnimFragmentBattleMatch::new(
280                                false,
281                                false,
282                                false,
283                                false,
284                                false,
285                                Some((row, None, false, false, false, true, false)),
286                                entry_match.start(),
287                                entry_match.end(),
288                                entry.skeleton_type().to_owned()
289                            )
290                        );
291                    }
292
293                    for entry_match in regex.find_iter(entry.uk_4()) {
294                        matches.matches.push(
295                            AnimFragmentBattleMatch::new(
296                                false,
297                                false,
298                                false,
299                                false,
300                                false,
301                                Some((row, None, false, false, false, false, true)),
302                                entry_match.start(),
303                                entry_match.end(),
304                                entry.uk_4().to_owned()
305                            )
306                        );
307                    }
308                }
309            }
310
311            MatchingMode::Pattern(regex) => {
312                let pattern = if case_sensitive || regex.is_some() {
313                    pattern.to_owned()
314                } else {
315                    pattern.to_lowercase()
316                };
317
318                for (start, end, _) in &find_in_string(self.skeleton_name(), &pattern, case_sensitive, regex) {
319                    matches.matches.push(
320                        AnimFragmentBattleMatch::new(
321                            true,
322                            false,
323                            false,
324                            false,
325                            false,
326                            None,
327                            *start,
328                            *end,
329                            self.skeleton_name().to_owned()
330                        )
331                    );
332                }
333
334                for (start, end, _) in &find_in_string(self.table_name(), &pattern, case_sensitive, regex) {
335                    matches.matches.push(
336                        AnimFragmentBattleMatch::new(
337                            false,
338                            true,
339                            false,
340                            false,
341                            false,
342                            None,
343                            *start,
344                            *end,
345                            self.table_name().to_owned()
346                        )
347                    );
348                }
349
350                for (start, end, _) in &find_in_string(self.mount_table_name(), &pattern, case_sensitive, regex) {
351                    matches.matches.push(
352                        AnimFragmentBattleMatch::new(
353                            false,
354                            false,
355                            true,
356                            false,
357                            false,
358                            None,
359                            *start,
360                            *end,
361                            self.mount_table_name().to_owned()
362                        )
363                    );
364                }
365
366                for (start, end, _) in &find_in_string(self.unmount_table_name(), &pattern, case_sensitive, regex) {
367                    matches.matches.push(
368                        AnimFragmentBattleMatch::new(
369                            false,
370                            false,
371                            false,
372                            true,
373                            false,
374                            None,
375                            *start,
376                            *end,
377                            self.unmount_table_name().to_owned()
378                        )
379                    );
380                }
381
382                for (start, end, _) in &find_in_string(self.locomotion_graph(), &pattern, case_sensitive, regex) {
383                    matches.matches.push(
384                        AnimFragmentBattleMatch::new(
385                            false,
386                            false,
387                            false,
388                            false,
389                            true,
390                            None,
391                            *start,
392                            *end,
393                            self.locomotion_graph().to_owned()
394                        )
395                    );
396                }
397
398                for (row, entry) in self.entries().iter().enumerate() {
399                    for (subrow, anim_refs) in entry.anim_refs().iter().enumerate() {
400                        for (start, end, _) in &find_in_string(anim_refs.file_path(), &pattern, case_sensitive, regex) {
401                            matches.matches.push(
402                                AnimFragmentBattleMatch::new(
403                                    false,
404                                    false,
405                                    false,
406                                    false,
407                                    false,
408                                    Some((row, Some((subrow, true, false, false)), false, false, false, false, false)),
409                                    *start,
410                                    *end,
411                                    anim_refs.file_path().to_owned()
412                                )
413                            );
414                        }
415
416                        for (start, end, _) in &find_in_string(anim_refs.meta_file_path(), &pattern, case_sensitive, regex) {
417                            matches.matches.push(
418                                AnimFragmentBattleMatch::new(
419                                    false,
420                                    false,
421                                    false,
422                                    false,
423                                    false,
424                                    Some((row, Some((subrow, false, true, false)), false, false, false, false, false)),
425                                    *start,
426                                    *end,
427                                    anim_refs.meta_file_path().to_owned()
428                                )
429                            );
430                        }
431
432                        for (start, end, _) in &find_in_string(anim_refs.snd_file_path(), &pattern, case_sensitive, regex) {
433                            matches.matches.push(
434                                AnimFragmentBattleMatch::new(
435                                    false,
436                                    false,
437                                    false,
438                                    false,
439                                    false,
440                                    Some((row, Some((subrow, false, false, true)), false, false, false, false, false)),
441                                    *start,
442                                    *end,
443                                    anim_refs.snd_file_path().to_owned()
444                                )
445                            );
446                        }
447                    }
448
449                    for (start, end, _) in &find_in_string(entry.filename(), &pattern, case_sensitive, regex) {
450                        matches.matches.push(
451                            AnimFragmentBattleMatch::new(
452                                false,
453                                false,
454                                false,
455                                false,
456                                false,
457                                Some((row, None, true, false, false, false, false)),
458                                *start,
459                                *end,
460                                entry.filename().to_owned()
461                            )
462                        );
463                    }
464
465                    for (start, end, _) in &find_in_string(entry.metadata(), &pattern, case_sensitive, regex) {
466                        matches.matches.push(
467                            AnimFragmentBattleMatch::new(
468                                false,
469                                false,
470                                false,
471                                false,
472                                false,
473                                Some((row, None, false, true, false, false, false)),
474                                *start,
475                                *end,
476                                entry.metadata().to_owned()
477                            )
478                        );
479                    }
480
481                    for (start, end, _) in &find_in_string(entry.metadata_sound(), &pattern, case_sensitive, regex) {
482                        matches.matches.push(
483                            AnimFragmentBattleMatch::new(
484                                false,
485                                false,
486                                false,
487                                false,
488                                false,
489                                Some((row, None, false, false, true, false, false)),
490                                *start,
491                                *end,
492                                entry.metadata_sound().to_owned()
493                            )
494                        );
495                    }
496
497                    for (start, end, _) in &find_in_string(entry.skeleton_type(), &pattern, case_sensitive, regex) {
498                        matches.matches.push(
499                            AnimFragmentBattleMatch::new(
500                                false,
501                                false,
502                                false,
503                                false,
504                                false,
505                                Some((row, None, false, false, false, true, false)),
506                                *start,
507                                *end,
508                                entry.skeleton_type().to_owned()
509                            )
510                        );
511                    }
512
513                    for (start, end, _) in &find_in_string(entry.uk_4(), &pattern, case_sensitive, regex) {
514                        matches.matches.push(
515                            AnimFragmentBattleMatch::new(
516                                false,
517                                false,
518                                false,
519                                false,
520                                false,
521                                Some((row, None, false, false, false, false, true)),
522                                *start,
523                                *end,
524                                entry.uk_4().to_owned()
525                            )
526                        );
527                    }
528                }
529            }
530        }
531
532        matches
533    }
534}
535
536impl Replaceable for AnimFragmentBattle {
537
538    fn replace(&mut self, pattern: &str, replace_pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode, search_matches: &AnimFragmentBattleMatches) -> bool {
539        let mut edited = false;
540
541        // NOTE: Due to changes in index positions, we need to do this in reverse.
542        // Otherwise we may cause one edit to generate invalid indexes for the next matches.
543        for search_match in search_matches.matches().iter().rev() {
544            edited |= search_match.replace(pattern, replace_pattern, case_sensitive, matching_mode, self);
545        }
546
547        edited
548    }
549}
550
551impl AnimFragmentBattleMatches {
552
553    /// This function creates a new `AnimFragmentBattleMatches` for the provided path.
554    pub fn new(path: &str) -> Self {
555        Self {
556            path: path.to_owned(),
557            matches: vec![],
558            source: SearchSource::default(),
559            container_name: String::new(),
560        }
561    }
562}
563
564impl AnimFragmentBattleMatch {
565
566    /// This function creates a new `AnimFragmentBattleMatch` with the provided data.
567    #[allow(clippy::too_many_arguments)]
568    pub fn new(skeleton_name: bool, table_name: bool, mount_table_name: bool, unmount_table_name: bool, locomotion_graph: bool, entry: Option<AnimFragmentBattleEntryHit>, start: usize, end: usize, text: String) -> Self {
569        Self {
570            skeleton_name,
571            table_name,
572            mount_table_name,
573            unmount_table_name,
574            locomotion_graph,
575            entry,
576            start,
577            end,
578            text,
579        }
580    }
581
582    /// This function replaces all the matches in the provided data.
583    fn replace(&self, pattern: &str, replace_pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode, data: &mut AnimFragmentBattle) -> bool {
584
585        // Get all the previous data and references of data to manipulate here, so we don't duplicate a lot of code per-field in the match mode part.
586        let (previous_data, current_data) = {
587            if self.skeleton_name {
588                (data.skeleton_name().to_owned(), data.skeleton_name_mut())
589            } else if self.table_name {
590                (data.table_name().to_owned(), data.table_name_mut())
591            } else if self.mount_table_name {
592                (data.mount_table_name().to_owned(), data.mount_table_name_mut())
593            } else if self.unmount_table_name {
594                (data.unmount_table_name().to_owned(), data.unmount_table_name_mut())
595            } else if self.locomotion_graph {
596                (data.locomotion_graph().to_owned(), data.locomotion_graph_mut())
597            } else if let Some((row, anim_ref, filename, metadata, metadata_sound, skeleton_type, uk_4)) = self.entry {
598                match data.entries_mut().get_mut(row) {
599                    Some(entry) => {
600                        if let Some((subrow, file_path, meta_file_path, snd_file_path)) = anim_ref {
601                             match entry.anim_refs_mut().get_mut(subrow) {
602                                Some(subentry) => {
603                                    if file_path {
604                                        (subentry.file_path().to_owned(), subentry.file_path_mut())
605                                    } else if meta_file_path {
606                                        (subentry.meta_file_path().to_owned(), subentry.meta_file_path_mut())
607                                    } else if snd_file_path {
608                                        (subentry.snd_file_path().to_owned(), subentry.snd_file_path_mut())
609                                    } else {
610                                        return false;
611                                    }
612                                }
613                                None => return false,
614                            }
615                        } else if filename {
616                            (entry.filename().to_owned(), entry.filename_mut())
617                        } else if metadata {
618                            (entry.metadata().to_owned(), entry.metadata_mut())
619                        } else if metadata_sound {
620                            (entry.metadata_sound().to_owned(), entry.metadata_sound_mut())
621                        } else if skeleton_type {
622                            (entry.skeleton_type().to_owned(), entry.skeleton_type_mut())
623                        } else if uk_4 {
624                            (entry.uk_4().to_owned(), entry.uk_4_mut())
625                        } else {
626                            return false;
627                        }
628                    }
629                    None => return false,
630                }
631            }
632
633            // This is an error.
634            else {
635                return false
636            }
637        };
638
639        replace_match_string(pattern, replace_pattern, case_sensitive, matching_mode, self.start, self.end, &previous_data, current_data)
640    }
641}