Add train generation and builder for blueprints

This commit is contained in:
hal8174 2024-11-23 22:36:24 +01:00
parent 3ec2d34f65
commit 6ac0cab8d5
7 changed files with 338 additions and 18 deletions

95
Cargo.lock generated
View file

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]] [[package]]
name = "addr2line" name = "addr2line"
@ -225,6 +225,31 @@ version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b81e1519b0d82120d2fd469d5bfb2919a9361c48b02d82d04befc1cdd2002452" checksum = "b81e1519b0d82120d2fd469d5bfb2919a9361c48b02d82d04befc1cdd2002452"
[[package]]
name = "bon"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a636f83af97c6946f3f5cf5c268ec02375bf5efd371110292dfd57961f57a509"
dependencies = [
"bon-macros",
"rustversion",
]
[[package]]
name = "bon-macros"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7eaf1bfaa5b8d512abfd36d0c432591fef139d3de9ee54f1f839ea109d70d33"
dependencies = [
"darling",
"ident_case",
"prettyplease",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]] [[package]]
name = "built" name = "built"
version = "0.7.4" version = "0.7.4"
@ -448,6 +473,41 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "darling"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]] [[package]]
name = "either" name = "either"
version = "1.13.0" version = "1.13.0"
@ -491,6 +551,7 @@ name = "factorio_blueprint"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"base64", "base64",
"bon",
"clap 4.5.17", "clap 4.5.17",
"criterion", "criterion",
"flate2", "flate2",
@ -609,6 +670,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]] [[package]]
name = "image" name = "image"
version = "0.25.2" version = "0.25.2"
@ -1029,10 +1096,20 @@ dependencies = [
] ]
[[package]] [[package]]
name = "proc-macro2" name = "prettyplease"
version = "1.0.86" version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -1282,6 +1359,12 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "rustversion"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248"
[[package]] [[package]]
name = "rusty-fork" name = "rusty-fork"
version = "0.3.0" version = "0.3.0"
@ -1450,9 +1533,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.77" version = "2.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View file

@ -17,6 +17,7 @@ harness = false
[dependencies] [dependencies]
base64 = "0.21.5" base64 = "0.21.5"
bon = "3.0.2"
clap = { version = "4.4.8", features = ["derive"] } clap = { version = "4.4.8", features = ["derive"] }
flate2 = "1.0.28" flate2 = "1.0.28"
image = "0.25.2" image = "0.25.2"

View file

@ -0,0 +1,14 @@
use factorio_blueprint::blueprint::{train::generate_train, BlueprintString};
fn main() {
let b = generate_train(2, 4, true, true);
let b = BlueprintString::Blueprint(b);
println!("{}", serde_json::to_string_pretty(&b).unwrap());
println!(
"{}",
factorio_blueprint::blueprint::encode(&serde_json::to_string(&b).unwrap())
);
}

View file

@ -0,0 +1,29 @@
use base64::prelude::*;
use clap::Parser;
use factorio_blueprint::blueprint::{decode, encode};
use std::path::PathBuf;
#[derive(Debug, Parser)]
struct Args {
filename: PathBuf,
#[arg(short, long)]
encode: bool,
}
fn main() {
let args = Args::parse();
let s = std::fs::read_to_string(args.filename).unwrap();
if args.encode {
let j: serde_json::Value = serde_json::from_str(&s).unwrap();
let d = encode(&serde_json::to_string(&j).unwrap());
println!("{}", d);
} else {
let d = decode(&s);
let j: serde_json::Value = serde_json::from_str(&d).unwrap();
println!("{}", serde_json::to_string_pretty(&j).unwrap());
}
}

View file

