use csv::{StringRecordsIter, Writer};
use getset::Getters;
use itertools::Itertools;
#[cfg(feature = "integration_sqlite")]use r2d2::Pool;
#[cfg(feature = "integration_sqlite")]use r2d2_sqlite::SqliteConnectionManager;
use rayon::prelude::*;
use serde_derive::{Serialize, Deserialize};
use uuid::Uuid;
use std::borrow::Cow;
#[cfg(test)] use std::collections::BTreeMap;
use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::SeekFrom;
use crate::binary::{ReadBytes, WriteBytes};
use crate::error::{RLibError, Result};
use crate::files::{Container, ContainerPath, DecodeableExtraData, Decodeable, EncodeableExtraData, Encodeable, FileType, table::{decode_table, DecodedData, local::TableInMemory, Table}, pack::Pack, RFileDecoded};
#[cfg(test)] use crate::schema::FieldType;
use crate::schema::{Definition, DefinitionPatch, Field, Schema};
use crate::utils::check_size_mismatch;
const GUID_MARKER: &[u8] = &[253, 254, 252, 255];
const VERSION_MARKER: &[u8] = &[252, 253, 254, 255];
#[cfg(test)] mod db_test;
#[derive(PartialEq, Clone, Debug, Getters, Serialize, Deserialize)]
#[getset(get = "pub")]
pub struct DB {
mysterious_byte: bool,
guid: String,
table: TableInMemory,
}
impl Decodeable for DB {
fn decode<R: ReadBytes>(data: &mut R, extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
let extra_data = extra_data.as_ref().ok_or(RLibError::DecodingMissingExtraData)?;
let schema = extra_data.schema.ok_or_else(|| RLibError::DecodingMissingExtraDataField("schema".to_owned()))?;
let table_name = extra_data.table_name.ok_or_else(|| RLibError::DecodingMissingExtraDataField("table_name".to_owned()))?;
let return_incomplete = extra_data.return_incomplete;
let (version, mysterious_byte, guid, entry_count) = Self::read_header(data)?;
let definitions = schema.definitions_by_table_name(table_name).ok_or({
if entry_count == 0 {
RLibError::DecodingDBNoDefinitionsFoundAndEmptyFile
} else {
RLibError::DecodingDBNoDefinitionsFound
}
})?;
let len = data.len()?;
let table = if version == 0 {
let mut altered = false;
let index_reset = data.stream_position()?;
let mut working_definition = Err(RLibError::DecodingDBNoDefinitionsFound);
for definition in definitions.iter().filter(|definition| *definition.version() < 1) {
data.seek(SeekFrom::Start(index_reset))?;
let db = decode_table(data, definition, Some(entry_count), return_incomplete, &mut altered);
if db.is_ok() && data.stream_position()? == len {
working_definition = Ok(definition);
break;
}
}
let definition = working_definition?;
let definition_patch = schema.patches_for_table(table_name).cloned().unwrap_or_default();
data.seek(SeekFrom::Start(index_reset))?;
TableInMemory::decode(data, definition, &definition_patch, Some(entry_count), return_incomplete, table_name)?
}
else {
let definition = definitions.iter()
.find(|definition| *definition.version() == version)
.ok_or(RLibError::DecodingDBNoDefinitionsFound)?;
let definition_patch = schema.patches_for_table(table_name).cloned().unwrap_or_default();
TableInMemory::decode(data, definition, &definition_patch, Some(entry_count), return_incomplete, table_name)?
};
check_size_mismatch(data.stream_position()? as usize, len as usize).map_err(|error| {
RLibError::DecodingTableIncomplete(error.to_string(), Box::new(table.clone()))
})?;
Ok(Self {
mysterious_byte,
guid,
table,
})
}
}
impl Encodeable for DB {
fn encode<W: WriteBytes>(&mut self, buffer: &mut W, extra_data: &Option<EncodeableExtraData>) -> Result<()> {
let table_has_guid = if let Some (ref extra_data) = extra_data { extra_data.table_has_guid } else { false };
let regenerate_table_guid = if let Some (ref extra_data) = extra_data { extra_data.regenerate_table_guid } else { false };
if table_has_guid {
buffer.write_all(GUID_MARKER)?;
if regenerate_table_guid || self.guid.is_empty() {
buffer.write_sized_string_u16(&Uuid::new_v4().to_string())?;
} else {
buffer.write_sized_string_u16(&self.guid)?;
}
}
if *self.table.definition().version() > 0 {
buffer.write_all(VERSION_MARKER)?;
buffer.write_i32(*self.table.definition().version())?;
}
buffer.write_bool(self.mysterious_byte)?;
buffer.write_u32(self.table.len() as u32)?;
self.table.encode(buffer)
}
}
impl DB {
pub fn new(definition: &Definition, definition_patch: Option<&DefinitionPatch>, table_name: &str) -> Self {
let table = TableInMemory::new(definition, definition_patch, table_name);
Self {
mysterious_byte: true,
guid: String::new(),
table,
}
}
pub fn read_header<R: ReadBytes>(data: &mut R) -> Result<(i32, bool, String, u32)> {
if data.len()? < 5 {
return Err(RLibError::DecodingDBNotADBTable);
}
let guid = if data.read_slice(4, false)? == GUID_MARKER {
data.read_sized_string_u16()?
} else {
data.seek(SeekFrom::Current(-4))?;
String::new()
};
let version = if data.read_slice(4, false)? == VERSION_MARKER {
data.read_i32()?
} else {
data.seek(SeekFrom::Current(-4))?;
0
};
let mysterious_byte = data.read_bool()?;
let entry_count = data.read_u32()?;
Ok((version, mysterious_byte, guid, entry_count))
}
pub fn definition(&self) -> &Definition {
self.table.definition()
}
pub fn patches(&self) -> &DefinitionPatch {
self.table.patches()
}
pub fn table_name(&self) -> &str {
self.table.table_name()
}
pub fn table_name_without_tables(&self) -> String {
if self.table_name().ends_with("_tables") {
self.table_name().to_owned().drain(..self.table_name().len() - 7).collect()
} else {
panic!("Either the code is broken, or someone with a few loose screws has renamed the fucking table folder. Crash for now, may return an error in the future.")
}
}
pub fn data(&'_ self) -> Cow<'_, [Vec<DecodedData>]> {
self.table.data()
}
#[cfg(feature = "integration_sqlite")]
pub fn sql_to_db(&mut self, pool: &Pool<SqliteConnectionManager>, pack_name: &str, file_name: &str) -> Result<()> {
self.table.sql_to_db(pool, pack_name, file_name)
}
pub fn data_mut(&mut self) -> &mut Vec<Vec<DecodedData>> {
self.table.data_mut()
}
pub fn set_data(&mut self, data: &[Vec<DecodedData>]) -> Result<()> {
self.table.set_data(data)
}
pub fn new_row(&self) -> Vec<DecodedData> {
self.table().new_row()
}
#[cfg(test)]
pub fn test_definition() -> Definition {
let mut definition = Definition::new(-100, None);
let mut fields = vec![];
fields.push(Field::new("bool".to_owned(), FieldType::Boolean, false, Some("true".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
fields.push(Field::new("f32".to_owned(), FieldType::F32, false, Some("1.0".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
fields.push(Field::new("f64".to_owned(), FieldType::F64, false, Some("2.0".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
fields.push(Field::new("i16".to_owned(), FieldType::I16, false, Some("3".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
fields.push(Field::new("i32".to_owned(), FieldType::I32, false, Some("4".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
fields.push(Field::new("i64".to_owned(), FieldType::I64, false, Some("5".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
fields.push(Field::new("colour".to_owned(), FieldType::ColourRGB, false, Some("ABCDEF".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
fields.push(Field::new("stringu8".to_owned(), FieldType::StringU8, false, Some("AAAA".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
fields.push(Field::new("stringu16".to_owned(), FieldType::StringU16, false, Some("BBBB".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
fields.push(Field::new("optionali16".to_owned(), FieldType::OptionalI16, false, Some("3".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
fields.push(Field::new("optionali32".to_owned(), FieldType::OptionalI32, false, Some("4".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
fields.push(Field::new("optionali64".to_owned(), FieldType::OptionalI64, false, Some("5".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
fields.push(Field::new("optionalstringu8".to_owned(), FieldType::OptionalStringU8, false, Some("Opt".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
fields.push(Field::new("optionalstringu16".to_owned(), FieldType::OptionalStringU16, false, Some("Opt".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
fields.push(Field::new("sequenceu16".to_owned(), FieldType::SequenceU16(Box::new(Definition::new(-100, None))), false, None, false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
fields.push(Field::new("sequenceu32".to_owned(), FieldType::SequenceU32(Box::new(Definition::new(-100, None))), false, None, false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
fields.push(Field::new("merged_colours_1_r".to_owned(), FieldType::I32, false, Some("AB".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), Some(0)));
fields.push(Field::new("merged_colours_1_g".to_owned(), FieldType::I32, false, Some("CD".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), Some(0)));
fields.push(Field::new("merged_colours_1_b".to_owned(), FieldType::I32, false, Some("EF".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), Some(0)));
fields.push(Field::new("bitwise_values".to_owned(), FieldType::I32, false, Some("4".to_string()), false, None, None, None, String::new(), 0, 5, BTreeMap::new(), None));
fields.push(Field::new("enum_values".to_owned(), FieldType::I32, false, Some("8".to_string()), false, None, None, None, String::new(), 0, 0, {
let mut bt = BTreeMap::new();
bt.insert(0, "test0".to_owned());
bt.insert(1, "test1".to_owned());
bt.insert(2, "test2".to_owned());
bt.insert(3, "test3".to_owned());
bt
}, None));
fields.push(Field::new("merged_colours_2_r".to_owned(), FieldType::I32, false, Some("AB".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), Some(1)));
fields.push(Field::new("merged_colours_2_g".to_owned(), FieldType::I32, false, Some("CD".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), Some(1)));
fields.push(Field::new("merged_colours_2_b".to_owned(), FieldType::I32, false, Some("EF".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), Some(1)));
definition.set_fields(fields);
definition
}
pub fn column_position_by_name(&self, column_name: &str) -> Option<usize> {
self.table.column_position_by_name(column_name)
}
pub fn len(&self) -> usize {
self.table.len()
}
pub fn is_empty(&self) -> bool {
self.table.is_empty()
}
pub fn set_definition(&mut self, new_definition: &Definition) {
self.table.set_definition(new_definition);
}
pub fn update(&mut self, new_definition: &Definition) {
self.set_definition(new_definition)
}
pub fn cascade_edition(pack: &mut Pack, schema: &Schema, table_name: &str, field: &Field, definition: &Definition, value_before: &str, value_after: &str) -> Vec<ContainerPath> {
let mut edited_paths = vec![];
if value_before == value_after {
return vec![];
}
let mut definition = definition.clone();
let patches = Some(definition.patches().clone());
let mut field = field.clone();
let mut table_name = table_name.to_owned();
while let Some((ref_table, ref_column)) = field.is_reference(patches.as_ref()) {
let ref_table_name = format!("{ref_table}_tables");
let table_folder = format!("db/{ref_table_name}");
let parent_files = pack.files_by_type_and_paths_mut(&[FileType::DB], &[ContainerPath::Folder(table_folder.to_owned())], true);
if !parent_files.is_empty() {
if let Ok(RFileDecoded::DB(table)) = parent_files[0].decoded() {
if let Some(index) = table.definition().column_position_by_name(&ref_column) {
definition = table.definition().clone();
field = definition.fields_processed()[index].clone();
table_name = table.table_name().to_owned();
continue;
}
}
}
break;
}
let fields_processed = definition.fields_processed();
let fields_localised = definition.localised_fields();
let (mut ref_table_data, _) = schema.tables_and_columns_referencing_our_own(&table_name, field.name(), &fields_processed, fields_localised);
ref_table_data.insert(table_name, vec![field.name().to_owned()]);
let container_paths = ref_table_data.keys().map(|ref_table_name| ContainerPath::Folder("db/".to_owned() + ref_table_name)).collect::<Vec<_>>();
let mut files = pack.files_by_paths_mut(&container_paths, true);
let mut loc_keys: Vec<(String, String)> = vec![];
for file in files.iter_mut() {
let path = file.path_in_container();
if let Ok(RFileDecoded::DB(table)) = file.decoded_mut() {
let fields_processed = table.definition().fields_processed();
let fields_localised = table.definition().localised_fields().to_vec();
let localised_order = table.definition().localised_key_order().to_vec();
let patches = table.definition().patches().clone();
let table_name = table.table_name().to_owned();
let table_name_no_tables = table.table_name_without_tables();
let table_data = table.data_mut();
let mut keys_edited = vec![];
let column_indexes = fields_processed.iter()
.enumerate()
.filter_map(|(index, field)| if ref_table_data[&table_name].iter().any(|name| name == field.name()) { Some(index) } else { None })
.collect::<Vec<usize>>();
for row in table_data.iter_mut() {
for column in &column_indexes {
let row_copy = row.to_vec();
if let Some(field_data) = row.get_mut(*column) {
match field_data {
DecodedData::StringU8(field_data) |
DecodedData::StringU16(field_data) |
DecodedData::OptionalStringU8(field_data) |
DecodedData::OptionalStringU16(field_data) => {
if field_data == value_before {
let mut locs_edited = vec![];
let is_key = fields_processed[*column].is_key(Some(&patches));
if is_key {
for loc_field in &fields_localised {
let loc_key = localised_order.iter().map(|pos| row_copy[*pos as usize].data_to_string()).collect::<Vec<_>>().join("");
locs_edited.push(format!("{}_{}_{}", table_name_no_tables, loc_field.name(), loc_key));
}
}
*field_data = value_after.to_owned();
if !locs_edited.is_empty() {
for (index, loc_field) in fields_localised.iter().enumerate() {
if let Some(key_old) = locs_edited.get(index) {
let loc_key = localised_order.iter().map(|pos| row[*pos as usize].data_to_string()).collect::<Vec<_>>().join("");
let key_new = format!("{}_{}_{}", table_name_no_tables, loc_field.name(), loc_key);
keys_edited.push((key_old.to_owned(), key_new.to_owned()))
}
}
}
if !edited_paths.contains(&path) {
edited_paths.push(path.clone());
}
}
}
_ => continue
}
}
}
}
if !keys_edited.is_empty() {
loc_keys.append(&mut keys_edited);
}
}
}
let mut loc_files = pack.files_by_type_mut(&[FileType::Loc]);
for file in &mut loc_files {
let path = file.path_in_container();
if let Ok(RFileDecoded::Loc(data)) = file.decoded_mut() {
let data = data.data_mut();
for row in data.iter_mut() {
if let Some(field_data) = row.get_mut(0) {
match field_data {
DecodedData::StringU8(field_data) |
DecodedData::StringU16(field_data) |
DecodedData::OptionalStringU8(field_data) |
DecodedData::OptionalStringU16(field_data) => {
for (key_old, key_new) in &loc_keys {
if field_data == key_old {
*field_data = key_new.to_owned();
if !edited_paths.contains(&path) {
edited_paths.push(path.clone());
}
}
}
}
_ => {}
}
}
}
}
}
edited_paths
}
pub fn merge(sources: &[&Self]) -> Result<Self> {
let table_names = sources.iter().map(|file| file.table_name()).collect::<HashSet<_>>();
if table_names.len() > 1 {
return Err(RLibError::RFileMergeTablesDifferentNames);
}
if sources.len() < 2 {
return Err(RLibError::RFileMergeTablesNotEnoughTablesProvided);
}
let mut new_table = Self::new(sources[0].definition(), Some(sources[0].patches()), sources[0].table_name());
let sources = sources.par_iter()
.map(|table| {
let mut table = table.table().clone();
table.set_definition(new_table.definition());
table
})
.collect::<Vec<_>>();
let new_data = sources.par_iter()
.map(|table| table.data().to_vec())
.flatten()
.collect::<Vec<_>>();
new_table.set_data(&new_data)?;
Ok(new_table)
}
pub fn tsv_import(records: StringRecordsIter<File>, field_order: &HashMap<u32, String>, schema: &Schema, table_name: &str, table_version: i32) -> Result<Self> {
let definition = schema.definition_by_name_and_version(table_name, table_version).ok_or(RLibError::DecodingDBNoDefinitionsFound)?;
let definition_patch = schema.patches_for_table(table_name);
let table = TableInMemory::tsv_import(records, definition, field_order, table_name, definition_patch)?;
let db = DB::from(table);
Ok(db)
}
pub fn tsv_export(&self, writer: &mut Writer<File>, table_path: &str, keys_first: bool) -> Result<()> {
self.table.tsv_export(writer, table_path, keys_first)
}
pub fn altered(&self) -> bool {
*self.table.altered()
}
pub fn generate_twad_key_deletes_keys(&self, keys: &mut HashSet<String>) {
let definition = self.definition();
match self.table_name() {
"agent_to_agent_attributes_tables" => {
let key_pos_0 = definition.column_position_by_name("agent").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("attribute").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string())
.collect::<Vec<_>>()
);
}
"animation_set_prebattle_group_view_configurations_tables" => {
let key_pos_0 = definition.column_position_by_name("attacker").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("defender").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
.collect::<Vec<_>>()
);
}
"armory_item_variants_tables" => {
let key_pos_0 = definition.column_position_by_name("armory_item").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("variant").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + " | " + &x[key_pos_1].data_to_string())
.collect::<Vec<_>>()
);
}
"battle_currency_deployables_cost_values_tables" => {
let key_pos_0 = definition.column_position_by_name("item_type").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("currency_type").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_0].data_to_string())
.collect::<Vec<_>>()
);
}
"battle_currency_units_cost_values_tables" => {
let key_pos_0 = definition.column_position_by_name("item_type").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("currency_type").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_0].data_to_string())
.collect::<Vec<_>>()
);
}
"battle_set_pieces_siege_items_tables" => {
let key_pos_0 = definition.column_position_by_name("army_name").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("siege_item").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_0].data_to_string())
.collect::<Vec<_>>()
);
}
"cai_agent_type_distribution_profile_junctions_tables" => {
let key_pos_0 = definition.column_position_by_name("distribution_profile_key").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("agent_type_key").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
.collect::<Vec<_>>()
);
}
"cai_agent_type_recruitment_profile_junctions_tables" => {
let key_pos_0 = definition.column_position_by_name("recruitment_profile_key").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("agent_type_key").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
.collect::<Vec<_>>()
);
}
"cai_personalities_budget_allocation_policy_junctions_tables" => {
let key_pos_0 = definition.column_position_by_name("budget_allocation_key").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("budget_context_key").unwrap_or_default();
let key_pos_2 = definition.column_position_by_name("budget_policy_key").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string() + ";" + &x[key_pos_2].data_to_string())
.collect::<Vec<_>>()
);
}
"cai_personalities_construction_preference_policy_building_junctions_tables" => {
let key_pos_0 = definition.column_position_by_name("building_key").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("policy_key").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
.collect::<Vec<_>>()
);
}
"cai_task_management_system_task_generator_variable_group_junctions_tables" => {
let key_pos_0 = definition.column_position_by_name("variable_group_key").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("variable_key").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_0].data_to_string())
.collect::<Vec<_>>()
);
}
"campaign_ai_manager_behaviour_junctions_tables" => {
let key_pos_0 = definition.column_position_by_name("manager").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("behaviour").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
.collect::<Vec<_>>()
);
}
"campaign_bmd_layer_group_bmd_export_types_junctions_tables" => {
let key_pos_0 = definition.column_position_by_name("bmd_export_types").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("campaign_bmd_layer_group").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string())
.collect::<Vec<_>>()
);
}
"campaign_difficulty_handicap_effects_tables" => {
let key_pos_0 = definition.column_position_by_name("campaign_difficulty_handicap").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("human").unwrap_or_default();
let key_pos_2 = definition.column_position_by_name("effect").unwrap_or_default();
let key_pos_3 = definition.column_position_by_name("optional_campaign_key").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| {
let human = if x[key_pos_1].data_to_string() == "true" { "1" } else { "0" };
x[key_pos_0].data_to_string().to_string() + "-" + human + "-" + &x[key_pos_2].data_to_string() + &x[key_pos_3].data_to_string()
})
.collect::<Vec<_>>()
);
}
"campaign_effect_scope_agent_junctions_tables" => {
let key_pos_0 = definition.column_position_by_name("agent_key").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("campaign_effect_scope_key").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string())
.collect::<Vec<_>>()
);
}
"campaign_features_tables" => {
let key_pos_0 = definition.column_position_by_name("feature").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("group").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
.collect::<Vec<_>>()
);
}
"campaign_markers_tables" => {
let key_pos_0 = definition.column_position_by_name("marker_type").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("group").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
.collect::<Vec<_>>()
);
}
"campaign_mercenary_unit_character_level_restrictions_tables" => {
let key_pos_0 = definition.column_position_by_name("unit").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("faction_override").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| {
let fover = if x[key_pos_1].data_to_string().is_empty() {
"".to_owned()
} else {
"_".to_string() + &x[key_pos_1].data_to_string()
};
x[key_pos_0].data_to_string().to_string() + &fover
})
.collect::<Vec<_>>()
);
}
"campaign_movement_spline_materials_tables" => {
let key_pos_0 = definition.column_position_by_name("id").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_0].data_to_string())
.collect::<Vec<_>>()
);
}
"campaign_rogue_army_leaders_tables" => {
let key_pos_0 = definition.column_position_by_name("campaign");
let key_pos_1 = definition.column_position_by_name("faction").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| {
let camp = if let Some(y) = key_pos_0 { x[y].data_to_string().to_string() } else { "".to_owned() };
camp + &x[key_pos_1].data_to_string()
})
.collect::<Vec<_>>()
);
}
"campaign_rogue_army_setups_tables" => {
let key_pos_0 = definition.column_position_by_name("faction").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("difficulty_level").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
.collect::<Vec<_>>()
);
}
"campaigns_campaign_variables_junctions_tables" => {
let key_pos_0 = definition.column_position_by_name("variable_key").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("campaign_name").unwrap_or_default();
let key_pos_2 = definition.column_position_by_name("difficulty").unwrap_or_default();
let key_pos_3 = definition.column_position_by_name("campaign_type").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_2].data_to_string() + &x[key_pos_3].data_to_string())
.collect::<Vec<_>>()
);
}
"campaign_tree_type_cultures_tables" => {
let key_pos_0 = definition.column_position_by_name("tree_type").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("culture").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_0].data_to_string())
.collect::<Vec<_>>()
);
}
"cdir_events_mission_issuer_junctions_tables" => {
let key_pos_0 = definition.column_position_by_name("issuer_key").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("mission_key").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string())
.collect::<Vec<_>>()
);
}
"character_experience_skill_tiers_tables" => {
let key_pos_0 = definition.column_position_by_name("for_army").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("for_navy").unwrap_or_default();
let key_pos_2 = definition.column_position_by_name("agent_key").unwrap_or_default();
let key_pos_3 = definition.column_position_by_name("skill_rank").unwrap_or_default();
let key_pos_4 = definition.column_position_by_name("optional_campaign_key").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| {
let mut ckey = String::new();
if x[key_pos_0].data_to_string() == "true" {
if x[key_pos_1].data_to_string() == "true" {
ckey.push_str("navy");
} else {
ckey.push_str("army");
}
} else {
ckey.push_str(&x[key_pos_2].data_to_string());
}
ckey + &x[key_pos_3].data_to_string() + &x[key_pos_4].data_to_string()
})
.collect::<Vec<_>>()
);
}
"culture_to_battle_animation_tables_tables" => {
let key_pos_0 = definition.column_position_by_name("battle_animations_table").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("culture_pack").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string())
.collect::<Vec<_>>()
);
}
"effect_bonus_value_id_action_results_additional_outcomes_junctions_tables" => {
let key_pos_0 = definition.column_position_by_name("effect").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("action_results_additional_outcome_record").unwrap_or_default();
let key_pos_2 = definition.column_position_by_name("bonus_value_id").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_2].data_to_string())
.collect::<Vec<_>>()
);
}
"effect_bonus_value_projectile_junctions_tables" => {
let key_pos_0 = definition.column_position_by_name("effect").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("bonus_value").unwrap_or_default();
let key_pos_2 = definition.column_position_by_name("projectile").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_2].data_to_string())
.collect::<Vec<_>>()
);
}
"effect_bonus_value_unit_attribute_junctions_tables" => {
let key_pos_0 = definition.column_position_by_name("effect").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("bonus_value_id").unwrap_or_default();
let key_pos_2 = definition.column_position_by_name("unit_attribute").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string() + ";" + &x[key_pos_2].data_to_string())
.collect::<Vec<_>>()
);
}
"effect_bonus_value_unit_record_junctions_tables" => {
let key_pos_0 = definition.column_position_by_name("bonus_value_id").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("effect").unwrap_or_default();
let key_pos_2 = definition.column_position_by_name("unit_record_key").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_2].data_to_string())
.collect::<Vec<_>>()
);
}
"ground_type_to_stat_effects_tables" => {
let key_pos_0 = definition.column_position_by_name("ground_type").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("affected_stat").unwrap_or_default();
let key_pos_2 = definition.column_position_by_name("affected_group").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_2].data_to_string() + &x[key_pos_1].data_to_string())
.collect::<Vec<_>>()
);
}
"land_units_to_unit_abilites_junctions_tables" => {
let key_pos_0 = definition.column_position_by_name("ability").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("land_unit").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string())
.collect::<Vec<_>>()
);
}
"loading_screen_quotes_to_campaigns_tables" => {
let key_pos_0 = definition.column_position_by_name("loading_quote").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("campaign").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_1].data_to_string())
.collect::<Vec<_>>()
);
}
"message_event_strings_tables" => {
let key_pos_0 = definition.column_position_by_name("event").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("optional_campaign_key").unwrap_or_default();
let key_pos_2 = definition.column_position_by_name("culture").unwrap_or_default();
let key_pos_3 = definition.column_position_by_name("optional_subculture").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_2].data_to_string() + &x[key_pos_3].data_to_string())
.collect::<Vec<_>>()
);
}
"mp_force_gen_template_junctions_tables" => {
let key_pos_0 = definition.column_position_by_name("template_key").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("battle_type").unwrap_or_default();
let key_pos_2 = definition.column_position_by_name("is_defender").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| {
let is_defender = if x[key_pos_2].data_to_string() == "true" { "1" } else { "0" };
x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + is_defender
})
.collect::<Vec<_>>()
);
}
"provincial_initiative_strength_levels_tables" => {
let key_pos_0 = definition.column_position_by_name("initiative_record").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("strength").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
.collect::<Vec<_>>()
);
}
"region_to_province_junctions_tables" => {
let key_pos_0 = definition.column_position_by_name("province").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("region").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string())
.collect::<Vec<_>>()
);
}
"ritual_payload_change_agent_capacities_tables" => {
let key_pos_0 = definition.column_position_by_name("payload").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("agent_record").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + "|" + &x[key_pos_1].data_to_string())
.collect::<Vec<_>>()
);
}
"ritual_payload_change_unit_capacities_tables" => {
let key_pos_0 = definition.column_position_by_name("payload").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("unit_record").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + "|" + &x[key_pos_1].data_to_string())
.collect::<Vec<_>>()
);
}
"slot_set_type_feature_junctions_tables" => {
let key_pos_0 = definition.column_position_by_name("slot_set_type").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("feature").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + "|" + &x[key_pos_1].data_to_string())
.collect::<Vec<_>>()
);
}
"special_ability_to_special_ability_phase_junctions_tables" => {
let key_pos_0 = definition.column_position_by_name("special_ability").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("phase").unwrap_or_default();
let key_pos_2 = definition.column_position_by_name("order").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + ":" + &x[key_pos_1].data_to_string() + ":" + &x[key_pos_2].data_to_string())
.collect::<Vec<_>>()
);
}
"start_pos_region_slot_templates_tables" => {
let key_pos_0 = definition.column_position_by_name("campaign").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("id").unwrap_or_default();
let key_pos_2 = definition.column_position_by_name("region").unwrap_or_default();
let key_pos_3 = definition.column_position_by_name("slot_template").unwrap_or_default();
let key_pos_4 = definition.column_position_by_name("slot_type").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x|
x[key_pos_0].data_to_string().to_string() +
&x[key_pos_1].data_to_string() +
&x[key_pos_2].data_to_string() +
&x[key_pos_3].data_to_string() +
&x[key_pos_4].data_to_string()
)
.collect::<Vec<_>>()
);
}
"start_pos_technologies_tables" => {
let key_pos_0 = definition.column_position_by_name("faction").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("technology").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string())
.collect::<Vec<_>>()
);
}
"taxes_effects_jct_tables" => {
let key_pos_0 = definition.column_position_by_name("tax_name").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("effect").unwrap_or_default();
let key_pos_2 = definition.column_position_by_name("optional_campaign_key").unwrap_or_default();
let key_pos_3 = definition.column_position_by_name("optional_difficulty_level").unwrap_or_default();
let key_pos_4 = definition.column_position_by_name("ai_only").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| {
let ai_only = if x[key_pos_4].data_to_string() == "true" { "1" } else { "0" };
x[key_pos_0].data_to_string().to_string() + ";" +
&x[key_pos_1].data_to_string() + ";" +
&x[key_pos_2].data_to_string() + ";" +
&x[key_pos_3].data_to_string() + ai_only
})
.collect::<Vec<_>>()
);
}
"ui_purchasable_effects_to_hex_ids_tables" => {
let key_pos_0 = definition.column_position_by_name("hex_id").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("category").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + "_" + &x[key_pos_1].data_to_string())
.collect::<Vec<_>>()
);
}
"units_custom_battle_permissions_tables" => {
let key_pos_0 = definition.column_position_by_name("unit").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("faction").unwrap_or_default();
let key_pos_2 = definition.column_position_by_name("general_unit").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| {
let general_unit = if x[key_pos_2].data_to_string() == "true" { "1" } else { "0" };
x[key_pos_0].data_to_string().to_string() + ":" + &x[key_pos_1].data_to_string() + ":" + general_unit
})
.collect::<Vec<_>>()
);
}
"workshop_categories_progress_levels_tables" => {
let key_pos_0 = definition.column_position_by_name("category").unwrap_or_default();
let key_pos_1 = definition.column_position_by_name("level_to_unlock").unwrap_or_default();
keys.extend(self.data()
.iter()
.map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_1].data_to_string())
.collect::<Vec<_>>()
);
}
_ => {
let key_cols = definition.key_column_positions_by_ca_order();
keys.extend(self.data()
.iter()
.map(|x| key_cols.iter()
.map(|y| x[*y].data_to_string())
.join("")
)
.collect::<Vec<_>>()
);
}
}
}
}
impl From<TableInMemory> for DB {
fn from(table: TableInMemory) -> Self {
Self {
mysterious_byte: true,
guid: Uuid::new_v4().to_string(),
table,
}
}
}