Last active
June 6, 2024 23:39
-
-
Save madwareru/9754da985ad9a5a4895ff2eef533fefa to your computer and use it in GitHub Desktop.
Used by myself to play the company of Songs Of Conquest without that much back hurt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [package] | |
| name = "songs-of-conquest-json-explorator" | |
| version = "0.1.0" | |
| edition = "2021" | |
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | |
| [dependencies] | |
| json = "0.12.4" | |
| getopts = "0.2.21" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use std::io::Write; | |
| use getopts::Options; | |
| use json::JsonValue; | |
| use json::number::Number; | |
| mod team { | |
| use std::fmt::{Display, Formatter}; | |
| use json::JsonValue; | |
| use crate::cast_json::CastJson; | |
| pub struct TeamInfo { | |
| pub id: i32, | |
| pub name: String, | |
| pub resources: Vec<ResourceInfo> | |
| } | |
| impl TeamInfo { | |
| pub fn read_from(jv: &JsonValue) -> Option<Self> { | |
| let object = jv.as_object()?; | |
| let id = object.get("_teamID")?.as_i32()?; | |
| let name = object.get("_name")?.as_str()?.to_string(); | |
| let resources = object | |
| .get("_resources")?.as_object()? | |
| .get("_resources")?.as_array_slice()? | |
| .iter() | |
| .filter_map(|jv| { | |
| let quantity = jv.as_object()?.get("_amount")?.as_usize()?; | |
| let resource_type = jv.as_object()?.get("Type")?.as_usize()?.into(); | |
| Some(ResourceInfo { resource_type, quantity }) | |
| }).collect(); | |
| Some(Self { | |
| id, | |
| name, | |
| resources | |
| }) | |
| } | |
| } | |
| #[derive(Copy, Clone)] | |
| pub struct ResourceInfo { | |
| pub resource_type: ResourceType, | |
| pub quantity: usize | |
| } | |
| impl Display for ResourceInfo { | |
| fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | |
| self.resource_type.fmt(f)?; | |
| f.write_str(": ")?; | |
| self.quantity.fmt(f) | |
| } | |
| } | |
| #[derive(Copy, Clone)] | |
| pub enum ResourceType { | |
| Gold, | |
| Wood, | |
| Stone, | |
| AncientAmber, | |
| GlimmerWeave, | |
| CelestialOre, | |
| Unknown | |
| } | |
| impl From<usize> for ResourceType { | |
| fn from(id: usize) -> Self { | |
| match id { | |
| 0 => Self::Gold, | |
| 1 => Self::Wood, | |
| 2 => Self::Stone, | |
| 3 => Self::AncientAmber, | |
| 4 => Self::GlimmerWeave, | |
| 5 => Self::CelestialOre, | |
| _ => Self::Unknown, | |
| } | |
| } | |
| } | |
| impl Display for ResourceType { | |
| fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | |
| match self { | |
| ResourceType::Gold => f.write_str("Gold"), | |
| ResourceType::Wood => f.write_str("Wood"), | |
| ResourceType::Stone => f.write_str("Stone"), | |
| ResourceType::AncientAmber => f.write_str("AncientAmber"), | |
| ResourceType::GlimmerWeave => f.write_str("GlimmerWeave"), | |
| ResourceType::CelestialOre => f.write_str("CelestialOre"), | |
| _ => f.write_str("???") | |
| } | |
| } | |
| } | |
| } | |
| mod commander { | |
| pub struct CommanderInfo { | |
| pub id: i32, | |
| pub name: String, | |
| pub troops: Vec<TroopInfo> | |
| } | |
| pub struct TroopInfo { | |
| pub faction_id: i32, | |
| pub unit_id: i32, | |
| pub upgrade_type: i32, | |
| pub health: i32, | |
| pub defense: i32, | |
| pub movement: i32, | |
| pub initiative: i32, | |
| pub size: i32, | |
| pub max_size: i32, | |
| pub spell_damage_resistance: i32, | |
| pub damage_multiplier: i32, | |
| pub min_damage: i32, | |
| pub max_damage: i32, | |
| } | |
| } | |
| mod cast_json { | |
| pub trait CastJson { | |
| fn as_array_slice(&self) -> Option<&[json::JsonValue]>; | |
| fn as_object(&self) -> Option<&json::object::Object>; | |
| } | |
| pub trait CastJsonMut { | |
| fn as_array_slice_mut(&mut self) -> Option<&mut [json::JsonValue]>; | |
| fn as_object_mut(&mut self) -> Option<&mut json::object::Object>; | |
| } | |
| impl CastJson for json::JsonValue { | |
| fn as_array_slice(&self) -> Option<&[json::JsonValue]> { | |
| match self { | |
| json::JsonValue::Array(it) => Some(&it[..]), | |
| _ => None | |
| } | |
| } | |
| fn as_object(&self) -> Option<&json::object::Object> { | |
| match self { | |
| json::JsonValue::Object(it) => Some(&it), | |
| _ => None | |
| } | |
| } | |
| } | |
| impl CastJsonMut for json::JsonValue { | |
| fn as_array_slice_mut(&mut self) -> Option<&mut [json::JsonValue]> { | |
| match self { | |
| json::JsonValue::Array(it) => Some(&mut it[..]), | |
| _ => None | |
| } | |
| } | |
| fn as_object_mut(&mut self) -> Option<&mut json::object::Object> { | |
| match self { | |
| json::JsonValue::Object(it) => Some(it), | |
| _ => None | |
| } | |
| } | |
| } | |
| } | |
| use cast_json::*; | |
| use team::TeamInfo; | |
| use crate::commander::{CommanderInfo, TroopInfo}; | |
| struct Opts { | |
| input : String, | |
| sub_command: SubCommand | |
| } | |
| enum SubCommand { | |
| ListTeams, | |
| ListCommanders { team_id: usize }, | |
| MakeTeamPoor { team_id: usize }, | |
| MakeChicken { commander_id: usize }, | |
| MakeStronger { commander_id: usize } | |
| } | |
| fn parse_options() -> Result<Opts, String> { | |
| let mut opts = Options::new(); | |
| opts.optflag( | |
| "", | |
| "list-teams", | |
| "list teams in a save file" | |
| ); | |
| opts.optopt( | |
| "", | |
| "list-commanders", | |
| "list teams in a save file for a given team", "TEAM_ID" | |
| ); | |
| opts.optopt( | |
| "", | |
| "make-poor", | |
| "make the team in a save file very poor", | |
| "TEAM_ID" | |
| ); | |
| opts.optopt( | |
| "", | |
| "make-chicken", | |
| "make the commander in a save file weak as a chicken", | |
| "COMMANDER_ID" | |
| ); | |
| opts.optopt( | |
| "", | |
| "make-stronger", | |
| "make all armies of the commander full in size", | |
| "COMMANDER_ID" | |
| ); | |
| let args: Vec<String> = std::env::args().collect(); | |
| let actual_args = &args[1..]; | |
| let program = args[0].clone(); | |
| let options = { | |
| if actual_args.is_empty() { | |
| None | |
| } | |
| else { | |
| let input = actual_args[0].clone(); | |
| let actual_args = &actual_args[1..]; | |
| let matches = match opts.parse(actual_args) { | |
| Ok(m) => { Some(m) } | |
| Err(f) => { | |
| println!("Error occured: {}", f.to_string()); | |
| None | |
| } | |
| }; | |
| matches.and_then(|it| { | |
| match () { | |
| _ if it.opt_present("list-teams") => Some(Opts { | |
| input, | |
| sub_command: SubCommand::ListTeams | |
| }), | |
| _ if it.opt_present("list-commanders") => { | |
| it | |
| .opt_str("list-commanders") | |
| .and_then(|it| it.parse::<usize>().ok()) | |
| .map(|team_id| Opts { input, sub_command: SubCommand::ListCommanders { team_id } }) | |
| }, | |
| _ if it.opt_present("make-poor") => { | |
| it | |
| .opt_str("make-poor") | |
| .and_then(|it| it.parse::<usize>().ok()) | |
| .map(|team_id| Opts { input, sub_command: SubCommand::MakeTeamPoor { team_id } }) | |
| }, | |
| _ if it.opt_present("make-chicken") => { | |
| it | |
| .opt_str("make-chicken") | |
| .and_then(|it| it.parse::<usize>().ok()) | |
| .map(|commander_id| Opts { input, sub_command: SubCommand::MakeChicken { commander_id } }) | |
| }, | |
| _ if it.opt_present("make-stronger") => { | |
| it | |
| .opt_str("make-stronger") | |
| .and_then(|it| it.parse::<usize>().ok()) | |
| .map(|commander_id| Opts { input, sub_command: SubCommand::MakeStronger { commander_id } }) | |
| }, | |
| _ => None | |
| } | |
| }) | |
| } | |
| }; | |
| options.ok_or_else(|| { | |
| let brief = format!("Usage: {} FILE [options]", program); | |
| opts.usage(&brief).to_string() | |
| }) | |
| } | |
| fn main() { | |
| let options = parse_options(); | |
| match options { | |
| Ok(opts) => { | |
| let Opts { input, .. } = &opts; | |
| if let Ok(file_contents) = std::fs::read_to_string(input) { | |
| let mut save_file = json::parse(&file_contents).expect("Failed to parse save file"); | |
| let mut file = { | |
| let file = save_file | |
| .as_object_mut().expect("Incorrect save file. Object expected") | |
| .get("File") | |
| .and_then(|it| it.as_str()) | |
| .expect("Failed to find File entry"); | |
| json::parse(file).expect("Failed to parse File entry") | |
| }; | |
| let changed = match opts { | |
| Opts { sub_command: SubCommand::ListTeams, .. } => { | |
| print_teams(&file).expect("Failed to print team info"); | |
| false | |
| }, | |
| Opts { sub_command: SubCommand::MakeTeamPoor { team_id }, .. } => { | |
| make_team_poor(&mut file, team_id) | |
| .expect("Failed to make team poor"); | |
| true | |
| }, | |
| Opts { sub_command: SubCommand::ListCommanders { team_id }, .. } => { | |
| print_commanders(&file, team_id).expect("Failed to print commander info"); | |
| false | |
| }, | |
| Opts { sub_command: SubCommand::MakeChicken { commander_id}, .. } => { | |
| make_chicken(&mut file, commander_id) | |
| .expect("Failed to make commander a chicken"); | |
| true | |
| } | |
| Opts { sub_command: SubCommand::MakeStronger { commander_id }, .. } => { | |
| make_stronger(&mut file, commander_id) | |
| .expect("Failed to make commander stronger"); | |
| true | |
| } | |
| }; | |
| if changed { | |
| let file_entry = save_file | |
| .as_object_mut().unwrap() | |
| .get_mut("File") | |
| .unwrap(); | |
| *file_entry = JsonValue::String(file.to_string()); | |
| let save_file_str = save_file.pretty(2); | |
| std::fs::File::create(input).unwrap().write_all(save_file_str.as_bytes()).unwrap(); | |
| } | |
| } | |
| else { | |
| println!("Failed to read {}", &input); | |
| } | |
| } | |
| Err(usages) => { | |
| println!("{}", usages) | |
| } | |
| } | |
| } | |
| fn make_chicken(adventure_data: &mut JsonValue, commander_id: usize) -> Option<()> { | |
| let troops = adventure_data | |
| .as_object_mut()? | |
| .get_mut("_troopsSerializable") | |
| .and_then(|it| it.as_array_slice_mut())?; | |
| for troop in troops.iter_mut() { | |
| let parent_id = troop.as_object()?.get("_parentId")?.as_i32()?; | |
| if parent_id != commander_id as i32 { | |
| continue; | |
| } | |
| let troop_stats = troop.as_object_mut()?.get_mut("_stats")?.as_object_mut()?; | |
| *(troop_stats.get_mut("_size")?) = JsonValue::Number(1.into()); | |
| let troop_reference = troop.as_object_mut()?.get_mut("_reference")?.as_object_mut()?; | |
| *(troop_reference.get_mut("Size")?) = JsonValue::Number(1.into()); | |
| } | |
| Some(()) | |
| } | |
| fn make_stronger(adventure_data: &mut JsonValue, commander_id: usize) -> Option<()> { | |
| let troops = adventure_data | |
| .as_object_mut()? | |
| .get_mut("_troopsSerializable") | |
| .and_then(|it| it.as_array_slice_mut())?; | |
| for troop in troops.iter_mut() { | |
| let parent_id = troop.as_object()?.get("_parentId")?.as_i32()?; | |
| if parent_id != commander_id as i32 { | |
| continue; | |
| } | |
| let troop_stats = troop.as_object_mut()?.get_mut("_stats")?.as_object_mut()?; | |
| let max_size = troop_stats.get("_maxTroopSize")?.as_i32()?; | |
| *(troop_stats.get_mut("_size")?) = JsonValue::Number(max_size.into()); | |
| let troop_reference = troop.as_object_mut()?.get_mut("_reference")?.as_object_mut()?; | |
| *(troop_reference.get_mut("Size")?) = JsonValue::Number(max_size.into()); | |
| } | |
| Some(()) | |
| } | |
| fn make_team_poor(adventure_data: &mut JsonValue, target_team_id: usize) -> Option<()> { | |
| let teams = adventure_data | |
| .as_object_mut()? | |
| .get_mut("_teamsSerializable") | |
| .and_then(|it| it.as_array_slice_mut())?; | |
| let enemy_team_object = teams | |
| .iter_mut() | |
| .find(|it| it | |
| .as_object() | |
| .and_then(|it| it.get("_teamID")) | |
| .and_then(|it| it.as_usize()) | |
| .map(|it| it == target_team_id) | |
| .unwrap_or(false) | |
| )?.as_object_mut()?; | |
| let resources = enemy_team_object | |
| .get_mut("_resources")?.as_object_mut()? | |
| .get_mut("_resources")?.as_array_slice_mut()?; | |
| for resource in resources.iter_mut() { | |
| match resource { | |
| JsonValue::Object(ro) => { | |
| if let Some(jv) = ro.get_mut("_amount") { | |
| *jv = JsonValue::Number(Number::from(0)) | |
| } | |
| } | |
| _ => {} | |
| } | |
| } | |
| Some(()) | |
| } | |
| fn print_teams(adventure_data: &JsonValue) -> Option<()> { | |
| let teams = adventure_data | |
| .as_object()? | |
| .get("_teamsSerializable")? | |
| .as_array_slice()?; | |
| for team_jvalue in teams.iter() { | |
| let team_info = TeamInfo::read_from(team_jvalue)?; | |
| println!("id: {}\nname: {}\nresources:", team_info.id, &team_info.name); | |
| for res in team_info.resources.iter() { | |
| println!(" - {}", res); | |
| } | |
| println!(); | |
| } | |
| Some(()) | |
| } | |
| fn print_commanders(adventure_data: &JsonValue, team_id: usize) -> Option<()> { | |
| let commanders_value = adventure_data.as_object()?.get("_commandersSerializable")?; | |
| let mut commanders = commanders_value.as_array_slice()? | |
| .iter() | |
| .filter_map(|commander_entry| { | |
| let commander_team_id = commander_entry.as_object()? | |
| .get("_teamId")?.as_usize()?; | |
| if commander_team_id != team_id { | |
| return None; | |
| } | |
| let id = commander_entry.as_object()? | |
| .get("_id")?.as_i32()?; | |
| let name = commander_entry.as_object()? | |
| .get("_reference")?.as_object()? | |
| .get("Name")?.as_str()? | |
| .to_string(); | |
| Some(CommanderInfo { | |
| name, | |
| id, | |
| troops: Vec::new() | |
| }) | |
| }).collect::<Vec<_>>(); | |
| for troop_entry in adventure_data | |
| .as_object()? | |
| .get("_troopsSerializable")? | |
| .as_array_slice()? | |
| .iter() { | |
| let troop_obj = troop_entry.as_object()?; | |
| let parent_id = troop_obj.get("_parentId")?.as_i32()?; | |
| let reference_obj = troop_obj.get("_reference")?.as_object()?; | |
| let faction_id = reference_obj.get("FactionIndex")?.as_i32()?; | |
| let unit_id = reference_obj.get("UnitIndex")?.as_i32()?; | |
| let upgrade_type = reference_obj.get("UpgradeType")?.as_i32()?; | |
| let stats_obj = troop_obj.get("_stats")?.as_object()?; | |
| let health = stats_obj.get("_health")?.as_i32()?; | |
| let defense = stats_obj.get("_defense")?.as_i32()?; | |
| let movement = stats_obj.get("_movement")?.as_i32()?; | |
| let initiative = stats_obj.get("_initiative")?.as_i32()?; | |
| let size = stats_obj.get("_size")?.as_i32()?; | |
| let max_size = stats_obj.get("_maxTroopSize")?.as_i32()?; | |
| let spell_damage_resistance = stats_obj.get("_spellDamageResistance")?.as_i32()?; | |
| let damage_multiplier = stats_obj.get("_damageMultiplier")?.as_i32()?; | |
| let damage_obj = stats_obj.get("_damage")?.as_object()?; | |
| let min_damage = damage_obj.get("min")?.as_i32()?; | |
| let max_damage = damage_obj.get("max")?.as_i32()?; | |
| let troop_info = TroopInfo { | |
| faction_id, | |
| unit_id, | |
| upgrade_type, | |
| health, | |
| defense, | |
| movement, | |
| initiative, | |
| size, | |
| max_size, | |
| spell_damage_resistance, | |
| damage_multiplier, | |
| min_damage, | |
| max_damage | |
| }; | |
| if let Some(commander) = commanders | |
| .iter_mut() | |
| .find(|it| it.id == parent_id) { | |
| commander.troops.push(troop_info); | |
| } | |
| } | |
| for commander in commanders.iter() { | |
| println!("id: {}, name: {}", commander.id, &commander.name); | |
| println!("troops:"); | |
| for troop in commander.troops.iter() { | |
| println!(" + faction_id: {}, unit_id: {}", troop.faction_id, troop.unit_id); | |
| println!(" - {} of {}, upgrade_type: {}", troop.size, troop.max_size, troop.upgrade_type); | |
| println!(" - 🦶: {}, initiative: {}", troop.movement, troop.initiative); | |
| println!(" - 🛡: {}, ♥: {}", troop.defense, troop.health); | |
| println!(" - ⚔: {}-{}, damage_multiplier: {}", troop.min_damage, troop.max_damage, troop.damage_multiplier); | |
| } | |
| println!() | |
| } | |
| Some(()) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment