ED_LRR/rust/src/ship.rs

272 lines
8.1 KiB
Rust

use crate::common::get_fsd_booster_info;
use crate::journal::*;
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;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FSD {
pub rating_val: f32,
pub class_val: f32,
pub opt_mass: f32,
pub max_fuel: f32,
pub boost: f32,
pub guardian_booster: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Ship {
pub name: String,
pub ident: String,
pub ship_type: String,
pub base_mass: f32,
pub fuel_mass: f32,
pub fuel_capacity: f32,
pub fsd: FSD,
}
impl Ship {
pub fn new(
name: String,
ident: String,
ship_type: String,
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 {
name,
ident,
ship_type,
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<Self, 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, Self>, 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 {
if let Ok(entry) = entry {
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 mut ship = loadout.try_into_ship()?;
if ship.name == "" {
ship.name = "<NO NAME>".to_owned();
}
let key = format!(
"[{}] {} ({})",
ship.ident,
ship.name,
ship.ship_type.to_ascii_lowercase()
);
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)
}
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);
}
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(&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();
}
}
/*
#[derive(Debug,Clone, Serialize, Deserialize, ToPyObject)]
pub struct FSD {
pub rating_val: f32,
pub class_val: f32,
pub opt_mass: f32,
pub max_fuel: f32,
pub boost: f32,
pub guardian_booster: f32,
}
#[derive(Debug,Clone, Serialize, Deserialize, ToPyObject)]
pub struct Ship {
pub name: String,
pub ident: String,
pub ship_type: String,
pub base_mass: f32,
pub fuel_mass: f32,
pub fuel_capacity: f32,
pub fsd: FSD,
}
*/
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("name", self.name.clone())?;
elem.set_item("ident", self.ident.clone())?;
elem.set_item("ship_type", self.ship_type.clone())?;
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))
}
}