253 lines
7.8 KiB
Rust
253 lines
7.8 KiB
Rust
//! Ship fuel consumption and jump range calculations
|
|
use crate::common::get_fsd_booster_info;
|
|
use crate::journal::*;
|
|
use eyre::Result;
|
|
use pyo3::conversion::ToPyObject;
|
|
use pyo3::prelude::*;
|
|
use pyo3::types::PyDict;
|
|
use regex::Regex;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
use std::fs::File;
|
|
use std::io::{BufRead, BufReader};
|
|
use std::path::PathBuf;
|
|
|
|
/// Frame Shift Drive information
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct FSD {
|
|
/// Rating
|
|
pub rating_val: f32,
|
|
/// Class
|
|
pub class_val: f32,
|
|
/// Optimized Mass
|
|
pub opt_mass: f32,
|
|
/// Max fuel per jump
|
|
pub max_fuel: f32,
|
|
/// Boost factor
|
|
pub boost: f32,
|
|
/// Guardian booster bonus range
|
|
pub guardian_booster: f32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Ship {
|
|
pub base_mass: f32,
|
|
pub fuel_mass: f32,
|
|
pub fuel_capacity: f32,
|
|
pub fsd: FSD,
|
|
}
|
|
|
|
impl Ship {
|
|
pub fn new(
|
|
base_mass: f32,
|
|
fuel_mass: f32,
|
|
fuel_capacity: f32,
|
|
fsd_type: (char, u8),
|
|
max_fuel: f32,
|
|
opt_mass: f32,
|
|
guardian_booster: usize,
|
|
) -> Result<Self, String> {
|
|
let rating_val: f32 = match fsd_type.0 {
|
|
'A' => 12.0,
|
|
'B' => 10.0,
|
|
'C' => 8.0,
|
|
'D' => 10.0,
|
|
'E' => 11.0,
|
|
err => {
|
|
return Err(format!("Invalid rating: {}", err));
|
|
}
|
|
};
|
|
if fsd_type.1 < 2 || fsd_type.1 > 8 {
|
|
return Err(format!("Invalid class: {}", fsd_type.1));
|
|
};
|
|
|
|
if guardian_booster != 0 {
|
|
return Err("Guardian booster not yet implemented!".to_owned());
|
|
}
|
|
|
|
let ret = Self {
|
|
fuel_capacity,
|
|
fuel_mass,
|
|
base_mass,
|
|
fsd: FSD {
|
|
rating_val,
|
|
class_val: 2.0 + (0.15 * ((fsd_type.1 - 2) as f32)),
|
|
opt_mass,
|
|
max_fuel,
|
|
boost: 1.0,
|
|
guardian_booster: get_fsd_booster_info(guardian_booster)?,
|
|
},
|
|
};
|
|
Ok(ret)
|
|
}
|
|
|
|
pub fn new_from_json(data: &str) -> Result<(String, Ship), String> {
|
|
match serde_json::from_str::<Event>(data) {
|
|
Ok(Event {
|
|
event: EventData::Unknown,
|
|
}) => {
|
|
return Err(format!("Invalid Loadout event: {}", data));
|
|
}
|
|
Ok(ev) => {
|
|
if let Some(loadout) = ev.get_loadout() {
|
|
return loadout.try_into_ship();
|
|
} else {
|
|
return Err(format!("Invalid Loadout event: {}", data));
|
|
}
|
|
}
|
|
Err(msg) => {
|
|
return Err(format!("{}", msg));
|
|
}
|
|
};
|
|
}
|
|
|
|
pub fn new_from_journal() -> Result<HashMap<String, Ship>, String> {
|
|
let mut ret = HashMap::new();
|
|
let re = Regex::new(r"^Journal\.\d{12}\.\d{2}\.log$").unwrap();
|
|
let mut journals: Vec<PathBuf> = Vec::new();
|
|
let mut userprofile = PathBuf::from(std::env::var("Userprofile").unwrap());
|
|
userprofile.push("Saved Games");
|
|
userprofile.push("Frontier Developments");
|
|
userprofile.push("Elite Dangerous");
|
|
if let Ok(iter) = userprofile.read_dir() {
|
|
for entry in iter.flatten() {
|
|
if re.is_match(&entry.file_name().to_string_lossy()) {
|
|
journals.push(entry.path());
|
|
};
|
|
}
|
|
}
|
|
journals.sort();
|
|
|
|
for journal in &journals {
|
|
let mut fh = BufReader::new(File::open(journal).unwrap());
|
|
let mut line = String::new();
|
|
while let Ok(n) = fh.read_line(&mut line) {
|
|
if n == 0 {
|
|
break;
|
|
}
|
|
match serde_json::from_str::<Event>(&line) {
|
|
Ok(Event {
|
|
event: EventData::Unknown,
|
|
}) => {}
|
|
Ok(ev) => {
|
|
if let Some(loadout) = ev.get_loadout() {
|
|
let (key, ship) = loadout.try_into_ship()?;
|
|
ret.insert(key, ship);
|
|
}
|
|
}
|
|
Err(_) => {}
|
|
};
|
|
line.clear();
|
|
}
|
|
}
|
|
if ret.is_empty() {
|
|
return Err("No ships loaded!".to_owned());
|
|
}
|
|
return Ok(ret);
|
|
}
|
|
|
|
pub fn can_jump(&self, d: f32) -> bool {
|
|
self.fuel_cost(d) <= self.fsd.max_fuel.min(self.fuel_mass)
|
|
}
|
|
|
|
pub fn boost(&mut self, boost: f32) {
|
|
self.fsd.boost = boost;
|
|
}
|
|
|
|
pub fn refuel(&mut self) {
|
|
self.fuel_mass = self.fuel_capacity;
|
|
}
|
|
|
|
pub fn make_jump(&mut self, d: f32) -> Option<f32> {
|
|
let cost = self.fuel_cost(d);
|
|
if cost > self.fsd.max_fuel.min(self.fuel_mass) {
|
|
return None;
|
|
}
|
|
self.fuel_mass -= cost;
|
|
self.fsd.boost = 1.0;
|
|
Some(cost)
|
|
}
|
|
|
|
pub fn jump_range(&self, fuel: f32, booster: bool) -> f32 {
|
|
let mass = self.base_mass + fuel;
|
|
let mut fuel = self.fsd.max_fuel.min(fuel);
|
|
if booster {
|
|
fuel *= self.boost_fuel_mult();
|
|
}
|
|
let opt_mass = self.fsd.opt_mass * self.fsd.boost;
|
|
return opt_mass * ((1000.0 * fuel) / self.fsd.rating_val).powf(self.fsd.class_val.recip())
|
|
/ mass;
|
|
}
|
|
|
|
pub fn max_range(&self) -> f32 {
|
|
return self.jump_range(self.fsd.max_fuel, true);
|
|
}
|
|
|
|
pub fn range(&self) -> f32 {
|
|
return self.jump_range(self.fuel_mass, true);
|
|
}
|
|
|
|
pub fn full_range(&self) -> f32 {
|
|
return self.jump_range(self.fuel_capacity, true);
|
|
}
|
|
|
|
fn boost_fuel_mult(&self) -> f32 {
|
|
if self.fsd.guardian_booster == 0.0 {
|
|
return 1.0;
|
|
}
|
|
|
|
let base_range = self.jump_range(self.fuel_mass, false); // current range without booster
|
|
|
|
return ((base_range + self.fsd.guardian_booster) / base_range).powf(self.fsd.class_val);
|
|
}
|
|
|
|
pub fn fuel_cost_for_jump(&self, fuel_mass: f32, dist: f32, boost: f32) -> Option<(f32, f32)> {
|
|
if dist == 0.0 {
|
|
return Some((0.0, 0.0));
|
|
}
|
|
let mass = self.base_mass + fuel_mass;
|
|
let opt_mass = self.fsd.opt_mass * boost;
|
|
let base_cost = (dist * mass) / opt_mass;
|
|
let fuel_cost = (self.fsd.rating_val * 0.001 * base_cost.powf(self.fsd.class_val))
|
|
/ self.boost_fuel_mult();
|
|
if fuel_cost > self.fsd.max_fuel || fuel_cost > fuel_mass {
|
|
return None;
|
|
};
|
|
return Some((fuel_cost, fuel_mass - fuel_cost));
|
|
}
|
|
|
|
pub fn fuel_cost(&self, d: f32) -> f32 {
|
|
if d == 0.0 {
|
|
return 0.0;
|
|
}
|
|
let mass = self.base_mass + self.fuel_mass;
|
|
let opt_mass = self.fsd.opt_mass * self.fsd.boost;
|
|
let base_cost = (d * mass) / opt_mass;
|
|
return (self.fsd.rating_val * 0.001 * base_cost.powf(self.fsd.class_val))
|
|
/ self.boost_fuel_mult();
|
|
}
|
|
}
|
|
|
|
impl FSD {
|
|
pub fn to_object(&self, py: Python<'_>) -> PyResult<PyObject> {
|
|
let elem = PyDict::new(py);
|
|
elem.set_item("rating_val", self.rating_val)?;
|
|
elem.set_item("class_val", self.class_val)?;
|
|
elem.set_item("opt_mass", self.opt_mass)?;
|
|
elem.set_item("max_fuel", self.max_fuel)?;
|
|
elem.set_item("boost", self.boost)?;
|
|
elem.set_item("guardian_booster", self.guardian_booster)?;
|
|
Ok(elem.to_object(py))
|
|
}
|
|
}
|
|
|
|
impl Ship {
|
|
pub fn to_object(&self, py: Python<'_>) -> PyResult<PyObject> {
|
|
let elem = PyDict::new(py);
|
|
elem.set_item("base_mass", self.base_mass)?;
|
|
elem.set_item("fuel_mass", self.fuel_mass)?;
|
|
elem.set_item("fuel_capacity", self.fuel_capacity)?;
|
|
elem.set_item("fsd", self.fsd.to_object(py)?)?;
|
|
Ok(elem.to_object(py))
|
|
}
|
|
}
|