mirror of
https://github.com/doukutsu-rs/doukutsu-rs.git
synced 2026-08-24 13:02:49 -07:00
Separate game data parameters from locale files
This commit is contained in:
Generated
+223
-8
@@ -60,6 +60,12 @@ version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.62.0"
|
||||
@@ -124,6 +130,15 @@ version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
|
||||
|
||||
[[package]]
|
||||
name = "bs58"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.3"
|
||||
@@ -219,6 +234,7 @@ checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
@@ -542,8 +558,18 @@ version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
"darling_core 0.13.4",
|
||||
"darling_macro 0.13.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
|
||||
dependencies = [
|
||||
"darling_core 0.23.0",
|
||||
"darling_macro 0.23.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -556,21 +582,45 @@ dependencies = [
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"strsim 0.10.0",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
|
||||
dependencies = [
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.11.1",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_core 0.13.4",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
|
||||
dependencies = [
|
||||
"darling_core 0.23.0",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dasp_sample"
|
||||
version = "0.11.0"
|
||||
@@ -600,6 +650,15 @@ dependencies = [
|
||||
"bindgen 0.62.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "directories"
|
||||
version = "3.0.2"
|
||||
@@ -693,6 +752,7 @@ dependencies = [
|
||||
"serde_cbor",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"vec_mut_scan",
|
||||
@@ -709,6 +769,12 @@ version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.16.0"
|
||||
@@ -948,6 +1014,12 @@ version = "1.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
@@ -966,6 +1038,12 @@ version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.12"
|
||||
@@ -1144,6 +1222,17 @@ dependencies = [
|
||||
"mint",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.0"
|
||||
@@ -1151,7 +1240,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"hashbrown 0.17.1",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1494,7 +1585,7 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"darling 0.13.4",
|
||||
"proc-macro-crate 1.3.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1544,6 +1635,12 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.3.3"
|
||||
@@ -1805,6 +1902,12 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "1.3.1"
|
||||
@@ -1911,6 +2014,26 @@ dependencies = [
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ref-cast"
|
||||
version = "1.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
|
||||
dependencies = [
|
||||
"ref-cast-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ref-cast-impl"
|
||||
version = "1.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.3"
|
||||
@@ -1980,6 +2103,30 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"ref-cast",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"ref-cast",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
@@ -2072,6 +2219,38 @@ dependencies = [
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bs58",
|
||||
"chrono",
|
||||
"hex",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.14.0",
|
||||
"schemars 0.9.0",
|
||||
"schemars 1.2.1",
|
||||
"serde_core",
|
||||
"serde_json",
|
||||
"serde_with_macros",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "3.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660"
|
||||
dependencies = [
|
||||
"darling 0.23.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shared_library"
|
||||
version = "0.1.9"
|
||||
@@ -2124,6 +2303,12 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.24.1"
|
||||
@@ -2216,6 +2401,36 @@ dependencies = [
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85c17d80feb7334b40c484e45ed1a5273dfd8bfda537c3be2e74a06a6686f327"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde_core",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcef1a61bdb119096e153208ec5cbec23944ce8bca13be5c7f60c634f7403935"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.3"
|
||||
@@ -2271,7 +2486,7 @@ version = "0.19.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"indexmap 2.14.0",
|
||||
"toml_datetime 0.6.11",
|
||||
"winnow 0.5.40",
|
||||
]
|
||||
@@ -2282,7 +2497,7 @@ version = "0.25.12+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"indexmap 2.14.0",
|
||||
"toml_datetime 1.1.1+spec-1.1.0",
|
||||
"toml_parser",
|
||||
"winnow 1.0.3",
|
||||
|
||||
@@ -87,6 +87,7 @@ serde = { version = "1", features = ["derive"] }
|
||||
serde_derive = "1"
|
||||
serde_cbor = { version = "0.11", optional = true }
|
||||
serde_json = "1.0"
|
||||
serde_with = "3.8.1"
|
||||
strum = "0.24"
|
||||
strum_macros = "0.24"
|
||||
# remove and replace with extract_if, when our MSRV is 1.87
|
||||
|
||||
+148
@@ -1,4 +1,5 @@
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use bitfield::bitfield;
|
||||
@@ -8,6 +9,7 @@ use serde::de::{SeqAccess, Visitor};
|
||||
use serde::ser::SerializeTupleStruct;
|
||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use crate::framework::error::GameError;
|
||||
use crate::graphics::texture_set::G_MAG;
|
||||
|
||||
pub const ORG_NAME: &str = "io.github";
|
||||
@@ -579,3 +581,149 @@ impl<T> SliceExt for [T] {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[repr(u8)]
|
||||
pub enum Encoding {
|
||||
UTF8 = 0,
|
||||
ShiftJIS,
|
||||
UTF16BE,
|
||||
UTF16LE,
|
||||
ISO_2022_JP,
|
||||
ISO_8859_2,
|
||||
ISO_8859_3,
|
||||
ISO_8859_4,
|
||||
ISO_8859_5,
|
||||
ISO_8859_6,
|
||||
ISO_8859_7,
|
||||
ISO_8859_8,
|
||||
ISO_8859_8_I,
|
||||
ISO_8859_10,
|
||||
ISO_8859_13,
|
||||
ISO_8859_14,
|
||||
ISO_8859_15,
|
||||
ISO_8859_16,
|
||||
KOI8_R,
|
||||
KOI8_U,
|
||||
MACINTOSH,
|
||||
EUC_JP,
|
||||
EUC_KR,
|
||||
GB18030,
|
||||
GBK,
|
||||
BIG5,
|
||||
Win1250,
|
||||
Win1251,
|
||||
Win1252,
|
||||
Win1253,
|
||||
Win1254,
|
||||
Win1255,
|
||||
Win1256,
|
||||
Win1257,
|
||||
Win1258,
|
||||
}
|
||||
|
||||
impl Default for Encoding {
|
||||
fn default() -> Self {
|
||||
Self::ShiftJIS
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Encoding {
|
||||
fn from(s: &str) -> Self {
|
||||
s.parse().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Encoding {
|
||||
type Err = GameError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"utf-8" => Ok(Self::UTF8),
|
||||
"utf-16be" => Ok(Self::UTF16BE),
|
||||
"utf-16le" => Ok(Self::UTF16LE),
|
||||
|
||||
"iso-2022-jp" => Ok(Self::ISO_2022_JP),
|
||||
"iso-8859-2" => Ok(Self::ISO_8859_2),
|
||||
"iso-8859-3" => Ok(Self::ISO_8859_3),
|
||||
"iso-8859-4" => Ok(Self::ISO_8859_4),
|
||||
"iso-8859-5" => Ok(Self::ISO_8859_5),
|
||||
"iso-8859-6" => Ok(Self::ISO_8859_6),
|
||||
"iso-8859-7" => Ok(Self::ISO_8859_7),
|
||||
"iso-8859-8" => Ok(Self::ISO_8859_8),
|
||||
"iso-8859-8-i" => Ok(Self::ISO_8859_8_I),
|
||||
"iso-8859-10" => Ok(Self::ISO_8859_10),
|
||||
"iso-8859-13" => Ok(Self::ISO_8859_13),
|
||||
"iso-8859-14" => Ok(Self::ISO_8859_14),
|
||||
"iso-8859-15" => Ok(Self::ISO_8859_15),
|
||||
"iso-8859-16" => Ok(Self::ISO_8859_16),
|
||||
|
||||
"koi8-r" => Ok(Self::KOI8_R),
|
||||
"koi8-u" => Ok(Self::KOI8_U),
|
||||
|
||||
"macintosh" => Ok(Self::MACINTOSH),
|
||||
|
||||
"euc-jp" => Ok(Self::EUC_JP),
|
||||
"euc-kr" => Ok(Self::EUC_KR),
|
||||
|
||||
"gb18030" => Ok(Self::GB18030),
|
||||
"gbk" => Ok(Self::GBK),
|
||||
"big5" => Ok(Self::BIG5),
|
||||
|
||||
"windows-1250" => Ok(Self::Win1250),
|
||||
"windows-1251" => Ok(Self::Win1251),
|
||||
"windows-1252" => Ok(Self::Win1252),
|
||||
"windows-1253" => Ok(Self::Win1253),
|
||||
"windows-1254" => Ok(Self::Win1254),
|
||||
"windows-1255" => Ok(Self::Win1255),
|
||||
"windows-1256" => Ok(Self::Win1256),
|
||||
"windows-1257" => Ok(Self::Win1257),
|
||||
"windows-1258" => Ok(Self::Win1258),
|
||||
|
||||
_ => Err(Self::Err::ParseError(format!("Invalid or unsupported encoding: {}", s))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Encoding> for &'static encoding_rs::Encoding {
|
||||
fn from(value: Encoding) -> Self {
|
||||
match value {
|
||||
Encoding::ShiftJIS => encoding_rs::SHIFT_JIS,
|
||||
Encoding::UTF8 => encoding_rs::UTF_8,
|
||||
Encoding::UTF16BE => encoding_rs::UTF_16BE,
|
||||
Encoding::UTF16LE => encoding_rs::UTF_16LE,
|
||||
Encoding::ISO_2022_JP => encoding_rs::ISO_2022_JP,
|
||||
Encoding::ISO_8859_2 => encoding_rs::ISO_8859_2,
|
||||
Encoding::ISO_8859_3 => encoding_rs::ISO_8859_3,
|
||||
Encoding::ISO_8859_4 => encoding_rs::ISO_8859_4,
|
||||
Encoding::ISO_8859_5 => encoding_rs::ISO_8859_5,
|
||||
Encoding::ISO_8859_6 => encoding_rs::ISO_8859_6,
|
||||
Encoding::ISO_8859_7 => encoding_rs::ISO_8859_7,
|
||||
Encoding::ISO_8859_8 => encoding_rs::ISO_8859_8,
|
||||
Encoding::ISO_8859_8_I => encoding_rs::ISO_8859_8_I,
|
||||
Encoding::ISO_8859_10 => encoding_rs::ISO_8859_10,
|
||||
Encoding::ISO_8859_13 => encoding_rs::ISO_8859_13,
|
||||
Encoding::ISO_8859_14 => encoding_rs::ISO_8859_14,
|
||||
Encoding::ISO_8859_15 => encoding_rs::ISO_8859_15,
|
||||
Encoding::ISO_8859_16 => encoding_rs::ISO_8859_16,
|
||||
Encoding::KOI8_R => encoding_rs::KOI8_R,
|
||||
Encoding::KOI8_U => encoding_rs::KOI8_U,
|
||||
Encoding::MACINTOSH => encoding_rs::MACINTOSH,
|
||||
Encoding::EUC_JP => encoding_rs::EUC_JP,
|
||||
Encoding::EUC_KR => encoding_rs::EUC_KR,
|
||||
Encoding::GB18030 => encoding_rs::GB18030,
|
||||
Encoding::GBK => encoding_rs::GBK,
|
||||
Encoding::BIG5 => encoding_rs::BIG5,
|
||||
Encoding::Win1250 => encoding_rs::WINDOWS_1250,
|
||||
Encoding::Win1251 => encoding_rs::WINDOWS_1251,
|
||||
Encoding::Win1252 => encoding_rs::WINDOWS_1252,
|
||||
Encoding::Win1253 => encoding_rs::WINDOWS_1253,
|
||||
Encoding::Win1254 => encoding_rs::WINDOWS_1254,
|
||||
Encoding::Win1255 => encoding_rs::WINDOWS_1255,
|
||||
Encoding::Win1256 => encoding_rs::WINDOWS_1256,
|
||||
Encoding::Win1257 => encoding_rs::WINDOWS_1257,
|
||||
Encoding::Win1258 => encoding_rs::WINDOWS_1258,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+118
-55
@@ -1,4 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsStr;
|
||||
use std::io::{BufRead, BufReader, Cursor, Read};
|
||||
|
||||
use byteorder::{ReadBytesExt, LE};
|
||||
@@ -6,19 +7,18 @@ use case_insensitive_hashmap::CaseInsensitiveHashMap;
|
||||
use xmltree::Element;
|
||||
|
||||
use crate::case_insensitive_hashmap;
|
||||
use crate::common::{BulletFlag, Color, Rect};
|
||||
use crate::common::{BulletFlag, Color, Encoding, Rect};
|
||||
use crate::engine_constants::npcs::NPCConsts;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::filesystem;
|
||||
use crate::framework::gamepad::{Axis, Button};
|
||||
use crate::game::player::ControlMode;
|
||||
use crate::game::scripting::tsc::text_script::TextScriptEncoding;
|
||||
use crate::game::settings::Settings;
|
||||
use crate::game::shared_game_state::{FontData, Season};
|
||||
use crate::game::stage::StageData;
|
||||
use crate::graphics::texture_set::TextureSet;
|
||||
use crate::i18n::Locale;
|
||||
use crate::i18n::{Locale, Translation};
|
||||
use crate::sound::pixtone::{Channel, Envelope, PixToneParameters, Waveform};
|
||||
use crate::sound::SoundManager;
|
||||
|
||||
@@ -178,7 +178,7 @@ pub struct ExtraSoundtrack {
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TextScriptConsts {
|
||||
pub encoding: TextScriptEncoding,
|
||||
pub encoding: Encoding,
|
||||
pub encrypted: bool,
|
||||
pub reset_invicibility_on_any_script: bool,
|
||||
pub animated_face_pics: bool,
|
||||
@@ -346,20 +346,45 @@ impl DataType {
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum RootType {
|
||||
Base, // "/" path. Supports mods and locales.
|
||||
Mod, // Mod root (not CS+ challenge). Doesn't support mods, but supports locales.
|
||||
Translation // Translation of the main game. Doesn't support mods and locales.
|
||||
/// The root of the main game, located in the "data" folder. Supports mods, locales, and translation variants.
|
||||
Base,
|
||||
/// The root of the mod (not CS+ challenge). Doesn't support mods and translation variants, but supports locales.
|
||||
Mod,
|
||||
// Translation of the main game. Doesn't support mods, locales, and translation variants.
|
||||
Translation,
|
||||
}
|
||||
|
||||
impl RootType {
|
||||
/// Whether the root supports locales.
|
||||
///
|
||||
/// This determines whether the list of locales will be reloaded when switching to the specified root.
|
||||
pub fn support_locales(&self) -> bool {
|
||||
*self != Self::Translation
|
||||
}
|
||||
|
||||
/// Whether the root supports translation variants.
|
||||
///
|
||||
/// Translation variants are only intended for the base (main game) root, since historically, only
|
||||
/// the base game has has multiple translation variants for each language (locale). Mods, on the other hand,
|
||||
/// often have almost no translations into other languages, and even if they do, they typically have only one
|
||||
/// translation variant for a given language.
|
||||
pub fn support_translations(&self) -> bool {
|
||||
*self == Self::Base
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DataRoot {
|
||||
// Type of the game data in the root
|
||||
/// The type of game data in the root. It's `None` if the data type cannot be determined, or if the root is
|
||||
/// an incomplete translation, since incomplete (overlaid) translations don't contain any files specific to any data type,
|
||||
/// as all such files are untranslatable.
|
||||
pub data_type: Option<DataType>,
|
||||
pub root_type: RootType,
|
||||
|
||||
// Full path to the root dir
|
||||
/// Full path to the root dir.
|
||||
pub path: String,
|
||||
pub support_locales: bool,
|
||||
pub support_translations: bool,
|
||||
}
|
||||
|
||||
impl Default for DataRoot {
|
||||
@@ -368,7 +393,8 @@ impl Default for DataRoot {
|
||||
data_type: None,
|
||||
root_type: RootType::Base,
|
||||
path: "/".to_string(),
|
||||
support_locales: false
|
||||
support_locales: false,
|
||||
support_translations: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -386,12 +412,13 @@ impl DataRoot {
|
||||
root_type,
|
||||
path,
|
||||
|
||||
support_locales: root_type != RootType::Translation,
|
||||
support_locales: root_type.support_locales(),
|
||||
support_translations: root_type.support_translations(),
|
||||
}
|
||||
}
|
||||
|
||||
// Path to the directory with a base game data.
|
||||
// For CS+ it's "/base", for WiiWare "/root" etc.
|
||||
/// Returns the path to the directory with the base game data.
|
||||
/// For CS+ it's "/base", for WiiWare "/root", for CS 3D it's "/data".
|
||||
pub fn base_path(&self) -> String {
|
||||
let mut base_path = self.path.clone();
|
||||
|
||||
@@ -403,6 +430,10 @@ impl DataRoot {
|
||||
base_path
|
||||
}
|
||||
|
||||
/// Checks whether the translation root is complete.
|
||||
///
|
||||
/// Incomplete translations (also called overlaid translations) often lack necessary files — such as the stage table
|
||||
/// and non-translatable textures — since those files are expected to be loaded from the base root.
|
||||
pub fn is_complete(&self, ctx: &Context) -> bool {
|
||||
// Only overlaid translations may be incomplete.
|
||||
// As a result, incomplete translations don't have any specific files to determine their data type.
|
||||
@@ -426,6 +457,12 @@ impl DataRoot {
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn translation_path(&self, _ctx: &Context, translation: &Translation) -> String {
|
||||
assert!(self.support_locales, "Translation path requested for the root ({}), which doesn't support locales", &self.path);
|
||||
|
||||
[&self.base_path(), &translation.full_code(), "/"].join("")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -467,8 +504,9 @@ pub struct EngineConstants {
|
||||
pub missile_flags: Vec<u16>,
|
||||
pub base_locale: String,
|
||||
pub locales: Vec<Locale>,
|
||||
pub translations: Vec<Translation>,
|
||||
pub gamepad: GamepadConsts,
|
||||
pub stage_encoding: Option<TextScriptEncoding>,
|
||||
pub stage_encoding: Option<Encoding>,
|
||||
}
|
||||
|
||||
impl EngineConstants {
|
||||
@@ -1621,7 +1659,7 @@ impl EngineConstants {
|
||||
"title".to_owned(),
|
||||
],
|
||||
textscript: TextScriptConsts {
|
||||
encoding: TextScriptEncoding::ShiftJIS,
|
||||
encoding: Encoding::ShiftJIS,
|
||||
encrypted: true,
|
||||
reset_invicibility_on_any_script: true,
|
||||
animated_face_pics: false,
|
||||
@@ -1777,6 +1815,7 @@ impl EngineConstants {
|
||||
missile_flags: vec![200, 201, 202, 218, 550, 766, 880, 920, 1551],
|
||||
base_locale: "en".to_owned(),
|
||||
locales: Vec::new(),
|
||||
translations: Vec::new(),
|
||||
gamepad: {
|
||||
let mut holder = GamepadConsts {
|
||||
button_rects: HashMap::from([
|
||||
@@ -1818,6 +1857,10 @@ impl EngineConstants {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_base(&self) -> bool {
|
||||
!self.is_switch && !self.is_cs_plus && !self.is_demo
|
||||
}
|
||||
|
||||
pub fn apply_csplus_patches(&mut self, sound_manager: &mut SoundManager) {
|
||||
log::info!("Applying Cave Story+ constants patches...");
|
||||
|
||||
@@ -1869,10 +1912,6 @@ impl EngineConstants {
|
||||
let _ = sound_manager.set_sample_params(2, typewriter_sample);
|
||||
}
|
||||
|
||||
pub fn is_base(&self) -> bool {
|
||||
!self.is_switch && !self.is_cs_plus && !self.is_demo
|
||||
}
|
||||
|
||||
pub fn apply_csplus_nx_patches(&mut self) {
|
||||
log::info!("Applying Switch-specific Cave Story+ constants patches...");
|
||||
|
||||
@@ -1885,7 +1924,7 @@ impl EngineConstants {
|
||||
self.title.logo_rect = Rect { left: 0, top: 0, right: 214, bottom: 62 };
|
||||
self.inventory_dim_color = Color::from_rgba(0, 0, 32, 150);
|
||||
self.intro_background_color = Color::from_rgb(0, 0, 32);
|
||||
self.textscript.encoding = TextScriptEncoding::UTF8;
|
||||
self.textscript.encoding = Encoding::UTF8;
|
||||
self.textscript.encrypted = false;
|
||||
self.textscript.animated_face_pics = true;
|
||||
self.textscript.text_shadow = true;
|
||||
@@ -1913,6 +1952,7 @@ impl EngineConstants {
|
||||
let mut consts = Self::defaults();
|
||||
consts.roots.clone_from(&self.roots);
|
||||
consts.locales.clone_from(&self.locales);
|
||||
consts.translations.clone_from(&self.translations);
|
||||
consts.active_root.clone_from(&self.active_root);
|
||||
|
||||
*self = consts;
|
||||
@@ -1937,22 +1977,20 @@ impl EngineConstants {
|
||||
consts.active_root = consts.roots.get(&data_root).cloned().unwrap();
|
||||
|
||||
consts.rebuild_path_list(None, Season::current(), settings);
|
||||
let _ = consts.load_locales(ctx);
|
||||
let _ = consts.load_translations(ctx, true);
|
||||
log::debug!("Temporary instance of EngineConstants has been initialized");
|
||||
|
||||
// The following roots are locales with data files whose type differs from that of the main root.
|
||||
// Switching to a such root requires reloading the engine constants.
|
||||
for loc in &consts.locales {
|
||||
let mut full_path = if consts.is_cs_plus { "/base/" } else { "/" }.to_string();
|
||||
full_path.push_str(loc.code.as_str());
|
||||
full_path.push('/');
|
||||
for tran in &consts.translations {
|
||||
let tran_path = consts.active_root.translation_path(ctx, tran);
|
||||
|
||||
if filesystem::exists(ctx, full_path.as_str()) {
|
||||
let data_type = DataType::from_path(ctx, full_path.as_str());
|
||||
if filesystem::exists(ctx, tran_path.as_str()) {
|
||||
let data_type = DataType::from_path(ctx, tran_path.as_str());
|
||||
if data_type != consts.data_type {
|
||||
let root = DataRoot::load(ctx, full_path.clone(), RootType::Translation);
|
||||
let root = DataRoot::load(ctx, tran_path.clone(), RootType::Translation);
|
||||
if root.is_complete(ctx) {
|
||||
self.roots.insert(full_path.clone(), root);
|
||||
self.roots.insert(tran_path, root);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1983,7 +2021,7 @@ impl EngineConstants {
|
||||
self.rebuild_path_list(None, Season::current(), settings);
|
||||
|
||||
if data_root.support_locales {
|
||||
let _ = self.load_locales(ctx);
|
||||
let _ = self.load_translations(ctx, true);
|
||||
}
|
||||
|
||||
log::debug!("Path list after switching the root: {:?}", &self.base_paths);
|
||||
@@ -2083,13 +2121,14 @@ impl EngineConstants {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_locales(&mut self, ctx: &mut Context) -> GameResult {
|
||||
pub fn load_locales(&mut self, ctx: &Context) -> GameResult {
|
||||
self.locales.clear();
|
||||
|
||||
let locale_files = filesystem::read_dir_find(ctx, &self.base_paths, "locale/");
|
||||
|
||||
for locale_file in locale_files.unwrap() {
|
||||
if locale_file.extension().unwrap() != "json" {
|
||||
for locale_file in locale_files? {
|
||||
let file_ext = locale_file.extension().and_then(OsStr::to_str);
|
||||
if file_ext != Some("json") {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2100,38 +2139,62 @@ impl EngineConstants {
|
||||
};
|
||||
|
||||
let mut locale = Locale::new(ctx, &self.base_paths, &locale_code);
|
||||
|
||||
// Roots that support locales may have their own default locale, which differs from English
|
||||
if locale.code == self.base_locale {
|
||||
locale.is_present = true;
|
||||
locale.is_complete = true;
|
||||
locale.data_type = self.active_root.data_type;
|
||||
} else {
|
||||
let translation_path = [self.active_root.base_path().as_str(), locale.code.as_str(), "/"].join("");
|
||||
locale.is_present = filesystem::exists(ctx, &translation_path);
|
||||
if locale.is_present {
|
||||
locale.data_type = DataType::from_path(ctx, translation_path.as_str());
|
||||
|
||||
if locale.data_type.is_none() {
|
||||
let translation_root = self.roots.get(&translation_path);
|
||||
|
||||
locale.is_complete = translation_root.is_some(); // incomplete translation roots aren't added to the list
|
||||
locale.data_type = translation_root.unwrap_or(&self.active_root).data_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if locale_code == "jp" && filesystem::exists(ctx, "/base/credit_jp.tsc") {
|
||||
locale.set_font(FontData::new("csfontjp.fnt".to_owned(), 0.5, 0.0));
|
||||
}
|
||||
|
||||
self.locales.push(locale.clone());
|
||||
log::info!("Loaded locale {} ({})", locale_code, locale.name.clone());
|
||||
self.locales.push(locale);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_translations(&mut self, ctx: &Context, load_locales: bool) -> GameResult {
|
||||
self.translations.clear();
|
||||
|
||||
if self.locales.is_empty() || load_locales {
|
||||
self.load_locales(ctx)?;
|
||||
}
|
||||
|
||||
// If the root doesn't support locales, only the default translation variants will be generated from loaded locales
|
||||
if self.active_root.support_translations {
|
||||
let translation_files = filesystem::read_dir_find(ctx, &self.base_paths, "translation/")?;
|
||||
|
||||
for tran_file in translation_files {
|
||||
let file_ext = tran_file.extension().and_then(OsStr::to_str);
|
||||
if file_ext != Some("json") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let file = filesystem::open(ctx, tran_file)?;
|
||||
if let Ok(mut tran) = Translation::from_file(file) {
|
||||
if let Some(locale) = self.locales.iter().find(|loc| loc.code == tran.locale) {
|
||||
tran.analyze_data(ctx, &self);
|
||||
|
||||
log::info!("Loaded {} translation for {}", &tran.name, locale.name);
|
||||
self.translations.push(tran);
|
||||
} else {
|
||||
log::debug!("Translation {} ({:?}) omitted because locale {} is missing", &tran.name, &tran.code, tran.locale);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add default translations for locales that don't have one
|
||||
for locale in &self.locales {
|
||||
if !self.translations.iter().any(|t| t.locale == locale.code && t.is_default()) {
|
||||
let mut tran: Translation = locale.into();
|
||||
tran.analyze_data(ctx, &self);
|
||||
|
||||
log::debug!("Default translation variant created for {}", &locale.name);
|
||||
self.translations.push(tran);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn apply_constant_json_files(&mut self) {}
|
||||
|
||||
// TODO: load soundtrack metadata from a json file
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::io::{Cursor, Read};
|
||||
|
||||
use crate::common::Encoding;
|
||||
use crate::framework::error::GameError::ParseError;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::game::scripting::tsc::text_script::TextScriptEncoding;
|
||||
|
||||
pub fn put_varint(val: i32, out: &mut Vec<u8>) {
|
||||
let mut x = ((val as u32) >> 31) ^ ((val as u32) << 1);
|
||||
@@ -57,7 +57,7 @@ pub fn read_varint<I: Iterator<Item = u8>>(iter: &mut I) -> GameResult<i32> {
|
||||
Ok(((result << 31) ^ (result >> 1)) as i32)
|
||||
}
|
||||
|
||||
pub fn put_string(buffer: &mut Vec<u8>, out: &mut Vec<u8>, encoding: TextScriptEncoding) {
|
||||
pub fn put_string(buffer: &mut Vec<u8>, out: &mut Vec<u8>, encoding: Encoding) {
|
||||
if buffer.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4,17 +4,18 @@ use std::str::FromStr;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::common::Encoding;
|
||||
use crate::framework::error::GameError::ParseError;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::game::scripting::tsc::bytecode_utils::{put_string, put_varint};
|
||||
use crate::game::scripting::tsc::credit_script::CreditScript;
|
||||
use crate::game::scripting::tsc::opcodes::{CreditOpCode, TSCOpCode};
|
||||
use crate::game::scripting::tsc::parse_utils::{expect_char, read_number, skip_until};
|
||||
use crate::game::scripting::tsc::text_script::{TextScript, TextScriptEncoding};
|
||||
use crate::game::scripting::tsc::text_script::TextScript;
|
||||
|
||||
impl TextScript {
|
||||
/// Compiles a decrypted text script data into internal bytecode.
|
||||
pub fn compile(data: &[u8], strict: bool, encoding: TextScriptEncoding) -> GameResult<TextScript> {
|
||||
pub fn compile(data: &[u8], strict: bool, encoding: Encoding) -> GameResult<TextScript> {
|
||||
let mut event_map = HashMap::new();
|
||||
let mut iter = data.iter().copied().peekable();
|
||||
let mut last_event = 0;
|
||||
@@ -70,7 +71,7 @@ impl TextScript {
|
||||
fn compile_event<I: Iterator<Item=u8>>(
|
||||
iter: &mut Peekable<I>,
|
||||
strict: bool,
|
||||
encoding: TextScriptEncoding,
|
||||
encoding: Encoding,
|
||||
) -> GameResult<Vec<u8>> {
|
||||
let mut bytecode = Vec::new();
|
||||
let mut char_buf = Vec::with_capacity(16);
|
||||
@@ -313,7 +314,7 @@ impl TextScript {
|
||||
}
|
||||
|
||||
impl CreditScript {
|
||||
pub fn compile(data: &[u8], strict: bool, encoding: TextScriptEncoding) -> GameResult<CreditScript> {
|
||||
pub fn compile(data: &[u8], strict: bool, encoding: Encoding) -> GameResult<CreditScript> {
|
||||
let mut labels = HashMap::new();
|
||||
let mut bytecode = Vec::new();
|
||||
let mut iter = data.iter().copied().peekable();
|
||||
|
||||
@@ -8,6 +8,7 @@ use std::io::SeekFrom;
|
||||
use std::ops::ControlFlow;
|
||||
use std::ops::Not;
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
|
||||
use bitfield::bitfield;
|
||||
use num_traits::{clamp, FromPrimitive};
|
||||
@@ -17,8 +18,7 @@ use crate::common::{Direction, FadeDirection, FadeState, Rect};
|
||||
use crate::engine_constants::EngineConstants;
|
||||
use crate::entity::GameEntity;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::map_err_to_break;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::error::{GameError, GameResult, map_err_to_break};
|
||||
use crate::game::frame::UpdateTarget;
|
||||
use crate::game::npc::NPCContext;
|
||||
use crate::game::npc::NPC;
|
||||
@@ -47,139 +47,6 @@ bitfield! {
|
||||
pub cutscene_skip, set_cutscene_skip: 7;
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[repr(u8)]
|
||||
pub enum TextScriptEncoding {
|
||||
UTF8 = 0,
|
||||
ShiftJIS,
|
||||
UTF16BE,
|
||||
UTF16LE,
|
||||
ISO_2022_JP,
|
||||
ISO_8859_2,
|
||||
ISO_8859_3,
|
||||
ISO_8859_4,
|
||||
ISO_8859_5,
|
||||
ISO_8859_6,
|
||||
ISO_8859_7,
|
||||
ISO_8859_8,
|
||||
ISO_8859_8_I,
|
||||
ISO_8859_10,
|
||||
ISO_8859_13,
|
||||
ISO_8859_14,
|
||||
ISO_8859_15,
|
||||
ISO_8859_16,
|
||||
KOI8_R,
|
||||
KOI8_U,
|
||||
MACINTOSH,
|
||||
EUC_JP,
|
||||
EUC_KR,
|
||||
GB18030,
|
||||
GBK,
|
||||
BIG5,
|
||||
Win1250,
|
||||
Win1251,
|
||||
Win1252,
|
||||
Win1253,
|
||||
Win1254,
|
||||
Win1255,
|
||||
Win1256,
|
||||
Win1257,
|
||||
Win1258,
|
||||
}
|
||||
|
||||
impl From<&str> for TextScriptEncoding {
|
||||
fn from(s: &str) -> Self {
|
||||
match s {
|
||||
"utf-8" => Self::UTF8,
|
||||
|
||||
"iso-2022-jp" => Self::ISO_2022_JP,
|
||||
"iso-8859-2" => Self::ISO_8859_2,
|
||||
"iso-8859-3" => Self::ISO_8859_3,
|
||||
"iso-8859-4" => Self::ISO_8859_4,
|
||||
"iso-8859-5" => Self::ISO_8859_5,
|
||||
"iso-8859-6" => Self::ISO_8859_6,
|
||||
"iso-8859-7" => Self::ISO_8859_7,
|
||||
"iso-8859-8" => Self::ISO_8859_8,
|
||||
"iso-8859-8-i" => Self::ISO_8859_8_I,
|
||||
"iso-8859-10" => Self::ISO_8859_10,
|
||||
"iso-8859-13" => Self::ISO_8859_13,
|
||||
"iso-8859-14" => Self::ISO_8859_14,
|
||||
"iso-8859-15" => Self::ISO_8859_15,
|
||||
"iso-8859-16" => Self::ISO_8859_16,
|
||||
|
||||
"koi8-r" => Self::KOI8_R,
|
||||
"koi8-u" => Self::KOI8_U,
|
||||
|
||||
"macintosh" => Self::MACINTOSH,
|
||||
|
||||
"euc-jp" => Self::EUC_JP,
|
||||
"euc-kr" => Self::EUC_KR,
|
||||
|
||||
"gb18030" => Self::GB18030,
|
||||
"gbk" => Self::GBK,
|
||||
"big5" => Self::BIG5,
|
||||
|
||||
"windows-1250" => Self::Win1250,
|
||||
"windows-1251" => Self::Win1251,
|
||||
"windows-1252" => Self::Win1252,
|
||||
"windows-1253" => Self::Win1253,
|
||||
"windows-1254" => Self::Win1254,
|
||||
"windows-1255" => Self::Win1255,
|
||||
"windows-1256" => Self::Win1256,
|
||||
"windows-1257" => Self::Win1257,
|
||||
"windows-1258" => Self::Win1258,
|
||||
|
||||
"utf-16be" => Self::UTF16BE,
|
||||
"utf-16le" => Self::UTF16LE,
|
||||
|
||||
_ => Self::ShiftJIS,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TextScriptEncoding> for &'static encoding_rs::Encoding {
|
||||
fn from(value: TextScriptEncoding) -> Self {
|
||||
match value {
|
||||
TextScriptEncoding::ShiftJIS => encoding_rs::SHIFT_JIS,
|
||||
TextScriptEncoding::UTF8 => encoding_rs::UTF_8,
|
||||
TextScriptEncoding::UTF16BE => encoding_rs::UTF_16BE,
|
||||
TextScriptEncoding::UTF16LE => encoding_rs::UTF_16LE,
|
||||
TextScriptEncoding::ISO_2022_JP => encoding_rs::ISO_2022_JP,
|
||||
TextScriptEncoding::ISO_8859_2 => encoding_rs::ISO_8859_2,
|
||||
TextScriptEncoding::ISO_8859_3 => encoding_rs::ISO_8859_3,
|
||||
TextScriptEncoding::ISO_8859_4 => encoding_rs::ISO_8859_4,
|
||||
TextScriptEncoding::ISO_8859_5 => encoding_rs::ISO_8859_5,
|
||||
TextScriptEncoding::ISO_8859_6 => encoding_rs::ISO_8859_6,
|
||||
TextScriptEncoding::ISO_8859_7 => encoding_rs::ISO_8859_7,
|
||||
TextScriptEncoding::ISO_8859_8 => encoding_rs::ISO_8859_8,
|
||||
TextScriptEncoding::ISO_8859_8_I => encoding_rs::ISO_8859_8_I,
|
||||
TextScriptEncoding::ISO_8859_10 => encoding_rs::ISO_8859_10,
|
||||
TextScriptEncoding::ISO_8859_13 => encoding_rs::ISO_8859_13,
|
||||
TextScriptEncoding::ISO_8859_14 => encoding_rs::ISO_8859_14,
|
||||
TextScriptEncoding::ISO_8859_15 => encoding_rs::ISO_8859_15,
|
||||
TextScriptEncoding::ISO_8859_16 => encoding_rs::ISO_8859_16,
|
||||
TextScriptEncoding::KOI8_R => encoding_rs::KOI8_R,
|
||||
TextScriptEncoding::KOI8_U => encoding_rs::KOI8_U,
|
||||
TextScriptEncoding::MACINTOSH => encoding_rs::MACINTOSH,
|
||||
TextScriptEncoding::EUC_JP => encoding_rs::EUC_JP,
|
||||
TextScriptEncoding::EUC_KR => encoding_rs::EUC_KR,
|
||||
TextScriptEncoding::GB18030 => encoding_rs::GB18030,
|
||||
TextScriptEncoding::GBK => encoding_rs::GBK,
|
||||
TextScriptEncoding::BIG5 => encoding_rs::BIG5,
|
||||
TextScriptEncoding::Win1250 => encoding_rs::WINDOWS_1250,
|
||||
TextScriptEncoding::Win1251 => encoding_rs::WINDOWS_1251,
|
||||
TextScriptEncoding::Win1252 => encoding_rs::WINDOWS_1252,
|
||||
TextScriptEncoding::Win1253 => encoding_rs::WINDOWS_1253,
|
||||
TextScriptEncoding::Win1254 => encoding_rs::WINDOWS_1254,
|
||||
TextScriptEncoding::Win1255 => encoding_rs::WINDOWS_1255,
|
||||
TextScriptEncoding::Win1256 => encoding_rs::WINDOWS_1256,
|
||||
TextScriptEncoding::Win1257 => encoding_rs::WINDOWS_1257,
|
||||
TextScriptEncoding::Win1258 => encoding_rs::WINDOWS_1258,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
#[repr(u8)]
|
||||
pub enum TextScriptLine {
|
||||
|
||||
@@ -87,6 +87,7 @@ pub struct Settings {
|
||||
pub discord_rpc: bool,
|
||||
#[serde(default = "default_true")]
|
||||
pub allow_strafe: bool,
|
||||
pub translation: Option<String>,
|
||||
}
|
||||
|
||||
fn default_true() -> bool {
|
||||
@@ -95,7 +96,7 @@ fn default_true() -> bool {
|
||||
|
||||
#[inline(always)]
|
||||
fn current_version() -> u32 {
|
||||
25
|
||||
26
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@@ -359,6 +360,11 @@ impl Settings {
|
||||
}
|
||||
}
|
||||
|
||||
if self.version == 25 {
|
||||
self.version = 26;
|
||||
self.translation = None;
|
||||
}
|
||||
|
||||
if self.version != initial_version {
|
||||
log::info!("Upgraded configuration file from version {} to {}.", initial_version, self.version);
|
||||
}
|
||||
@@ -477,6 +483,7 @@ impl Default for Settings {
|
||||
cutscene_skip_mode: CutsceneSkipMode::Hold,
|
||||
discord_rpc: true,
|
||||
allow_strafe: true,
|
||||
translation: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::{cmp, ops::Div};
|
||||
|
||||
use chrono::{Datelike, Local};
|
||||
|
||||
use crate::common::{Color, ControlFlags, Direction, FadeState};
|
||||
use crate::common::{Color, ControlFlags, Direction, Encoding, FadeState};
|
||||
use crate::components::draw_common::{draw_number, Alignment};
|
||||
use crate::data::vanilla::VanillaExtractor;
|
||||
#[cfg(feature = "discord-rpc")]
|
||||
@@ -20,13 +20,13 @@ use crate::game::player::TargetPlayer;
|
||||
use crate::game::profile::GameProfile;
|
||||
use crate::game::scripting::tsc::credit_script::{CreditScript, CreditScriptVM};
|
||||
use crate::game::scripting::tsc::text_script::{
|
||||
ScriptMode, TextScript, TextScriptEncoding, TextScriptExecutionState, TextScriptVM,
|
||||
ScriptMode, TextScript, TextScriptExecutionState, TextScriptVM,
|
||||
};
|
||||
use crate::game::settings::Settings;
|
||||
use crate::game::stage::StageData;
|
||||
use crate::graphics::bmfont::BMFont;
|
||||
use crate::graphics::texture_set::TextureSet;
|
||||
use crate::i18n::Locale;
|
||||
use crate::i18n::{Locale, Translation};
|
||||
use crate::input::touch_controls::TouchControls;
|
||||
use crate::mod_list::ModList;
|
||||
use crate::mod_requirements::ModRequirements;
|
||||
@@ -346,6 +346,7 @@ pub struct SharedGameState {
|
||||
pub replay_state: ReplayState,
|
||||
pub mod_requirements: ModRequirements,
|
||||
pub loc: Locale,
|
||||
pub tran: Translation,
|
||||
pub tutorial_counter: u16,
|
||||
pub more_rust: bool,
|
||||
#[cfg(feature = "discord-rpc")]
|
||||
@@ -398,11 +399,11 @@ impl SharedGameState {
|
||||
let season = Season::current();
|
||||
constants.rebuild_path_list(None, season, &settings);
|
||||
|
||||
constants.load_locales(ctx)?;
|
||||
constants.load_translations(ctx, true)?;
|
||||
constants.rebuild_roots_list(ctx, &settings, &mut sound_manager);
|
||||
|
||||
let locale = SharedGameState::get_locale(&constants, &settings.locale).unwrap_or_default();
|
||||
let font = Self::try_update_locale(ctx, &mut constants, &settings, &mut sound_manager, &locale).unwrap();
|
||||
let (locale, translation) = SharedGameState::get_translation(&constants, &settings.locale, settings.translation.as_deref()).unwrap_or_default();
|
||||
let font = Self::try_update_locale(ctx, &mut constants, &settings, &mut sound_manager, &locale, &translation).unwrap();
|
||||
|
||||
let mod_list = ModList::load(ctx, &constants)?;
|
||||
|
||||
@@ -484,6 +485,7 @@ impl SharedGameState {
|
||||
replay_state: ReplayState::None,
|
||||
mod_requirements,
|
||||
loc: locale,
|
||||
tran: translation,
|
||||
tutorial_counter: 0,
|
||||
more_rust,
|
||||
#[cfg(feature = "discord-rpc")]
|
||||
@@ -563,8 +565,10 @@ impl SharedGameState {
|
||||
settings: &Settings,
|
||||
sound_manager: &mut SoundManager,
|
||||
locale: &Locale,
|
||||
translation: &Translation
|
||||
) -> GameResult<BMFont> {
|
||||
log::info!("Updating locale to {} ({})", &locale.code, &locale.name);
|
||||
assert!(locale.code == translation.locale);
|
||||
|
||||
// If the active root doesn't support locales (we're probably on a Translation root)
|
||||
// or the target locale isn't the default one (its data dir could be a root), we need to switch the root
|
||||
@@ -573,14 +577,14 @@ impl SharedGameState {
|
||||
|
||||
let path = if constants.active_root.support_locales {
|
||||
// If the active root support locales, we'll try to set active root to the translation data dir.
|
||||
format!("{}{}/", constants.active_root.base_path(), &locale.code)
|
||||
} else if constants.active_root.root_type == RootType::Translation && locale.code != constants.base_locale && locale.is_complete {
|
||||
constants.active_root.translation_path(ctx, &translation)
|
||||
} else if constants.active_root.root_type == RootType::Translation && locale.code != constants.base_locale && translation.is_complete {
|
||||
// Only the main game can have translations of different data types,
|
||||
// so we'll try to find the translation data dir in the main root.
|
||||
// We only need to do it if the translation is complete; otherwise it could be missing,
|
||||
// which results in applying the new locale to the menu entries, while the game data is
|
||||
// used from the previous locale.
|
||||
format!("{}{}/", main_root.base_path(), &locale.code)
|
||||
main_root.translation_path(ctx, &translation)
|
||||
} else {
|
||||
// The active root doesn't support translations and isn't a translation itself,
|
||||
// or the data files in the target lcoale data dir are incomplete (they're missing or the translation isn't a root).
|
||||
@@ -591,32 +595,30 @@ impl SharedGameState {
|
||||
constants.set_active_root(ctx, path, settings, sound_manager);
|
||||
}
|
||||
|
||||
constants.textscript.encoding = if let Some(encoding) = locale.encoding {
|
||||
encoding
|
||||
} else {
|
||||
constants.textscript.encoding = translation.encoding.unwrap_or_else(|| {
|
||||
// In freeware, Japanese and English text scripts use ShiftJIS.
|
||||
// In Cave Story+, Japanese scripts use ShiftJIS and English scripts use UTF-8.
|
||||
// In the Switch edition, UTF-8 is used for both English and Japanese scripts.
|
||||
match locale.code.as_str() {
|
||||
"jp" => {
|
||||
if constants.is_switch {
|
||||
TextScriptEncoding::UTF8
|
||||
Encoding::UTF8
|
||||
} else {
|
||||
TextScriptEncoding::ShiftJIS
|
||||
Encoding::ShiftJIS
|
||||
}
|
||||
}
|
||||
"en" => {
|
||||
if constants.is_base() {
|
||||
TextScriptEncoding::ShiftJIS
|
||||
Encoding::ShiftJIS
|
||||
} else {
|
||||
TextScriptEncoding::UTF8
|
||||
Encoding::UTF8
|
||||
}
|
||||
}
|
||||
_ => TextScriptEncoding::UTF8,
|
||||
_ => Encoding::UTF8,
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
constants.stage_encoding = locale.stage_encoding;
|
||||
constants.stage_encoding = translation.stage_encoding;
|
||||
|
||||
let font = BMFont::load(&constants.base_paths, &locale.font.path, ctx, locale.font.scale).or_else(|e| {
|
||||
log::warn!("Failed to load font, using built-in: {}", e);
|
||||
@@ -627,14 +629,16 @@ impl SharedGameState {
|
||||
}
|
||||
|
||||
pub fn update_locale(&mut self, ctx: &mut Context) {
|
||||
let Some(locale) = SharedGameState::get_locale(&self.constants, &self.settings.locale) else {
|
||||
let loc_res = SharedGameState::get_translation(&self.constants, &self.settings.locale, self.settings.translation.as_deref());
|
||||
let Some((locale, translation)) = loc_res else {
|
||||
return;
|
||||
};
|
||||
|
||||
let prev_root = self.constants.active_root.path.clone();
|
||||
|
||||
let font = Self::try_update_locale(ctx, &mut self.constants, &self.settings, &mut self.sound_manager, &locale).unwrap();
|
||||
let font = Self::try_update_locale(ctx, &mut self.constants, &self.settings, &mut self.sound_manager, &locale, &translation).unwrap();
|
||||
self.loc = locale;
|
||||
self.tran = translation;
|
||||
self.font = font;
|
||||
|
||||
let _ = if prev_root != self.constants.active_root.path {
|
||||
@@ -934,21 +938,23 @@ impl SharedGameState {
|
||||
return self.difficulty as u16;
|
||||
}
|
||||
|
||||
fn get_locale(constants: &EngineConstants, user_locale: &str) -> Option<Locale> {
|
||||
let mut out_locale = None;
|
||||
fn get_translation(constants: &EngineConstants, locale: &str, translation: Option<&str>) -> Option<(Locale, Translation)> {
|
||||
let mut out = None;
|
||||
|
||||
for locale in &constants.locales {
|
||||
if locale.code == constants.base_locale {
|
||||
out_locale = Some(locale.clone());
|
||||
for tran in &constants.translations {
|
||||
if tran.locale == constants.base_locale {
|
||||
let loc = constants.locales.iter().find(|l| l.code == locale)?;
|
||||
out = Some((loc.clone(), tran.clone()));
|
||||
}
|
||||
|
||||
if locale.code == user_locale {
|
||||
out_locale = Some(locale.clone());
|
||||
if tran.locale == locale && tran.code.as_deref() == translation {
|
||||
let loc = constants.locales.iter().find(|l| l.code == locale)?;
|
||||
out = Some((loc.clone(), tran.clone()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
out_locale
|
||||
out
|
||||
}
|
||||
|
||||
pub fn get_localized_soundtrack_name(&self, id: &str) -> String {
|
||||
|
||||
+4
-4
@@ -5,14 +5,14 @@ use byteorder::ReadBytesExt;
|
||||
use byteorder::LE;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::common::Color;
|
||||
use crate::common::{Color, Encoding};
|
||||
use crate::engine_constants::EngineConstants;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameError::ResourceLoadError;
|
||||
use crate::framework::error::{GameError, GameResult};
|
||||
use crate::framework::filesystem;
|
||||
use crate::game::map::{Map, NPCData};
|
||||
use crate::game::scripting::tsc::text_script::{TextScript, TextScriptEncoding};
|
||||
use crate::game::scripting::tsc::text_script::TextScript;
|
||||
use crate::game::SharedGameState;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||
@@ -225,7 +225,7 @@ fn zero_index(s: &[u8]) -> usize {
|
||||
s.iter().position(|&c| c == b'\0').unwrap_or(s.len())
|
||||
}
|
||||
|
||||
fn from_encoding(s: &[u8], encoding: Option<TextScriptEncoding>) -> String {
|
||||
fn from_encoding(s: &[u8], encoding: Option<Encoding>) -> String {
|
||||
if let Some(encoding) = encoding {
|
||||
let encoding: &encoding_rs::Encoding = encoding.into();
|
||||
return encoding.decode_without_bom_handling(s).0.into_owned();
|
||||
@@ -238,7 +238,7 @@ fn from_shift_jis(s: &[u8]) -> String {
|
||||
encoding_rs::SHIFT_JIS.decode_without_bom_handling(s).0.into_owned()
|
||||
}
|
||||
|
||||
fn from_csplus_stagetbl(s: &[u8], is_switch: bool, encoding: Option<TextScriptEncoding>) -> String {
|
||||
fn from_csplus_stagetbl(s: &[u8], is_switch: bool, encoding: Option<Encoding>) -> String {
|
||||
if let Some(encoding) = encoding {
|
||||
let encoding: &encoding_rs::Encoding = encoding.into();
|
||||
return encoding.decode_without_bom_handling(s).0.into_owned();
|
||||
|
||||
+129
-7
@@ -1,9 +1,13 @@
|
||||
use std::collections::HashMap;
|
||||
use std::io::Read;
|
||||
|
||||
use crate::engine_constants::DataType;
|
||||
use serde_with::{DisplayFromStr, serde_as};
|
||||
|
||||
use crate::common::Encoding;
|
||||
use crate::engine_constants::{DataType, EngineConstants};
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::filesystem;
|
||||
use crate::game::scripting::tsc::text_script::TextScriptEncoding;
|
||||
use crate::game::shared_game_state::FontData;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -11,8 +15,8 @@ pub struct Locale {
|
||||
pub code: String,
|
||||
pub name: String,
|
||||
pub font: FontData,
|
||||
pub encoding: Option<TextScriptEncoding>,
|
||||
pub stage_encoding: Option<TextScriptEncoding>,
|
||||
pub encoding: Option<Encoding>,
|
||||
pub stage_encoding: Option<Encoding>,
|
||||
strings: HashMap<String, String>,
|
||||
|
||||
// Properties of the translation data "root"
|
||||
@@ -39,7 +43,7 @@ impl Default for Locale {
|
||||
}
|
||||
|
||||
impl Locale {
|
||||
pub fn new(ctx: &mut Context, base_paths: &Vec<String>, code: &str) -> Locale {
|
||||
pub fn new(ctx: &Context, base_paths: &Vec<String>, code: &str) -> Locale {
|
||||
let file = filesystem::open_find(ctx, base_paths, &format!("locale/{code}.json")).unwrap();
|
||||
let json: serde_json::Value = serde_json::from_reader(file).unwrap();
|
||||
|
||||
@@ -52,12 +56,12 @@ impl Locale {
|
||||
let font = FontData::new(font_name, font_scale, 0.0);
|
||||
|
||||
let encoding = if let Some(enc) = strings.get("encoding").clone() {
|
||||
Some(TextScriptEncoding::from(enc.as_str()))
|
||||
Some(Encoding::from(enc.as_str()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let stage_encoding = if let Some(enc) = strings.get("stage_encoding").clone() {
|
||||
Some(TextScriptEncoding::from(enc.as_str()))
|
||||
Some(Encoding::from(enc.as_str()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -115,3 +119,121 @@ impl Locale {
|
||||
self.font = font;
|
||||
}
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Clone, Debug, serde::Deserialize)]
|
||||
/// Info about a translation variant.
|
||||
///
|
||||
/// A translation variant contains information about a specific translation of game files
|
||||
/// into a particular language (its name, code and game data parameters), while the [`Locale`] contains data
|
||||
/// common to all translation variants (font parameters and UI strings).
|
||||
pub struct Translation {
|
||||
/// The locale code for which the translation variant is intended.
|
||||
pub locale: String,
|
||||
/// Code of the translation variant.
|
||||
/// `None` represents the default translation variant for the specified locale.
|
||||
pub code: Option<String>,
|
||||
/// Name of the the translation variant (typically the name of the translator or translation team).
|
||||
pub name: String,
|
||||
|
||||
#[serde_as(as = "Option<DisplayFromStr>")]
|
||||
/// Encoding of text scripts.
|
||||
pub encoding: Option<Encoding>,
|
||||
#[serde_as(as = "Option<DisplayFromStr>")]
|
||||
/// Encoding of strings in the stage table.
|
||||
pub stage_encoding: Option<Encoding>,
|
||||
|
||||
// Properties of the translation data "root"
|
||||
#[serde(default, skip)]
|
||||
/// Whether the translation game data directory exists.
|
||||
pub available: bool,
|
||||
#[serde(default, skip)]
|
||||
/// Whether the translation data directory is complete.
|
||||
///
|
||||
/// Incomplete translations don't contain all necessary files,
|
||||
/// as they are intended to contain only translatable files (scripts, textures, the stage table);
|
||||
/// therefore, a type of a type of game data cannot be determined for them.
|
||||
pub is_complete: bool,
|
||||
#[serde(default, skip)]
|
||||
pub data_type: Option<DataType>,
|
||||
}
|
||||
|
||||
impl Default for Translation {
|
||||
fn default() -> Self {
|
||||
let locale = Locale::default();
|
||||
Self {
|
||||
locale: locale.code,
|
||||
code: None,
|
||||
name: "".to_string(),
|
||||
encoding: None,
|
||||
stage_encoding: None,
|
||||
available: false,
|
||||
is_complete: false,
|
||||
data_type: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Translation {
|
||||
pub fn new(ctx: &mut Context, base_paths: &Vec<String>, code: &str) -> GameResult<Self> {
|
||||
let file = filesystem::open_find(ctx, base_paths, &format!("translation/{code}.json"))?;
|
||||
Self::from_file(file)
|
||||
}
|
||||
|
||||
pub fn from_file<R: std::io::Read>(mut file: R) -> GameResult<Self> {
|
||||
let mut content = String::new();
|
||||
file.read_to_string(&mut content)?;
|
||||
|
||||
Ok(serde_json::from_str(&content)?)
|
||||
}
|
||||
|
||||
pub fn full_code(&self) -> String {
|
||||
let mut full_code = self.locale.clone();
|
||||
|
||||
if let Some(code) = &self.code {
|
||||
full_code.push('_');
|
||||
full_code.push_str(code);
|
||||
}
|
||||
|
||||
full_code
|
||||
}
|
||||
|
||||
/// Whether this translation is the default for its locale.
|
||||
pub fn is_default(&self) -> bool {
|
||||
self.code.is_none()
|
||||
}
|
||||
|
||||
pub fn analyze_data(&mut self, ctx: &Context, constants: &EngineConstants) {
|
||||
if self.locale == constants.base_locale && self.is_default() {
|
||||
self.available = true;
|
||||
self.is_complete = true;
|
||||
self.data_type = constants.active_root.data_type;
|
||||
} else {
|
||||
let tran_path = constants.active_root.translation_path(ctx, self);
|
||||
self.available = filesystem::exists(ctx, &tran_path);
|
||||
|
||||
if self.available {
|
||||
self.data_type = DataType::from_path(ctx, tran_path.as_str());
|
||||
|
||||
if self.data_type.is_none() {
|
||||
let translation_root = constants.roots.get(&tran_path);
|
||||
|
||||
self.is_complete = translation_root.is_some(); // incomplete translation roots aren't added to the list
|
||||
self.data_type = translation_root.unwrap_or(&constants.active_root).data_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Locale> for Translation {
|
||||
fn from(value: &Locale) -> Self {
|
||||
Self {
|
||||
locale: value.code.clone(),
|
||||
name: "".to_string(),
|
||||
encoding: value.encoding,
|
||||
stage_encoding: value.stage_encoding,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use num_traits::FromPrimitive;
|
||||
|
||||
use crate::common::Encoding;
|
||||
use crate::framework::error::{GameError::CommandLineError, GameResult};
|
||||
use crate::game::npc::NPC;
|
||||
use crate::game::scripting::tsc::text_script::{ScriptMode, TextScript, TextScriptEncoding};
|
||||
use crate::game::scripting::tsc::text_script::{ScriptMode, TextScript};
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::game::weapon::WeaponType;
|
||||
use crate::scene::game_scene::GameScene;
|
||||
@@ -249,7 +250,7 @@ impl CommandLineCommand {
|
||||
}
|
||||
CommandLineCommand::TSC(script) => {
|
||||
log::info!("Executing TSC script: {}", format!("#9999\n{}", script));
|
||||
match TextScript::compile(format!("#9999\n{}", script).as_bytes(), true, TextScriptEncoding::UTF8) {
|
||||
match TextScript::compile(format!("#9999\n{}", script).as_bytes(), true, Encoding::UTF8) {
|
||||
Ok(text_script) => {
|
||||
state.textscript_vm.set_debug_script(text_script);
|
||||
state.textscript_vm.set_mode(ScriptMode::Debug);
|
||||
|
||||
+39
-10
@@ -1,6 +1,7 @@
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::common::Rect;
|
||||
use crate::engine_constants::RootType;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics::VSyncMode;
|
||||
@@ -101,7 +102,7 @@ impl Default for SoundtrackMenuEntry {
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
enum LanguageMenuEntry {
|
||||
Title,
|
||||
Language(String),
|
||||
Language(String, Option<String>), // locale code, translation code (None represents a default translation variant)
|
||||
Spacer,
|
||||
Warning,
|
||||
Back,
|
||||
@@ -379,8 +380,13 @@ impl SettingsMenu {
|
||||
let mut has_diff_data_locales = false;
|
||||
let mut has_no_data_locales = false;
|
||||
|
||||
for locale in &state.constants.locales {
|
||||
if !locale.is_present {
|
||||
for tran in &state.constants.translations {
|
||||
if !tran.available {
|
||||
if !tran.is_default() {
|
||||
log::debug!("Skipped locale: {:?}", tran);
|
||||
continue;
|
||||
}
|
||||
|
||||
has_no_data_locales = true;
|
||||
}
|
||||
|
||||
@@ -388,15 +394,37 @@ impl SettingsMenu {
|
||||
let main_root = state.constants.roots.get("/").unwrap();
|
||||
let active_root = &state.constants.active_root;
|
||||
|
||||
let mut entry = locale.name.clone();
|
||||
if !locale.is_present {
|
||||
entry.push(no_data_char.0)
|
||||
} else if locale.code != state.constants.base_locale && active_root.support_locales && active_root.data_type != locale.data_type || main_root.data_type != locale.data_type {
|
||||
let loc = state.constants.locales.iter().find(|l| l.code == tran.locale).unwrap();
|
||||
let mut entry = loc.name.clone();
|
||||
|
||||
// Workaround for locales like "Chinese (Simplified)"
|
||||
if !tran.is_default() {
|
||||
if entry.ends_with(')') {
|
||||
let _ = entry.pop();
|
||||
entry.push_str(", ");
|
||||
} else {
|
||||
entry.push_str(" (");
|
||||
}
|
||||
|
||||
entry.push_str(tran.name.as_str());
|
||||
entry.push(')');
|
||||
}
|
||||
|
||||
if !tran.available {
|
||||
entry.push(no_data_char.0);
|
||||
} else if
|
||||
tran.locale != state.constants.base_locale &&
|
||||
active_root.support_locales && active_root.data_type != tran.data_type ||
|
||||
(main_root.data_type != tran.data_type && active_root.root_type == RootType::Translation)
|
||||
{
|
||||
// We compare the data type of the translation with the data type of the active root,
|
||||
// if it supports locales and isn't a translation root (in case it's a mod root that, for some reason,
|
||||
// doesn't support locales), or with the data type of the main root otherwise.
|
||||
has_diff_data_locales = true;
|
||||
entry.push(save_warn_char.0);
|
||||
}
|
||||
|
||||
self.language.push_entry(LanguageMenuEntry::Language(locale.code.clone()), MenuEntry::Active(entry));
|
||||
self.language.push_entry(LanguageMenuEntry::Language(tran.locale.clone(), tran.code.clone()), MenuEntry::Active(entry));
|
||||
}
|
||||
|
||||
self.language.push_entry(LanguageMenuEntry::Spacer, MenuEntry::Spacer(8.0));
|
||||
@@ -982,12 +1010,13 @@ impl SettingsMenu {
|
||||
)?;
|
||||
}
|
||||
CurrentMenu::LanguageMenu => match self.language.tick(controller, state) {
|
||||
MenuSelectionResult::Selected(LanguageMenuEntry::Language(new_locale), entry) => {
|
||||
MenuSelectionResult::Selected(LanguageMenuEntry::Language(new_locale, new_translation), entry) => {
|
||||
if let MenuEntry::Active(_) = entry {
|
||||
if new_locale == state.settings.locale {
|
||||
if new_locale == state.settings.locale && new_translation == state.settings.translation {
|
||||
self.current = CurrentMenu::MainMenu;
|
||||
} else {
|
||||
state.settings.locale = new_locale;
|
||||
state.settings.translation = new_translation;
|
||||
state.update_locale(ctx);
|
||||
|
||||
let _ = state.settings.save(ctx);
|
||||
|
||||
Reference in New Issue
Block a user