Skip to main content

rpfm_lib/files/trade_routes/
mod.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//! Trade routes file format support (`campaign_maps/<map>/trade_routes.ptd`).
12//!
13//! These files contain the precomputed trade network of a campaign map: the spline curves trade routes
14//! follow, and the routes between regions and between ports. The game copies them into the trade manager of
15//! the campaign startpos.
16//!
17//! # File Format
18//!
19//! All values are little endian:
20//!
21//! - Signature `\x89PTD\r\n\x1a\n`, `u32` version (1 in Attila).
22//! - `u32` region count, then each region key as `u32` length + bytes.
23//! - `u32` spline count, then per spline: 4 flag bytes, `u32` segment count, and per segment the start point
24//!   (`u16`, `u16`), the first control point as an `i16` offset from the start, the second control point as an
25//!   `i16` offset from the end, and the end point (`u16`, `u16`). Points are in raw map units; offsets are at
26//!   100 times the raw scale.
27//! - Region routes, then port routes: `u32` count, then per route `u32` from, `u32` to, `u32` edge count and
28//!   the edges. `from`/`to` index the region table. A pair can appear more than once (alternative paths).
29//! - A trailing `u32` (checksum?).
30
31use getset::*;
32use serde_derive::{Deserialize, Serialize};
33
34use crate::binary::{ReadBytes, WriteBytes};
35use crate::error::{Result, RLibError};
36use crate::files::{DecodeableExtraData, Decodeable, EncodeableExtraData, Encodeable};
37use crate::utils::check_size_mismatch;
38
39/// File extension of trade routes files.
40pub const EXTENSION: &str = ".ptd";
41
42/// Signature of trade routes files.
43pub const SIGNATURE: &[u8; 8] = b"\x89PTD\r\n\x1a\n";
44
45/// Version of trade routes files supported.
46const SUPPORTED_VERSION: u32 = 1;
47
48#[cfg(test)] mod trade_routes_test;
49
50//---------------------------------------------------------------------------//
51//                              Enum & Structs
52//---------------------------------------------------------------------------//
53
54/// Decoded trade routes (`.ptd`) file.
55#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
56#[getset(get = "pub", get_mut = "pub", set = "pub")]
57pub struct TradeRoutes {
58
59    /// File version.
60    version: u32,
61
62    /// Region keys. Routes refer to regions by their index in this list.
63    regions: Vec<String>,
64
65    /// Spline curves the routes are made of.
66    splines: Vec<TradeSpline>,
67
68    /// Routes between regions. A region pair may have several alternatives.
69    region_routes: Vec<TradeRoute>,
70
71    /// Routes between ports.
72    port_routes: Vec<TradeRoute>,
73
74    /// Trailing value of the file (checksum?).
75    trailer: u32,
76}
77
78/// A spline of the trade network: a chain of cubic Bezier segments.
79#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
80#[getset(get = "pub", get_mut = "pub", set = "pub")]
81pub struct TradeSpline {
82
83    /// Flags. Meaning unknown; the game copies them into the startpos spline record.
84    flags: [bool; 4],
85
86    /// Bezier segments, in order.
87    segments: Vec<TradeSplineSegment>,
88}
89
90/// A cubic Bezier segment, in raw map units.
91#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
92#[getset(get = "pub", get_mut = "pub", set = "pub")]
93pub struct TradeSplineSegment {
94
95    /// Start point.
96    start: (u16, u16),
97
98    /// First control point, as an offset from the start point (at 100x the raw scale).
99    start_control: (i16, i16),
100
101    /// Second control point, as an offset from the end point (at 100x the raw scale).
102    end_control: (i16, i16),
103
104    /// End point.
105    end: (u16, u16),
106}
107
108/// A route between two regions or ports: the spline edges to follow, in order.
109#[derive(Default, PartialEq, Eq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
110#[getset(get = "pub", get_mut = "pub", set = "pub")]
111pub struct TradeRoute {
112
113    /// Index of the origin in the region table.
114    from: u32,
115
116    /// Index of the destination in the region table.
117    to: u32,
118
119    /// Edges to follow.
120    edges: Vec<TradeRouteEdge>,
121}
122
123/// A spline traversal in a route: `spline index * 2 + direction`, where an odd value means the spline is
124/// traversed from end to start.
125#[derive(Default, PartialEq, Eq, Hash, Clone, Copy, Debug, Serialize, Deserialize)]
126pub struct TradeRouteEdge(u32);
127
128//---------------------------------------------------------------------------//
129//                              Implementations
130//---------------------------------------------------------------------------//
131
132impl Decodeable for TradeRoutes {
133
134    fn decode<R: ReadBytes>(data: &mut R, _extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
135        let signature = data.read_slice(SIGNATURE.len(), false)?;
136        if signature != SIGNATURE {
137            return Err(RLibError::DecodingPTDUnsupportedSignature(signature));
138        }
139
140        let version = data.read_u32()?;
141        if version != SUPPORTED_VERSION {
142            return Err(RLibError::DecodingPTDUnsupportedVersion(version));
143        }
144
145        let region_count = data.read_u32()?;
146        let regions = (0..region_count).map(|_| data.read_sized_string_u8_u32()).collect::<Result<Vec<_>>>()?;
147
148        let spline_count = data.read_u32()?;
149        let splines = (0..spline_count).map(|_| TradeSpline::decode(data)).collect::<Result<Vec<_>>>()?;
150
151        let region_routes = TradeRoute::decode_list(data)?;
152        let port_routes = TradeRoute::decode_list(data)?;
153        let trailer = data.read_u32()?;
154
155        check_size_mismatch(data.stream_position()? as usize, data.len()? as usize)?;
156
157        Ok(Self {
158            version,
159            regions,
160            splines,
161            region_routes,
162            port_routes,
163            trailer,
164        })
165    }
166}
167
168impl Encodeable for TradeRoutes {
169
170    fn encode<W: WriteBytes>(&mut self, buffer: &mut W, _extra_data: &Option<EncodeableExtraData>) -> Result<()> {
171        buffer.write_all(SIGNATURE)?;
172        buffer.write_u32(self.version)?;
173
174        buffer.write_u32(self.regions.len() as u32)?;
175        for region in &self.regions {
176            buffer.write_sized_string_u8_u32(region)?;
177        }
178
179        buffer.write_u32(self.splines.len() as u32)?;
180        for spline in &self.splines {
181            spline.encode(buffer)?;
182        }
183
184        TradeRoute::encode_list(&self.region_routes, buffer)?;
185        TradeRoute::encode_list(&self.port_routes, buffer)?;
186        buffer.write_u32(self.trailer)?;
187        Ok(())
188    }
189}
190
191impl TradeSpline {
192
193    fn decode<R: ReadBytes>(data: &mut R) -> Result<Self> {
194        let mut flags = [false; 4];
195        for flag in &mut flags {
196            *flag = data.read_bool()?;
197        }
198
199        let segment_count = data.read_u32()?;
200        let segments = (0..segment_count).map(|_| TradeSplineSegment::decode(data)).collect::<Result<Vec<_>>>()?;
201        Ok(Self { flags, segments })
202    }
203
204    fn encode<W: WriteBytes>(&self, buffer: &mut W) -> Result<()> {
205        for flag in self.flags {
206            buffer.write_bool(flag)?;
207        }
208
209        buffer.write_u32(self.segments.len() as u32)?;
210        for segment in &self.segments {
211            segment.encode(buffer)?;
212        }
213        Ok(())
214    }
215}
216
217impl TradeSplineSegment {
218
219    fn decode<R: ReadBytes>(data: &mut R) -> Result<Self> {
220        Ok(Self {
221            start: (data.read_u16()?, data.read_u16()?),
222            start_control: (data.read_i16()?, data.read_i16()?),
223            end_control: (data.read_i16()?, data.read_i16()?),
224            end: (data.read_u16()?, data.read_u16()?),
225        })
226    }
227
228    fn encode<W: WriteBytes>(&self, buffer: &mut W) -> Result<()> {
229        buffer.write_u16(self.start.0)?;
230        buffer.write_u16(self.start.1)?;
231        buffer.write_i16(self.start_control.0)?;
232        buffer.write_i16(self.start_control.1)?;
233        buffer.write_i16(self.end_control.0)?;
234        buffer.write_i16(self.end_control.1)?;
235        buffer.write_u16(self.end.0)?;
236        buffer.write_u16(self.end.1)
237    }
238}
239
240impl TradeRoute {
241
242    /// Creates a route.
243    pub fn new(from: u32, to: u32, edges: Vec<TradeRouteEdge>) -> Self {
244        Self { from, to, edges }
245    }
246
247    /// Decodes a `u32` count followed by that many routes.
248    fn decode_list<R: ReadBytes>(data: &mut R) -> Result<Vec<Self>> {
249        let count = data.read_u32()?;
250        (0..count).map(|_| {
251            let from = data.read_u32()?;
252            let to = data.read_u32()?;
253            let edge_count = data.read_u32()?;
254            let edges = (0..edge_count).map(|_| data.read_u32().map(TradeRouteEdge)).collect::<Result<Vec<_>>>()?;
255            Ok(Self { from, to, edges })
256        }).collect()
257    }
258
259    /// Encodes a `u32` count followed by the routes.
260    fn encode_list<W: WriteBytes>(routes: &[Self], buffer: &mut W) -> Result<()> {
261        buffer.write_u32(routes.len() as u32)?;
262        for route in routes {
263            buffer.write_u32(route.from)?;
264            buffer.write_u32(route.to)?;
265            buffer.write_u32(route.edges.len() as u32)?;
266            for edge in &route.edges {
267                buffer.write_u32(edge.0)?;
268            }
269        }
270        Ok(())
271    }
272}
273
274impl TradeRouteEdge {
275
276    /// Creates an edge from its stored value.
277    pub fn new(value: u32) -> Self {
278        Self(value)
279    }
280
281    /// Returns the stored value.
282    pub fn value(&self) -> u32 {
283        self.0
284    }
285
286    /// Returns the index of the spline this edge traverses.
287    pub fn spline(&self) -> usize {
288        (self.0 / 2) as usize
289    }
290
291    /// Returns `true` if the spline is traversed from end to start.
292    pub fn is_reversed(&self) -> bool {
293        self.0 % 2 == 1
294    }
295}