factorio_blueprint/
objects.rs

1use noisy_float::types::{R32, R64};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use crate::Container;
5
6const DEFAULT_VERSION: u64 = 77310525440;
7
8pub type Prototype = String;
9pub type EntityNumber = OneBasedIndex;
10pub type ItemStackIndex = u16;
11pub type ItemCountType = u32;
12pub type GraphicsVariation = u8;
13pub type OneBasedIndex = std::num::NonZeroUsize;
14
15#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
16#[serde(default)]
17/// https://wiki.factorio.com/Blueprint_string_format#Blueprint_book_object
18pub struct BlueprintBook {
19    pub item: String,
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub label: Option<String>,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub label_color: Option<Color>,
24    pub blueprints: Vec<BlueprintBookBlueprintValue>,
25    pub active_index: usize,
26    pub version: u64,
27}
28
29impl Default for BlueprintBook {
30    fn default() -> BlueprintBook {
31        BlueprintBook {
32            item: "blueprint-book".into(),
33            version: DEFAULT_VERSION,
34            label: Default::default(),
35            label_color: Default::default(),
36            blueprints: Default::default(),
37            active_index: Default::default(),
38        }
39    }
40}
41
42#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
43#[serde(rename_all = "snake_case")]
44pub struct BlueprintBookBlueprintValue {
45    pub index: usize,
46    #[serde(flatten)]
47    pub item: Container,
48}
49
50#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
51#[serde(default)]
52/// https://wiki.factorio.com/Blueprint_string_format#Blueprint_object
53pub struct Blueprint {
54    pub item: String,
55    pub label: String,
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub label_color: Option<Color>,
58    pub entities: Vec<Entity>,
59    pub tiles: Vec<Tile>,
60    pub icons: Vec<Icon>,
61    pub schedules: Vec<Schedule>,
62    pub version: u64,
63}
64
65impl Default for Blueprint {
66    fn default() -> Blueprint {
67        Blueprint {
68            item: "blueprint".into(),
69            version: DEFAULT_VERSION,
70            label: Default::default(),
71            label_color: Default::default(),
72            entities: Default::default(),
73            tiles: Default::default(),
74            icons: Default::default(),
75            schedules: Default::default(),
76        }
77    }
78}
79
80#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
81/// https://wiki.factorio.com/Blueprint_string_format#Icon_object
82pub struct Icon {
83    pub index: OneBasedIndex,
84    pub signal: SignalID,
85}
86
87#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
88/// https://wiki.factorio.com/Blueprint_string_format#SignalID_object
89pub struct SignalID {
90    pub name: Prototype,
91    #[serde(rename = "type")]
92    pub type_: SignalIDType,
93}
94
95#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
96#[serde(rename_all = "snake_case")]
97pub enum SignalIDType {
98    Item,
99    Fluid,
100    Virtual,
101}
102
103#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
104/// https://wiki.factorio.com/Blueprint_string_format#Entity_object
105pub struct Entity {
106    pub entity_number: EntityNumber,
107    pub name: Prototype,
108    pub position: Position,
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub direction: Option<u8>,
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub orientation: Option<R64>,
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub connections: Option<EntityConnections>,
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub control_behavior: Option<ControlBehavior>,
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub items: Option<ItemRequest>,
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub recipe: Option<Prototype>,
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub bar: Option<ItemStackIndex>,
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub inventory: Option<Inventory>,
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub infinity_settings: Option<InfinitySettings>,
127    #[serde(rename = "type")]
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub type_: Option<EntityType>,
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub input_priority: Option<EntityPriority>,
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub output_priority: Option<EntityPriority>,
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub filter: Option<Prototype>,
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub filters: Option<Vec<ItemFilter>>,
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub filter_mode: Option<EntityFilterMode>,
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub override_stack_size: Option<u8>,
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub drop_position: Option<Position>,
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub pickup_position: Option<Position>,
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub request_filters: Option<Vec<LogisticFilter>>,
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub request_from_buffers: Option<bool>,
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub parameters: Option<SpeakerParameter>,
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub alert_parameters: Option<SpeakerAlertParameter>,
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub auto_launch: Option<bool>,
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub variation: Option<GraphicsVariation>,
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub color: Option<Color>,
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub station: Option<String>,
162}
163
164#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
165/// Reverse-engineered by hand, contains circuit network metadata
166pub struct ControlBehavior {
167    #[serde(skip_serializing_if = "Option::is_none")]
168    /// Used in arithmetic combinators.
169    pub arithmetic_conditions: Option<ArithmeticConditions>,
170    #[serde(skip_serializing_if = "Option::is_none")]
171    /// Used in decider combinators.
172    pub decider_conditions: Option<DeciderConditions>,
173    #[serde(skip_serializing_if = "Option::is_none")]
174    /// Used in constant combinators.
175    pub filters: Option<Vec<ControlFilter>>,
176    #[serde(skip_serializing_if = "Option::is_none")]
177    /// Used in constant combinators, optional. Default: true
178    pub is_on: Option<bool>
179}
180
181#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
182/// Reverse-engineered by hand, contains arithmetic combinator metadata
183pub struct ArithmeticConditions {
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub first_constant: Option<i32>,
186    #[serde(skip_serializing_if = "Option::is_none")]
187    pub first_signal: Option<SignalID>,
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub second_constant: Option<i32>,
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub second_signal: Option<SignalID>,
192    pub operation: String,
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub output_signal: Option<SignalID>
195}
196
197#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
198/// Reverse-engineered by hand, contains constant combinator metadata
199pub struct DeciderConditions {
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub first_signal: Option<SignalID>,
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub second_signal: Option<SignalID>,
204    #[serde(skip_serializing_if = "Option::is_none")]
205    pub constant: Option<i32>,
206    pub comparator: String,
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub output_signal: Option<SignalID>,
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub copy_count_from_input: Option<bool>
211}
212
213#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
214#[serde(untagged)]
215pub enum EntityConnections {
216    StringIdx(HashMap<String, Connection>),
217    NumberIdx(HashMap<OneBasedIndex, Connection>),
218}
219
220#[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize)]
221#[serde(rename_all = "snake_case")]
222pub enum EntityType {
223    Input,
224    Output,
225    Item,
226}
227
228#[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize)]
229#[serde(rename_all = "snake_case")]
230pub enum EntityPriority {
231    Left,
232    Right,
233}
234
235#[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize)]
236#[serde(rename_all = "snake_case")]
237pub enum EntityFilterMode {
238    Whitelist,
239    Blacklist,
240}
241
242#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
243/// https://wiki.factorio.com/Blueprint_string_format#Inventory_object
244pub struct Inventory {
245    pub filters: Vec<ItemFilter>,
246    #[serde(skip_serializing_if = "Option::is_none")]
247    pub bar: Option<ItemStackIndex>,
248}
249
250#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
251/// https://wiki.factorio.com/Blueprint_string_format#Schedule_object
252pub struct Schedule {
253    pub schedule: Vec<ScheduleRecord>,
254    pub locomotives: Vec<EntityNumber>,
255}
256
257#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
258/// https://wiki.factorio.com/Blueprint_string_format#Schedule_Record_object
259pub struct ScheduleRecord {
260    pub station: String,
261    pub wait_conditions: Vec<WaitCondition>,
262}
263
264#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
265/// https://wiki.factorio.com/Blueprint_string_format#Wait_Condition_object
266pub struct WaitCondition {
267    #[serde(rename = "type")]
268    pub type_: WaitConditionType,
269    pub compare_type: CompareType,
270    #[serde(skip_serializing_if = "Option::is_none")]
271    pub ticks: Option<u64>,
272    #[serde(skip_serializing_if = "Option::is_none")]
273    pub condition: Option<CircuitCondition>,
274}
275
276#[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize)]
277#[serde(rename_all = "snake_case")]
278pub enum WaitConditionType {
279    Time,
280    Inactivity,
281    Full,
282    Empty,
283    ItemCount,
284    Circuit,
285    RobotsInactive,
286    FluidCount,
287    PassengerPresent,
288    PassengerNotPresent,
289}
290
291#[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize)]
292#[serde(rename_all = "snake_case")]
293pub enum CompareType {
294    And,
295    Or,
296}
297
298#[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize)]
299pub struct CircuitCondition;
300
301#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
302/// https://wiki.factorio.com/Blueprint_string_format#Tile_object
303pub struct Tile {
304    pub name: Prototype,
305    pub position: Position,
306}
307
308#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
309/// https://wiki.factorio.com/Blueprint_string_format#Position_object
310pub struct Position {
311    pub x: R64,
312    pub y: R64,
313}
314
315/// https://wiki.factorio.com/Blueprint_string_format#Connection_object
316pub type Connection = ConnectionPoint;
317
318#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
319/// https://wiki.factorio.com/Blueprint_string_format#Connection_point_object
320pub struct ConnectionPoint {
321    #[serde(skip_serializing_if = "Option::is_none")]
322    pub red: Option<Vec<ConnectionData>>,
323    #[serde(skip_serializing_if = "Option::is_none")]
324    pub green: Option<Vec<ConnectionData>>,
325}
326
327#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
328/// https://wiki.factorio.com/Blueprint_string_format#Connection_data_object
329pub struct ConnectionData {
330    pub entity_id: EntityNumber,
331    // FIXME: this should be an enum which maps to the defined ints, but
332    // I don't have the definitions handy right now.
333    #[serde(skip_serializing_if = "Option::is_none")]
334    pub circuit_id: Option<i32>,
335}
336
337/// https://wiki.factorio.com/Blueprint_string_format#Item_request_object
338#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
339#[serde(untagged)]
340pub enum ItemRequest {
341    Compact(HashMap<Prototype, ItemCountType>),
342    Verbose(Vec<ItemRequestVerbose>),
343}
344
345#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
346pub struct ItemRequestVerbose {
347    pub item: Prototype,
348    pub count: ItemCountType,
349}
350
351#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
352/// https://wiki.factorio.com/Blueprint_string_format#Item_filter_object
353pub struct ItemFilter {
354    pub name: Prototype,
355    pub index: OneBasedIndex,
356}
357
358#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
359/// https://wiki.factorio.com/Blueprint_string_format#Infinity_settings_object
360pub struct InfinitySettings {
361    pub remove_unfiltered_items: bool,
362    #[serde(skip_serializing_if = "Option::is_none")]
363    pub filters: Option<Vec<InfinityFilter>>,
364}
365
366#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
367/// https://wiki.factorio.com/Blueprint_string_format#Infinity_filter_object
368pub struct InfinityFilter {
369    pub name: Prototype,
370    pub count: ItemCountType,
371    pub mode: InfinityFilterMode,
372    pub index: OneBasedIndex,
373}
374
375#[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize)]
376#[serde(rename_all = "kebab-case")]
377pub enum InfinityFilterMode {
378    AtLeast,
379    AtMost,
380    Exactly,
381}
382
383#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
384/// https://wiki.factorio.com/Blueprint_string_format#Logistic_filter_object
385pub struct LogisticFilter {
386    pub name: Prototype,
387    pub index: OneBasedIndex,
388    pub count: ItemCountType,
389}
390
391#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
392/// Reverse-engineered by hand, contains constant combinator metadata
393pub struct ControlFilter {
394    pub signal: SignalID,
395    pub index: OneBasedIndex,
396    pub count: i32,
397}
398
399#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
400/// https://wiki.factorio.com/Blueprint_string_format#Speaker_parameter_object
401pub struct SpeakerParameter {
402    pub playback_volume: R64,
403    pub playback_globally: bool,
404    pub allow_polyphony: bool,
405}
406
407#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
408/// https://wiki.factorio.com/Blueprint_string_format#Speaker_alert_parameter_object
409pub struct SpeakerAlertParameter {
410    pub show_alert: bool,
411    pub show_on_map: bool,
412    #[serde(skip_serializing_if = "Option::is_none")]
413    pub icon_signal_id: Option<SignalID>,
414    pub alert_message: String,
415}
416
417#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
418/// https://wiki.factorio.com/Blueprint_string_format#Color_object
419pub struct Color {
420    pub r: R32,
421    pub g: R32,
422    pub b: R32,
423    pub a: R32,
424}