Skip to main content

rpfm_lib/files/group_formations/
edit.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
11//! Editing operations over the blocks of a formation.
12//!
13//! Vanilla files always define every block after the blocks it depends on. Operations that can break
14//! that order sort the blocks afterwards. Block ids are never changed.
15
16use std::collections::{BTreeSet, HashMap, HashSet};
17
18use crate::error::{Result, RLibError};
19
20use super::{Block, ContainerAbsolute, ContainerRelative, EntityArrangement, EntityPreference, GroupFormation, GroupFormationBlock, GroupFormationsFormat, Spanning};
21use super::layout::{self, LayoutParams, LayoutRect};
22
23//---------------------------------------------------------------------------//
24//                             Implementations
25//---------------------------------------------------------------------------//
26
27impl GroupFormation {
28
29    /// Adds a container with the defaults used by vanilla files at the end of the formation.
30    ///
31    /// # Arguments
32    ///
33    /// * `parent_id` - Block to position the container relative to, or `None` for an absolute container.
34    /// * `position` - Offset from the parent for relative containers, or position for absolute ones.
35    /// * `format` - Format of the file, to create a valid entity preference.
36    ///
37    /// # Returns
38    ///
39    /// The id of the new block.
40    ///
41    /// # Errors
42    ///
43    /// Returns an error if the parent doesn't exist.
44    pub fn add_container(&mut self, parent_id: Option<u32>, position: (f32, f32), format: GroupFormationsFormat) -> Result<u32> {
45        let container = ContainerAbsolute {
46            block_priority: 1.0,
47            entity_arrangement: EntityArrangement::Line,
48            inter_entity_spacing: 2.0,
49            crescent_y_offset: 0.0,
50            position_x: position.0,
51            position_y: position.1,
52            minimum_entity_threshold: 0,
53            maximum_entity_threshold: -1,
54            entity_preferences: vec![EntityPreference::new(format)],
55        };
56
57        let block = match parent_id {
58            Some(parent_id) => {
59                self.position_of(parent_id)?;
60                Block::ContainerRelative(to_relative(container, parent_id))
61            },
62            None => Block::ContainerAbsolute(container),
63        };
64
65        Ok(self.push_block(block))
66    }
67
68    /// Adds a span over the provided blocks at the end of the formation.
69    ///
70    /// # Arguments
71    ///
72    /// * `member_ids` - Blocks covered by the span.
73    ///
74    /// # Returns
75    ///
76    /// The id of the new block.
77    ///
78    /// # Errors
79    ///
80    /// Returns an error if any of the members doesn't exist.
81    pub fn add_span(&mut self, member_ids: &[u32]) -> Result<u32> {
82        for member_id in member_ids {
83            self.position_of(*member_id)?;
84        }
85
86        Ok(self.push_block(Block::Spanning(Spanning { spanned_block_ids: member_ids.to_vec() })))
87    }
88
89    /// Replaces the members of a span.
90    ///
91    /// # Arguments
92    ///
93    /// * `span_id` - The span to edit.
94    /// * `member_ids` - New blocks covered by the span.
95    ///
96    /// # Errors
97    ///
98    /// Returns an error if a block doesn't exist, the edited block isn't a span, or a member depends on the span.
99    pub fn set_span_members(&mut self, span_id: u32, member_ids: &[u32]) -> Result<()> {
100        let span_position = self.position_of(span_id)?;
101        for member_id in member_ids {
102            self.position_of(*member_id)?;
103            if self.depends_on(*member_id, span_id) {
104                return Err(RLibError::GroupFormationsCyclicReference(span_id, *member_id));
105            }
106        }
107
108        match &mut self.group_formation_blocks[span_position].block {
109            Block::Spanning(span) => span.spanned_block_ids = member_ids.to_vec(),
110            Block::ContainerAbsolute(_) |
111            Block::ContainerRelative(_) => return Err(RLibError::GroupFormationsBlockIsNotASpan(span_id)),
112        }
113
114        self.sort_blocks();
115        Ok(())
116    }
117
118    /// Changes the parent of a container, keeping its simulated position when possible.
119    ///
120    /// # Arguments
121    ///
122    /// * `block_id` - The container to edit.
123    /// * `parent_id` - The new parent, or `None` to turn it into an absolute container.
124    /// * `params` - The simulated deployment used to keep the container in place.
125    ///
126    /// # Errors
127    ///
128    /// Returns an error if a block doesn't exist, the edited block is a span, or the new parent depends on the edited block.
129    pub fn reparent(&mut self, block_id: u32, parent_id: Option<u32>, params: &LayoutParams) -> Result<()> {
130        let position = self.position_of(block_id)?;
131        if let Some(parent_id) = parent_id {
132            self.position_of(parent_id)?;
133            if self.depends_on(parent_id, block_id) {
134                return Err(RLibError::GroupFormationsCyclicReference(block_id, parent_id));
135            }
136        }
137
138        if let Block::Spanning(_) = self.group_formation_blocks[position].block {
139            return Err(RLibError::GroupFormationsBlockIsNotAContainer(block_id));
140        }
141
142        let rects = self.layout(params);
143        self.attach(position, parent_id, &rects);
144        self.sort_blocks();
145        Ok(())
146    }
147
148    /// Moves a container so its simulated center lands on the target, snapping its offset or position to a grid.
149    ///
150    /// # Arguments
151    ///
152    /// * `block_id` - The container to move.
153    /// * `center` - Target position of the container's center.
154    /// * `grid_step` - Size of the grid to snap to, or 0 to not snap.
155    /// * `params` - The simulated deployment used to compute the offset.
156    ///
157    /// # Errors
158    ///
159    /// Returns an error if the block doesn't exist, is a span, or it or its parent can't be positioned.
160    pub fn move_block(&mut self, block_id: u32, center: (f32, f32), grid_step: f32, params: &LayoutParams) -> Result<()> {
161        let position = self.position_of(block_id)?;
162        let rects = self.layout(params);
163        let rect = rects.get(&block_id).ok_or(RLibError::GroupFormationsBlockNotPlaceable(block_id))?;
164
165        match &mut self.group_formation_blocks[position].block {
166            Block::ContainerAbsolute(container) => {
167                container.position_x = snap(center.0, grid_step);
168                container.position_y = snap(center.1, grid_step);
169            },
170            Block::ContainerRelative(container) => {
171                let parent = rects.get(&container.relative_block_id).ok_or(RLibError::GroupFormationsBlockNotPlaceable(container.relative_block_id))?;
172                let (offset_x, offset_y) = layout::offset_for_center(parent, rect.width(), rect.height(), center.0, center.1);
173                container.position_x = snap(offset_x, grid_step);
174                container.position_y = snap(offset_y, grid_step);
175            },
176            Block::Spanning(_) => return Err(RLibError::GroupFormationsBlockIsNotAContainer(block_id)),
177        }
178
179        Ok(())
180    }
181
182    /// Moves several blocks by the same distance, snapping their offsets or positions to a grid.
183    ///
184    /// Spans are moved by moving their members. Blocks that depend on another moved block already move with it,
185    /// so they're left untouched.
186    ///
187    /// # Arguments
188    ///
189    /// * `block_ids` - The blocks to move.
190    /// * `delta` - Distance to move them.
191    /// * `grid_step` - Size of the grid to snap to, or 0 to not snap.
192    /// * `params` - The simulated deployment used to compute the offsets.
193    ///
194    /// # Errors
195    ///
196    /// Returns an error if a block doesn't exist, or a moved block or its parent can't be positioned.
197    pub fn move_blocks(&mut self, block_ids: &[u32], delta: (f32, f32), grid_step: f32, params: &LayoutParams) -> Result<()> {
198        for block_id in block_ids {
199            self.position_of(*block_id)?;
200        }
201
202        // Replace spans with their members, recursively. Missing members are skipped, as they can't be moved.
203        let mut containers = vec![];
204        let mut visited = HashSet::new();
205        let mut pending = block_ids.to_vec();
206        while let Some(block_id) = pending.pop() {
207            let Ok(position) = self.position_of(block_id) else { continue };
208            if !visited.insert(block_id) {
209                continue;
210            }
211
212            match &self.group_formation_blocks[position].block {
213                Block::Spanning(span) => pending.extend_from_slice(&span.spanned_block_ids),
214                Block::ContainerAbsolute(_) |
215                Block::ContainerRelative(_) => containers.push(block_id),
216            }
217        }
218
219        let rects = self.layout(params);
220        let mut targets = vec![];
221        for block_id in &containers {
222            if containers.iter().any(|other_id| other_id != block_id && self.depends_on(*block_id, *other_id)) {
223                continue;
224            }
225
226            let rect = rects.get(block_id).ok_or(RLibError::GroupFormationsBlockNotPlaceable(*block_id))?;
227            targets.push((*block_id, (rect.center_x() + delta.0, rect.center_y() + delta.1)));
228        }
229
230        // Targets are computed before moving anything, as each move changes the layout.
231        for (block_id, center) in targets {
232            self.move_block(block_id, center, grid_step, params)?;
233        }
234
235        Ok(())
236    }
237
238    /// Deletes blocks, re-attaching the containers positioned relative to them to their closest surviving ancestor.
239    ///
240    /// Spans left without members are deleted too. Containers left without ancestors become absolute.
241    ///
242    /// # Arguments
243    ///
244    /// * `block_ids` - The blocks to delete.
245    /// * `params` - The simulated deployment used to keep re-attached containers in place.
246    ///
247    /// # Errors
248    ///
249    /// Returns an error if any of the blocks doesn't exist.
250    pub fn delete_blocks(&mut self, block_ids: &[u32], params: &LayoutParams) -> Result<()> {
251        for block_id in block_ids {
252            self.position_of(*block_id)?;
253        }
254
255        let rects = self.layout(params);
256        let deleted = self.with_emptied_spans(block_ids.iter().copied().collect());
257
258        let parents = self.group_formation_blocks.iter()
259            .filter_map(|block| match &block.block {
260                Block::ContainerRelative(container) => Some((block.block_id, container.relative_block_id)),
261                Block::ContainerAbsolute(_) |
262                Block::Spanning(_) => None,
263            })
264            .collect::<HashMap<_, _>>();
265
266        for position in 0..self.group_formation_blocks.len() {
267            let block_id = self.group_formation_blocks[position].block_id;
268            let Some(parent_id) = parents.get(&block_id) else { continue };
269            if deleted.contains(&block_id) || !deleted.contains(parent_id) {
270                continue;
271            }
272
273            // The step limit protects against cycles between deleted blocks.
274            let mut ancestor = Some(*parent_id);
275            for _ in 0..=self.group_formation_blocks.len() {
276                match ancestor {
277                    Some(ancestor_id) if deleted.contains(&ancestor_id) => ancestor = parents.get(&ancestor_id).copied(),
278                    _ => break,
279                }
280            }
281
282            let ancestor = ancestor.filter(|ancestor_id| !deleted.contains(ancestor_id));
283            self.attach(position, ancestor, &rects);
284        }
285
286        self.group_formation_blocks.retain(|block| !deleted.contains(&block.block_id));
287        for block in &mut self.group_formation_blocks {
288            if let Block::Spanning(span) = &mut block.block {
289                span.spanned_block_ids.retain(|member_id| !deleted.contains(member_id));
290            }
291        }
292
293        self.sort_blocks();
294        Ok(())
295    }
296
297    /// Deletes a block, all the containers positioned relative to it, recursively, and the spans left without members.
298    ///
299    /// # Arguments
300    ///
301    /// * `block_id` - The root of the subtree to delete.
302    /// * `params` - The simulated deployment used to keep re-attached containers in place.
303    ///
304    /// # Errors
305    ///
306    /// Returns an error if the block doesn't exist.
307    pub fn delete_subtree(&mut self, block_id: u32, params: &LayoutParams) -> Result<()> {
308        self.position_of(block_id)?;
309
310        let mut subtree = HashSet::from([block_id]);
311        loop {
312            let children = self.group_formation_blocks.iter()
313                .filter(|block| !subtree.contains(&block.block_id))
314                .filter(|block| match &block.block {
315                    Block::ContainerRelative(container) => subtree.contains(&container.relative_block_id),
316                    Block::ContainerAbsolute(_) => false,
317                    Block::Spanning(span) => !span.spanned_block_ids.is_empty() && span.spanned_block_ids.iter().all(|member_id| subtree.contains(member_id)),
318                })
319                .map(|block| block.block_id)
320                .collect::<Vec<_>>();
321
322            if children.is_empty() {
323                break;
324            }
325
326            subtree.extend(children);
327        }
328
329        self.delete_blocks(&subtree.into_iter().collect::<Vec<_>>(), params)
330    }
331
332    /// Reorders the blocks so each one comes after the blocks it depends on.
333    ///
334    /// Blocks keep their current order when possible, and blocks in reference cycles are moved to the end.
335    pub fn sort_blocks(&mut self) {
336        let mut positions = HashMap::with_capacity(self.group_formation_blocks.len());
337        for (position, block) in self.group_formation_blocks.iter().enumerate() {
338            positions.entry(block.block_id).or_insert(position);
339        }
340
341        // Kahn's algorithm, always taking the earliest ready block so the current order is kept when possible.
342        let mut pending_dependencies = vec![0; self.group_formation_blocks.len()];
343        let mut dependents = vec![vec![]; self.group_formation_blocks.len()];
344        for (position, block) in self.group_formation_blocks.iter().enumerate() {
345            for dependency in block.block.dependencies() {
346                if let Some(dependency_position) = positions.get(dependency) {
347                    pending_dependencies[position] += 1;
348                    dependents[*dependency_position].push(position);
349                }
350            }
351        }
352
353        let mut ready = pending_dependencies.iter()
354            .enumerate()
355            .filter(|(_, pending)| **pending == 0)
356            .map(|(position, _)| position)
357            .collect::<BTreeSet<_>>();
358
359        let mut order = Vec::with_capacity(self.group_formation_blocks.len());
360        while let Some(position) = ready.pop_first() {
361            order.push(position);
362            for dependent in &dependents[position] {
363                pending_dependencies[*dependent] -= 1;
364                if pending_dependencies[*dependent] == 0 {
365                    ready.insert(*dependent);
366                }
367            }
368        }
369
370        let sorted = order.iter().copied().collect::<HashSet<_>>();
371        order.extend((0..self.group_formation_blocks.len()).filter(|position| !sorted.contains(position)));
372
373        let mut blocks = std::mem::take(&mut self.group_formation_blocks).into_iter().map(Some).collect::<Vec<_>>();
374        self.group_formation_blocks = order.iter()
375            .map(|position| blocks[*position].take().expect("every position appears once in the order"))
376            .collect();
377    }
378
379    /// Returns the position in the block list of the first block with the provided id.
380    fn position_of(&self, block_id: u32) -> Result<usize> {
381        self.group_formation_blocks.iter()
382            .position(|block| block.block_id == block_id)
383            .ok_or(RLibError::GroupFormationsBlockNotFound(block_id))
384    }
385
386    /// Returns if a block is the target, or depends on it directly or through other blocks.
387    ///
388    /// # Arguments
389    ///
390    /// * `block_id` - The block to check.
391    /// * `target_id` - The block that may be depended on.
392    ///
393    /// # Returns
394    ///
395    /// `true` if making the target depend on the block would create a reference cycle.
396    pub fn depends_on(&self, block_id: u32, target_id: u32) -> bool {
397        let mut visited = HashSet::new();
398        let mut pending = vec![block_id];
399        while let Some(current_id) = pending.pop() {
400            if current_id == target_id {
401                return true;
402            }
403
404            if visited.insert(current_id) {
405                if let Ok(position) = self.position_of(current_id) {
406                    pending.extend_from_slice(self.group_formation_blocks[position].block.dependencies());
407                }
408            }
409        }
410
411        false
412    }
413
414    /// Returns the provided blocks plus the spans that would be left without members if they were deleted.
415    fn with_emptied_spans(&self, mut deleted: HashSet<u32>) -> HashSet<u32> {
416        loop {
417            let emptied = self.group_formation_blocks.iter()
418                .filter(|block| !deleted.contains(&block.block_id))
419                .filter(|block| match &block.block {
420                    Block::Spanning(span) => !span.spanned_block_ids.is_empty() && span.spanned_block_ids.iter().all(|member_id| deleted.contains(member_id)),
421                    Block::ContainerAbsolute(_) |
422                    Block::ContainerRelative(_) => false,
423                })
424                .map(|block| block.block_id)
425                .collect::<Vec<_>>();
426
427            if emptied.is_empty() {
428                return deleted;
429            }
430
431            deleted.extend(emptied);
432        }
433    }
434
435    /// Makes the container at the provided position absolute or relative to a new parent, keeping its simulated position if it has one.
436    fn attach(&mut self, position: usize, parent_id: Option<u32>, rects: &HashMap<u32, LayoutRect>) {
437        let block = &mut self.group_formation_blocks[position];
438        let rect = rects.get(&block.block_id);
439        let mut container = match std::mem::take(&mut block.block) {
440            Block::ContainerAbsolute(container) => container,
441            Block::ContainerRelative(container) => to_absolute(container),
442            span @ Block::Spanning(_) => {
443                block.block = span;
444                return;
445            },
446        };
447
448        block.block = match parent_id {
449            Some(parent_id) => {
450                if let (Some(rect), Some(parent)) = (rect, rects.get(&parent_id)) {
451                    (container.position_x, container.position_y) = layout::offset_for_center(parent, rect.width(), rect.height(), rect.center_x(), rect.center_y());
452                }
453                Block::ContainerRelative(to_relative(container, parent_id))
454            },
455            None => {
456                if let Some(rect) = rect {
457                    (container.position_x, container.position_y) = (rect.center_x(), rect.center_y());
458                }
459                Block::ContainerAbsolute(container)
460            },
461        };
462    }
463
464    /// Adds a block at the end of the formation, with an id after all the existing ones.
465    fn push_block(&mut self, block: Block) -> u32 {
466        let block_id = self.group_formation_blocks.iter().map(|block| block.block_id.saturating_add(1)).max().unwrap_or(0);
467        self.group_formation_blocks.push(GroupFormationBlock { block_id, block });
468        block_id
469    }
470}
471
472/// Converts a relative container into an absolute one, keeping its offset as position.
473fn to_absolute(container: ContainerRelative) -> ContainerAbsolute {
474    ContainerAbsolute {
475        block_priority: container.block_priority,
476        entity_arrangement: container.entity_arrangement,
477        inter_entity_spacing: container.inter_entity_spacing,
478        crescent_y_offset: container.crescent_y_offset,
479        position_x: container.position_x,
480        position_y: container.position_y,
481        minimum_entity_threshold: container.minimum_entity_threshold,
482        maximum_entity_threshold: container.maximum_entity_threshold,
483        entity_preferences: container.entity_preferences,
484    }
485}
486
487/// Converts an absolute container into one relative to the provided parent, keeping its position as offset.
488fn to_relative(container: ContainerAbsolute, relative_block_id: u32) -> ContainerRelative {
489    ContainerRelative {
490        block_priority: container.block_priority,
491        relative_block_id,
492        entity_arrangement: container.entity_arrangement,
493        inter_entity_spacing: container.inter_entity_spacing,
494        crescent_y_offset: container.crescent_y_offset,
495        position_x: container.position_x,
496        position_y: container.position_y,
497        minimum_entity_threshold: container.minimum_entity_threshold,
498        maximum_entity_threshold: container.maximum_entity_threshold,
499        entity_preferences: container.entity_preferences,
500    }
501}
502
503/// Rounds a value to the closest multiple of the grid step, if the step is positive.
504fn snap(value: f32, grid_step: f32) -> f32 {
505    if grid_step > 0.0 {
506        (value / grid_step).round() * grid_step
507    } else {
508        value
509    }
510}