tiger_lib/vic3/data/
themes.rs

1use std::path::PathBuf;
2
3use crate::block::Block;
4use crate::context::ScopeContext;
5use crate::db::{Db, DbKind};
6use crate::everything::Everything;
7use crate::game::GameFlags;
8use crate::item::{Item, ItemLoader, LoadAsFile, Recursive};
9use crate::pdxfile::PdxEncoding;
10use crate::report::{ErrorKey, warn};
11use crate::scopes::Scopes;
12use crate::token::Token;
13use crate::tooltipped::Tooltipped;
14use crate::validator::Validator;
15
16#[derive(Clone, Debug)]
17pub struct Theme {}
18
19inventory::submit! {
20    ItemLoader::Normal(GameFlags::Vic3, Item::Theme, Theme::add)
21}
22
23impl Theme {
24    pub fn add(db: &mut Db, key: Token, block: Block) {
25        db.add(Item::Theme, key, block, Box::new(Self {}));
26    }
27}
28
29impl DbKind for Theme {
30    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
31        let mut vd = Validator::new(block, data);
32
33        data.verify_exists(Item::Localization, key);
34        let loca = format!("{key}_desc");
35        data.verify_exists_implied(Item::Localization, &loca, key);
36
37        vd.req_field("category");
38        vd.field_choice("category", THEME_CATEGORIES);
39        if let Some(category) = block.get_field_value("category") {
40            data.verify_exists(Item::Localization, category);
41            let loca = format!("{category}_desc");
42            data.verify_exists_implied(Item::Localization, &loca, category);
43
44            vd.advice_field(
45                "map_textures",
46                "docs say map_textures, but it's papermap_textures_file",
47            );
48            if category.is("papermap_theme") {
49                vd.field_item("papermap_textures_file", Item::File);
50            } else {
51                vd.ban_field("papermap_textures_file", || "category papermap_theme");
52            }
53        }
54
55        vd.field_trigger_rooted("selectable", Tooltipped::No, Scopes::None);
56
57        vd.advice_field("skin", "docs say skin, but it's ui_skin");
58        vd.field_validated_value("ui_skin", |_, mut vd| {
59            vd.maybe_is("default");
60            vd.item(Item::Skin);
61        });
62
63        vd.advice_field("theme_object", "docs say theme_object, but it's map_object");
64        vd.multi_field_validated_block("map_object", |block, data| {
65            let mut vd = Validator::new(block, data);
66            vd.req_field_one_of(&["pdxmesh", "entity"]);
67            vd.field_item("pdxmesh", Item::Pdxmesh);
68            vd.field_item("entity", Item::Entity);
69            vd.req_field("locator");
70            vd.field_value("locator"); // TODO
71        });
72
73        vd.field_item("dlc", Item::Dlc);
74        vd.field_value("bulk_selection_group");
75        let mut sc = ScopeContext::new(Scopes::Country, key);
76        vd.field_script_value_no_breakdown("dynamic_weight", &mut sc);
77
78        // undocumented
79
80        // TODO: is this only for main_menu_image_theme ?
81        vd.field_item("video", Item::File);
82    }
83}
84
85#[derive(Clone, Debug)]
86pub struct Skin {}
87
88inventory::submit! {
89    ItemLoader::Full(GameFlags::Vic3, Item::Skin, PdxEncoding::Utf8Bom, ".skin", LoadAsFile::Yes, Recursive::No, Skin::add)
90}
91
92impl Skin {
93    pub fn add(db: &mut Db, key: Token, block: Block) {
94        if let Some(name) = block.get_field_value("name") {
95            db.add(Item::Skin, name.clone(), block, Box::new(Self {}));
96        } else {
97            let msg = "skin file with no name field";
98            warn(ErrorKey::FieldMissing).msg(msg).loc(key).push();
99        }
100    }
101}
102
103impl DbKind for Skin {
104    fn validate(&self, _key: &Token, block: &Block, data: &Everything) {
105        let mut vd = Validator::new(block, data);
106
107        vd.field_value("name");
108        vd.field_item("folder", Item::Directory);
109
110        if let Some(folder) = block.get_field_value("folder") {
111            for entry in data.fileset.get_files_under(&PathBuf::from(folder.as_str())) {
112                let path = entry.path().to_string_lossy();
113                // This slice should never fail, because of how get_files_under() works
114                let base_path = format!("gfx/interface{}", &path[folder.as_str().len()..]);
115                if !data.item_exists(Item::File, &base_path) {
116                    let msg = format!("file {base_path} does not exist");
117                    let info = "every skin file must override an existing interface file";
118                    warn(ErrorKey::ExtraFile).msg(msg).info(info).loc(entry).push();
119                }
120            }
121        }
122    }
123}
124
125const THEME_CATEGORIES: &[&str] = &[
126    "ui_skin_theme",
127    "papermap_theme",
128    "table_top_theme",
129    "table_asset_1_theme",
130    "table_asset_2_theme",
131    "table_asset_3_theme",
132    "table_asset_4_theme",
133    "table_asset_cloth_theme",
134    "building_sets_themes",
135    // undocumented
136    "main_menu_image_theme",
137];