@ -1,4 +1,5 @@
use base64::engine::general_purpose::STANDARD; use base64::engine::general_purpose::STANDARD;
use base64::prelude::BASE64_STANDARD;
use base64::Engine; use base64::Engine;
use std::io::Cursor; use std::io::Cursor;
use std::io::Read; use std::io::Read;
@ -7,11 +8,13 @@ pub mod structs;
pub use structs::*; pub use structs::*;
pub mod train;
pub fn decode(s: &str) -> String { pub fn decode(s: &str) -> String {
let raw = s.as_bytes(); let raw = s.trim().as_bytes();
assert!(raw[0] == b'0'); assert!(raw[0] == b'0');
let u = STANDARD.decode(&raw[1..]).unwrap(); let u = BASE64_STANDARD.decode(&raw[1..]).unwrap();
let mut o = String::new(); let mut o = String::new();

View file

@ -1,5 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use bon::Builder;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
@ -28,63 +29,164 @@ pub struct BlueprintBookEntry {
index: u32, index: u32,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Builder)]
pub struct Blueprint { pub struct Blueprint {
#[builder(skip = "blueprint".to_owned())]
item: String, item: String,
label: Option<String>, label: String,
#[serde(skip_serializing_if = "Option::is_none")]
label_color: Option<BlueprintColor>, label_color: Option<BlueprintColor>,
#[serde(default)] #[serde(default, skip_serializing_if = "Vec::is_empty")]
#[builder(default)]
entities: Vec<BlueprintEntity>, entities: Vec<BlueprintEntity>,
#[serde(default)] #[serde(default, skip_serializing_if = "Vec::is_empty")]
#[builder(default)]
tiles: Vec<BlueprintTile>, tiles: Vec<BlueprintTile>,
#[serde(default)] #[serde(default, skip_serializing_if = "Vec::is_empty")]
#[builder(default)]
icons: Vec<BlueprintIcon>, icons: Vec<BlueprintIcon>,
#[serde(default)] #[serde(default, skip_serializing_if = "Vec::is_empty")]
#[builder(default)]
schedules: Vec<BlueprintSchedule>, schedules: Vec<BlueprintSchedule>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[builder(default)]
stock_connections: Vec<BlueprintStockConnection>,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>, description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
snap_to_grid: Option<BlueprintPosition>, snap_to_grid: Option<BlueprintPosition>,
#[serde(skip_serializing_if = "Option::is_none")]
absolute_snapping: Option<bool>, absolute_snapping: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
position_relative_to_grid: Option<BlueprintPosition>, position_relative_to_grid: Option<BlueprintPosition>,
version: u64, version: u64,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Builder)]
pub struct BlueprintStockConnection {
#[builder(start_fn)]
stock: u32,
#[serde(skip_serializing_if = "Option::is_none")]
front: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
back: Option<u32>,
}
#[derive(Serialize, Deserialize, Debug, Builder)]
pub struct BlueprintEntity { pub struct BlueprintEntity {
entity_number: u64, #[builder(start_fn)]
name: String, name: String,
#[builder(start_fn)]
entity_number: u32,
#[builder(start_fn)]
position: BlueprintPosition, position: BlueprintPosition,
#[serde(skip_serializing_if = "Option::is_none")]
direciton: Option<u8>, direciton: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
orientation: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
connections: Option<BlueprintConnection>, connections: Option<BlueprintConnection>,
#[serde(skip_serializing_if = "Option::is_none")]
neighbours: Option<Vec<u64>>, neighbours: Option<Vec<u64>>,
#[serde(skip_serializing_if = "Option::is_none")]
control_behaviour: Option<()>, control_behaviour: Option<()>,
items: Option<HashMap<String, u32>>, #[serde(skip_serializing_if = "Option::is_none")]
items: Option<Vec<BlueprintItemRequest>>,
#[serde(skip_serializing_if = "Option::is_none")]
recipe: Option<String>, recipe: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
bar: Option<u64>, bar: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
inventory: Option<BlueprintInventory>, inventory: Option<BlueprintInventory>,
#[serde(skip_serializing_if = "Option::is_none")]
infinity_settings: Option<()>, infinity_settings: Option<()>,
#[serde(rename = "type")] #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
underground_type: Option<String>, underground_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
input_priority: Option<String>, input_priority: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
output_priority: Option<String>, output_priority: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
filter: Option<String>, filter: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
filters: Option<Vec<BlueprintItemFilter>>, filters: Option<Vec<BlueprintItemFilter>>,
#[serde(skip_serializing_if = "Option::is_none")]
filter_mode: Option<String>, filter_mode: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
override_stack_size: Option<u8>, override_stack_size: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
drop_position: Option<BlueprintPosition>, drop_position: Option<BlueprintPosition>,
#[serde(skip_serializing_if = "Option::is_none")]
pickup_position: Option<BlueprintPosition>, pickup_position: Option<BlueprintPosition>,
#[serde(skip_serializing_if = "Option::is_none")]
request_filters: Option<Vec<BlueprintLogisticFilter>>, request_filters: Option<Vec<BlueprintLogisticFilter>>,
#[serde(skip_serializing_if = "Option::is_none")]
request_from_buffers: Option<bool>, request_from_buffers: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
parameters: Option<BlueprintSpeakerParameter>, parameters: Option<BlueprintSpeakerParameter>,
#[serde(skip_serializing_if = "Option::is_none")]
alert_parameters: Option<BlueprintSpeakerAlertParameter>, alert_parameters: Option<BlueprintSpeakerAlertParameter>,
#[serde(skip_serializing_if = "Option::is_none")]
auto_launch: Option<bool>, auto_launch: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
variation: Option<u8>, variation: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
color: Option<BlueprintColor>, color: Option<BlueprintColor>,
#[serde(skip_serializing_if = "Option::is_none")]
station: Option<String>, station: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
manuel_trains_limit: Option<u32>, manuel_trains_limit: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
switch_state: Option<bool>, switch_state: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
tags: Option<serde_json::Value>, tags: Option<serde_json::Value>,
} }
#[derive(Serialize, Deserialize, Debug)]
pub struct BlueprintItemRequest {
id: BlueprintItemID,
items: BlueprintItem,
}
impl BlueprintItemRequest {
pub fn new(id: BlueprintItemID, items: Vec<BlueprintInventoryLocation>) -> Self {
Self {
id,
items: BlueprintItem {
in_inventory: items,
},
}
}
}
#[derive(Serialize, Deserialize, Debug, Builder)]
pub struct BlueprintItemID {
#[builder(start_fn)]
name: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct BlueprintItem {
in_inventory: Vec<BlueprintInventoryLocation>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct BlueprintInventoryLocation {
count: u32,
inventory: u32,
stack: u32,
}
impl BlueprintInventoryLocation {
pub fn new(count: u32, inventory: u32, stack: u32) -> Self {
Self {
count,
inventory,
stack,
}
}
}
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct BlueprintInventory { pub struct BlueprintInventory {
filters: Vec<BlueprintItemFilter>, filters: Vec<BlueprintItemFilter>,
@ -154,6 +256,12 @@ pub struct BlueprintPosition {
y: f64, y: f64,
} }
impl BlueprintPosition {
pub fn new(x: f64, y: f64) -> Self {
BlueprintPosition { x, y }
}
}
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct BlueprintIcon { pub struct BlueprintIcon {
index: u32, index: u32,

82
src/blueprint/train.rs Normal file
View file

@ -0,0 +1,82 @@
use super::{
Blueprint, BlueprintEntity, BlueprintInventoryLocation, BlueprintItemID, BlueprintItemRequest,
BlueprintPosition, BlueprintStockConnection,
};
pub fn generate_train(locomotives: u32, wagons: u32, rails: bool, fluid: bool) -> Blueprint {
let mut e = Vec::new();
for i in 0..locomotives {
e.push(
BlueprintEntity::builder(
"locomotive".to_owned(),
i + 1,
BlueprintPosition::new(1.0, 3.5 + 7.0 * i as f64),
)
.items(vec![BlueprintItemRequest::new(
BlueprintItemID::builder("rocket-fuel".to_owned()).build(),
vec![BlueprintInventoryLocation::new(5, 1, 0)],
)])
.build(),
);
}
for i in 0..wagons {
e.push(
BlueprintEntity::builder(
if fluid {
"fluid-wagon".to_owned()
} else {
"cargo-wagon".to_owned()
},
locomotives + i + 1,
BlueprintPosition::new(1.0, 3.5 + 7.0 * (locomotives + i) as f64),
)
.build(),
);
}
if rails {
for i in 0..((locomotives + wagons) * 7).div_ceil(2) {
e.push(
BlueprintEntity::builder(
"straight-rail".to_owned(),
locomotives + wagons + i + 1,
BlueprintPosition::new(1.0, 1.0 + 2.0 * i as f64),
)
.build(),
);
}
}
let mut stock_connections = Vec::new();
if locomotives + wagons > 1 {
stock_connections.push(BlueprintStockConnection::builder(1).back(2).build());
for i in 2..=(locomotives + wagons - 1) {
stock_connections.push(
BlueprintStockConnection::builder(i)
.front(i - 1)
.back(i + 1)
.build(),
);
}
stock_connections.push(
BlueprintStockConnection::builder(locomotives + wagons)
.front(locomotives + wagons - 1)
.build(),
);
}
Blueprint::builder()
.label("train".to_string())
.entities(e)
.stock_connections(stock_connections)
.version(562949954797573)
.snap_to_grid(BlueprintPosition::new(
4.0,
2.0 * ((locomotives + wagons) * 7).div_ceil(2) as f64,
))
.build()
}