diff --git a/dt_lib/src/battle/army.rs b/dt_lib/src/battle/army.rs index a3eaf6e..8c953fa 100644 --- a/dt_lib/src/battle/army.rs +++ b/dt_lib/src/battle/army.rs @@ -19,7 +19,7 @@ use num::{integer::sqrt, pow}; use once_cell::sync::Lazy; use pathfinding::directed::astar::astar; -use super::control::Control; +use super::control::{Control, PC_ControlSetings}; #[derive(Clone, Debug, Default, Sections)] #[alkahest(Deserialize, Serialize, SerializeRef, Formula)] pub struct ArmyStats { @@ -70,6 +70,7 @@ pub struct Army { pub defeated: bool, //#[default_value = "Control::PC"] pub control: Control, + pub pc_settings: Option, //#[unused] pub path: Vec<(usize, usize)>, } @@ -101,6 +102,7 @@ impl Army { ) -> Self { let hitmap: Vec> = (0..*MAX_TROOPS).map(|_| None::).collect(); let mut army = Army { + pc_settings: None, troops: Vec::new(), building: None, hitmap, @@ -221,8 +223,8 @@ impl Army { Ok(()) } - pub fn add_item(&mut self, item: usize) { - self.inventory.push(Item { index: item }) + pub fn add_item(&mut self, item: Item) { + self.inventory.push(item) } pub fn remove_item(&mut self, rem_item: usize) { if let Some(index) = self @@ -304,14 +306,14 @@ pub fn find_path( } .into_iter() .filter(|p: &(usize, usize)| { - let hitbox = &gamemap.hitmap[p.0][p.1]; + let hitbox = &gamemap.hitmap[*p]; hitbox.passable() && (!(hitbox.need_transport ^ on_transport) || hitbox.building.is_some_and(|n| { objects[gamemap.buildings[n].id].obj_type == ObjectType::Bridge })) }) - .map(|p| (p, 10 / TILES[gamemap.tilemap[p.0][p.1]].walkspeed)) + .map(|p| (p, 10 / TILES[gamemap.tilemap[p]].walkspeed)) }, |&p| dist(&p, &goal), |&p| p == goal, diff --git a/dt_lib/src/battle/control.rs b/dt_lib/src/battle/control.rs index 28a1161..683c9db 100644 --- a/dt_lib/src/battle/control.rs +++ b/dt_lib/src/battle/control.rs @@ -1,12 +1,15 @@ use advini::{Ini, IniParseError}; use alkahest::*; +use math_thingies::Percent; + +use crate::time::time::Time; #[derive(Clone, Debug)] #[alkahest(Deserialize, Serialize, SerializeRef, Formula)] pub struct Relations { - player: u8, - ally: u8, - neighbour: u8, - enemy: u8, + pub player: u8, + pub ally: u8, + pub neighbour: u8, + pub enemy: u8, } impl Ini for Relations { fn eat(chars: std::str::Chars) -> Result<(Self, std::str::Chars), IniParseError> { @@ -73,17 +76,27 @@ impl Ini for Control { } #[derive(Clone, Debug, Default)] +#[alkahest(Deserialize, Serialize, SerializeRef, Formula)] pub struct PC_ControlSetings { - xp_like_player: bool, - xp_add: u64, - units_dont_have_money: bool, - ignores_ai_armys: bool, - targets_player: bool, - forbid_random_targets: bool, - forbid_random_talks: bool, - not_interested_in_buildings: bool, - patrol_radius: Option, - relations: Relations, + pub xp_like_player: bool, + pub xp_add: u64, + pub xp_correction: Percent, + pub gold_income: u64, + pub mana_income: u64, + pub speed_correction: Percent, + pub units_dont_have_money: bool, + pub ignores_ai_armys: bool, + pub targets_player: bool, + pub forbid_random_targets: bool, + pub forbid_random_talks: bool, + pub not_interested_in_buildings: bool, + pub aggression: u8, + pub patrol: u8, + pub revive_everyone: bool, + pub revive_time: Time, + pub garrison_power: u8, + pub patrol_radius: Option, + pub relations: Relations, } #[derive(Clone, Debug)] pub enum Target { diff --git a/dt_lib/src/lib.rs b/dt_lib/src/lib.rs index cbc6855..1c54355 100644 --- a/dt_lib/src/lib.rs +++ b/dt_lib/src/lib.rs @@ -1,3 +1,4 @@ +#![allow(unused_imports)] pub mod battle; pub mod bonuses; pub mod effects; diff --git a/dt_lib/src/map/convert.rs b/dt_lib/src/map/convert.rs index eb0ebbc..53a96b4 100644 --- a/dt_lib/src/map/convert.rs +++ b/dt_lib/src/map/convert.rs @@ -1,10 +1,14 @@ -use super::{deco::*, map::*}; +use crate::{ + battle::{control::{Control, Relations}, Army, ArmyStats, Troop}, items::{Item, ITEMS}, map::object::{BuildingVariant, Village}, mutrc::SendMut, time::time::Time, units::unit::{Unit, UNITS} +}; + +use super::{deco::*, map::*, object::{MapBuildingdata, Market, RecruitUnit, Recruitment}}; use bufread::BzDecoder; use bytes::*; use bzip2::*; +use core::str; use encoding_rs::*; use num_enum::{Default, FromPrimitive, IntoPrimitive}; -use core::str; use std::{ fs, fs::File, @@ -55,7 +59,7 @@ pub struct ManyUnitsData { } #[derive(FromBytes, Unaligned, Debug, Copy, Clone, PartialEq, Eq)] #[repr(packed(1))] -pub struct RecruitUnit { +pub struct RecruitUnitData { pub id: u8, pub amount: u8, pub max_amount: u8, @@ -240,7 +244,7 @@ pub struct BuildingData { pub event_ids: [u16; 64], // айди событий в максимальном количестве 64 штуки 136 pub artifact_ids: [u16; 6], // на рынке 5 штук, в руинах 4 максимально 148 pub _empty_big: [u8; 117], // - pub recruits: [RecruitUnit; 6], // найм в казармах 282 + pub recruits: [RecruitUnitData; 6], // найм в казармах 282 pub gold_income: u16, // золотой доход 284 pub max_gold_income: u16, // максимальный золотой доход? 286 pub _empty: [u8; 2], // 288 @@ -256,13 +260,13 @@ pub struct BuildingData { pub spell_ids: [u8; 5], // айди заклинаний 314 bytes (info about spells available for study) pub garrison_units: [GarrisonUnit; 6], // гарнизон 332 bytes pub additional_garrison_defense: u8, // дополнительная защита гарнизона 333 - pub min_artifact_price: u16, // минимальная стоимость артефактов 335, - pub max_artifact_price: u16, // максимальная стоимость артефактов 337, + pub min_artifact_price: u16, // минимальная стоимость артефактов на продажу 335, + pub max_artifact_price: u16, // максимальная стоимость артефактов на продажу 337, pub group: u8, // союзник, сосед, враг в таком духе 338 pub relations: RelationsData, // отношения здания 342 pub _empty4: [u8; 8], // 350 pub mana_income: u8, // приток маны 351 - pub max_mana_income: u8, // максимальный приток маны 352 + pub max_mana_income: u8, // максимальный приток маны в деревне 352 pub _empty5: [u8; 1], // 353 pub knight_start_building: u8, // стартовое здание рыцаря 354 pub mage_start_building: u8, // стартовое здание архимага 355 @@ -344,7 +348,7 @@ pub struct LightOrEvent { pub map_model: u8, pub _empty: [u8; 33], pub light_radius: u8, - pub _empty1: [u8; 59] + pub _empty1: [u8; 59], } #[derive(FromBytes, Unaligned, PartialEq, Eq, Debug, Copy, Clone)] #[repr(packed(1))] @@ -359,9 +363,9 @@ pub struct MapData { pub map: Vec, pub decos: Vec, pub armies: Vec, - pub lanterns: Vec, - pub events: Vec, - pub text: Vec + pub lanterns: Vec, + pub events: Vec, + pub text: Vec, } pub fn parse_dtm_map(path: &Path) -> Result { let mut buf: bytes::Bytes = { @@ -370,6 +374,9 @@ pub fn parse_dtm_map(path: &Path) -> Result { file.read_to_end(&mut buf); Bytes::copy_from_slice(&buf) }; + parse_dtm_map_by_bytes(buf) +} +pub fn parse_dtm_map_by_bytes(mut buf: Bytes) -> Result { let mut header_buf_start = buf.copy_to_bytes(8); buf.advance(4); let bzip_buf = buf.copy_to_bytes(4); @@ -449,78 +456,308 @@ pub fn parse_dtm_map(path: &Path) -> Result { parse_vec_by_bytes(bytes) } fn parse_text(bytes: &mut Bytes) -> Vec { - fn copy_until(bytes: &mut Bytes, amount: usize, until: &[u8]) -> Vec { - let mut buf = vec![]; - loop { - if bytes.is_empty() { - break buf; - } - let copy = bytes.copy_to_bytes(amount); - if copy.as_bytes() == until { - break buf; - } else { - buf.extend_from_slice(copy.as_bytes()); - } - } - } - const TEXT_SECTION_START: [u8; 8] = *b"\x00>-Text-"; - let section = bytes.copy_to_bytes(TEXT_SECTION_START.len()); - if section.as_bytes() != TEXT_SECTION_START { - println!("Wrong text section start {:?} {:?}", section.as_bytes(), str::from_utf8(section.as_bytes())); - } + fn copy_until(bytes: &mut Bytes, amount: usize, until: &[u8]) -> Vec { + let mut buf = vec![]; + loop { + if bytes.is_empty() { + break buf; + } + let copy = bytes.copy_to_bytes(amount); + if copy.as_bytes() == until { + break buf; + } else { + buf.extend_from_slice(copy.as_bytes()); + } + } + } + const TEXT_SECTION_START: [u8; 8] = *b"\x00>-Text-"; + let section = bytes.copy_to_bytes(TEXT_SECTION_START.len()); + if section.as_bytes() != TEXT_SECTION_START { + println!( + "Wrong text section start {:?} {:?}", + section.as_bytes(), + str::from_utf8(section.as_bytes()) + ); + } let mut text_buffer = vec![]; loop { - let buf = copy_until(bytes, 1, &[0]); - let string = if buf.is_empty() { - String::new() - } else { WINDOWS_1251.decode(&buf).0.to_string() }; - if string.contains("LIT") || bytes.is_empty() { - break; - } - text_buffer.push(string); - } + let buf = copy_until(bytes, 1, &[0]); + let string = if buf.is_empty() { + String::new() + } else { + WINDOWS_1251.decode(&buf).0.to_string() + }; + if string.contains("LIT") || bytes.is_empty() { + break; + } + text_buffer.push(string); + } text_buffer } - let mut map = parse_by_2_bytes(surface_data); + let map = parse_by_2_bytes(surface_data); let decos = parse_decos(objects_data); let armies = parse_armies(armies_data); let buildings = parse_buildings(buildings_data); let lanterns = parse_lanterns(lanterns_data); let events = parse_events(events_data); let text = parse_text(&mut texts_data); + // Дальше идет лит картинка и хрен с ней + Ok(MapData { settings, buildings, decos, map, - events, - lanterns, + events, + lanterns, armies, - text, + text, }) } +pub fn parse_dtm_texts( + data: &mut MapData, +) -> ( + (String, String), + (String, String), + Vec<(String, String, String)>, + Vec<(String, String, String)>, + Vec<(String, String, String)>, +) { + let texts = &mut data.text; + let name = texts.remove(0); + let desc = texts.remove(0); + + let comp = texts.remove(0); + let next = texts.remove(0); + + let mut buildings = vec![]; + for _ in &data.buildings { + let name = texts.remove(0); + let owner = texts.remove(0); + let desc = texts.remove(0); + buildings.push((name, owner, desc)); + } + let mut armies = vec![]; + for _ in &data.armies { + let name = texts.remove(0); + let warlord = texts.remove(0); + let desc = texts.remove(0); + armies.push((name, warlord, desc)); + } + + let mut events = vec![]; + for _ in &data.events { + let name = texts.remove(0); + let question = texts.remove(0); + let text = texts.remove(0); + events.push((name, question, text)); + } + + // TODO: Named units handling + + dbg!(((name, desc), (comp, next), buildings, armies, events)) +} +trait FromDtm { + type From; + type Additional; + type Texts; + fn from_dtm(from: &Self::From, texts: &mut Self::Texts, additional: Self::Additional) -> Self; +} +impl FromDtm for Relations { + type Additional = (); + type From = RelationsData; + type Texts = (); + fn from_dtm(from: &Self::From, _: &mut Self::Texts, _: Self::Additional) -> Self { + Self { + player: from.a, + ally: from.b, + neighbour: from.c, + enemy: from.d + } + } +} +impl FromDtm for Army { + type Additional = usize; + type From = ArmyData; + type Texts = Vec<(String, String, String)>; + + fn from_dtm(army: &Self::From, armies_texts: &mut Self::Texts, id: Self::Additional) -> Self { + let mut troops = vec![]; + let units = UNITS.read().unwrap(); + let (army_name, _, _) = armies_texts.remove(0); + for troop in &army.troops.troops { + troops.extend([ + &units[troop.id.min(100) as usize] + ].iter().cycle().take(troop.amount as usize)); + } + let troops = troops.iter().map(|x: &&Unit| { + SendMut::new(Troop::new(::clone(x))) + }); + let stats = ArmyStats { + gold: 0, + mana: 0, + army_name + }; + let inventory = army.items_ids.map(|index| Item { index: index as usize }).to_vec(); + let pos = pos_from_dtm((army.x as usize, army.y as usize)); + let active = army.activity.to_bool(); + let control = Control::PC; + Army::new(troops.collect(), + stats, + inventory, + pos, + active, + control) + } +} +impl FromDtm for MapBuildingdata { + type Additional = usize; + type From = BuildingData; + type Texts = Vec<(String, String, String)>; + + fn from_dtm(building: &Self::From, building_texts: &mut Self::Texts, id: Self::Additional) -> Self { + let pos = pos_from_dtm((building.x as usize, building.y as usize)); + let size = pos_from_dtm((building.size_x as usize, building.size_y as usize)); + let events = building.event_ids.map(|x| x as usize).to_vec(); + let gold_income = building.gold_income as u64; + let mana_income = building.mana_income as u64; + let (name, desc, owner_name) = building_texts.remove(0); + let spells_to_learn = building.spell_ids.map(|x| x as usize).to_vec(); + let group = building.group as usize; + let owner = match building.owner_army_id as usize { + 255 => None, + x => Some(x) + }; + let additional_defense = building.additional_garrison_defense as u64; + let (items, max_items) = ( + building.artifact_ids.map(|x| Item {index: x as usize }).to_vec(), + building.number_of_artifacts_for_sale as usize, + ); + let (max_mana, max_gold) = ( + building.max_mana_income as u64, + building.max_gold_income as u64, + ); + let variant = match building.variant { + 1 => BuildingVariant::Town, + 2 => BuildingVariant::Village(Village { max_mana, max_gold }), + 3 => BuildingVariant::Castle, + 4 => BuildingVariant::Fort, + 5 => BuildingVariant::Tavern, + 6 => BuildingVariant::Market, + 7 => BuildingVariant::Church, + 8 => BuildingVariant::Forge, + 9 => BuildingVariant::Verf, + 10 => BuildingVariant::Altar, + 11 => BuildingVariant::Mine, + 12 => BuildingVariant::Ruins(items.clone()), + 13 => BuildingVariant::StoneBridge, + 14 => BuildingVariant::WoodenBridge, + _ => BuildingVariant::Ruins(items.clone()), + }; + let itemcost_range = (building.min_artifact_price as u64, building.max_artifact_price as u64); + let market = if matches!(variant, BuildingVariant::Market | BuildingVariant::Town | BuildingVariant::Church) && max_items != 0 { + Market { + itemcost_range, + items, + max_items + }.into() + } else { None }; + let recruitment = Some(Recruitment::new(building.recruits.map(|x| RecruitUnit { unit: x.id as usize, count: x.amount as usize}).to_vec(), 1.)); + let mut garrison = vec![]; + for unit in building.garrison_units { + let units = [UNITS.read().unwrap()[unit.id.min(100) as usize].clone()]; + let units = units + .iter() + .cycle() + .take(unit.count as usize) + .map(|x| x.clone()) + .collect::>(); + garrison.extend(units); + } + MapBuildingdata { + owner_name, + additional_defense, + name, + desc, + id, + events, + variant, + gold_income, + mana_income, + garrison, + group, + market, + recruitment, + owner, + pos, + spells_to_learn, + relations: Relations::from_dtm(&building.relations, &mut (), ()), + } + } +} +pub fn pos_from_dtm(pos: (usize, usize)) -> (usize, usize) { + (pos.1, pos.0) +} +pub fn convert_dtm_map(mut data: MapData) -> GameMap { + let ((name, description), (company_name, next_map), mut buildings_text, mut armies_text, mut events_text) = parse_dtm_texts(&mut data); + let mut tilemap = TileMap::new(data.map.iter().map(|x| *x as usize)); + for y in 0..(data.settings.size_y as usize) { + for x in 0..(data.settings.size_x as usize) { + tilemap[pos_from_dtm((x, y))] = data.map[x * tilemap.size + y] as usize; + } + } + let decomap: Vec = data.decos.iter().map(|x| x.id as usize).collect(); + let time = data.settings.start_time; + let seed = data.settings.seed as usize; + let winning_event_id = data.settings.winning_event_id as usize; + let losing_event_id = data.settings.losing_event_id as usize; + let scenario = match data.settings.scenario_variant { + 0 => ScenarioVariant::Single, + 1 => ScenarioVariant::Start(next_map), + _ => ScenarioVariant::Series(next_map), + }; + let size = (data.settings.size_x, data.settings.size_y); + let buildings = data.buildings.iter().enumerate().map(|(id, b)| MapBuildingdata::from_dtm(&b, &mut buildings_text, id)).collect(); + let armys = data.armies.iter().enumerate().map(|(id, a)| Army::from_dtm(&a, &mut armies_text, id)).collect(); + GameMap { + pause: false, + start: StartStats { + name, + description, + seed, + winning_event_id, + losing_event_id, + scenario, + time: Time::new(time as u64) + }, + time: Time::new(time as u64), + tilemap, + decomap, + relations: FractionsRelations::default(), + hitmap: TileMap::new((0..(size.0 * size.0)).map(|_| HitboxTile::default())), + buildings, + armys + } +} mod test { use std::{ fs::{self, File}, - io::Write, + io::{Read, Write}, os, path::Path, }; + use bytes::Bytes; + #[test] fn test() { - for path in fs::read_dir("../dt/Maps_Rus/").unwrap() { - let path = path.as_ref().unwrap(); - if !path.file_name().to_str().unwrap().ends_with("DTm") { - continue; - } - let data = super::parse_dtm_map(&path.path().as_path()).unwrap(); - let map = data.map; - let (map_height, map_width) = - (data.settings.size_x as usize, data.settings.size_y as usize); - let terr_ascii = [ - "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", - ]; - } + let buf = include_bytes!("../../../dt/Maps_Rus/РК1-Начало пути.DTm"); + let data = super::parse_dtm_map_by_bytes(Bytes::copy_from_slice(buf)).unwrap(); + let map = data.map; + let (map_height, map_width) = + (data.settings.size_x as usize, data.settings.size_y as usize); + let terr_ascii = [ + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", + ]; } } diff --git a/dt_lib/src/map/event.rs b/dt_lib/src/map/event.rs index 721ba6a..860b1d5 100644 --- a/dt_lib/src/map/event.rs +++ b/dt_lib/src/map/event.rs @@ -1,9 +1,5 @@ use crate::{ - battle::troop::Troop, - map::map::GameMap, - mutrc::SendMut, - time::time::Time, - units::unit::{Unit, UnitPos}, + battle::troop::Troop, items::Item, map::map::GameMap, mutrc::SendMut, time::time::Time, units::unit::{Unit, UnitPos} }; use advini::{Ini, IniParseError, Section, SectionError, Sections, SEPARATOR}; use serde; @@ -197,42 +193,6 @@ pub struct Conditions { #[default_value = "None"] pub in_building: Option, } -impl Conditions { - fn new( - relative_time: bool, - repeat: Option