diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index a232e910..4fe716dc 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -90,7 +90,6 @@ jobs: run: | cargo build cargo test - bunx @biomejs/biome format --write jsonlib/bindings/*.ts - name: Check for changes run: | git diff --exit-code diff --git a/.gitignore b/.gitignore index 28787590..97828bb7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,11 +5,15 @@ .DS_Store ._* -# Ignore vanilla raws if present -vanilla-raws/ +# Ignore testing raws if present +test-data/ # Ignore any test *json exports *.json !parsed-raws.json !.vscode/*.json !.zed/*.json + +# Ignore any database files +*.db +*.db-* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..47c78bd0 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,460 @@ +# Contributing to dfraw_parser + +Thank you for considering contributing to dfraw_parser! This document provides guidelines to help you contribute effectively. + +## Table of Contents + +- [Code of Conduct](#code-of-conduct) +- [How to Contribute](#how-to-contribute) +- [Development Setup](#development-setup) +- [Documentation Guidelines](#documentation-guidelines) +- [Coding Standards](#coding-standards) +- [Testing Guidelines](#testing-guidelines) +- [Pull Request Process](#pull-request-process) + +## Code of Conduct + +This project follows a standard code of conduct. Please be respectful and constructive in all interactions. + +## How to Contribute + +### Reporting Bugs + +- Check if the issue already exists in the issue tracker +- Use a clear and descriptive title +- Describe the exact steps to reproduce the problem +- Include relevant code samples and error messages +- Specify your environment (OS, Rust version, DF version) + +### Suggesting Enhancements + +- Use a clear and descriptive title +- Provide a detailed description of the suggested enhancement +- Explain why this enhancement would be useful +- Include code examples if applicable + +### Contributing Code + +1. Fork the repository +2. Create a new branch (`git checkout -b feature/your-feature-name`) +3. Make your changes +4. Write or update tests as needed +5. Ensure all tests pass +6. Update documentation +7. Commit your changes with clear commit messages +8. Push to your fork +9. Open a pull request + +## Development Setup + +### Prerequisites + +- Rust 1.75 or later +- Cargo +- Git + +### Building the Project + +```bash +# Clone the repository +git clone https://github.com/nwesterhausen/dfraw_parser.git +cd dfraw_parser + +# Build all workspace members +cargo build --workspace + +# Run tests +cargo test --workspace + +# Check code with clippy +cargo clippy --workspace -- -D warnings +``` + +### Running Tests + +```bash +# Run all tests +cargo test --workspace + +# Run tests for a specific package +cargo test -p dfraw_parser + +# Run a specific test +cargo test test_name +``` + +## Documentation Guidelines + +Good documentation is crucial for library usability. All public APIs must be documented. + +### General Documentation Rules + +1. **All public items must have documentation comments** (`///` or `//!`) +2. **First line should be a concise summary** (aim for < 80 characters) +3. **Add a blank line before detailed descriptions** +4. **Use proper Markdown formatting** +5. **Include examples for non-trivial functions** + +### Documentation Structure + +Use these sections in order, as appropriate: + +```rust +/// Brief one-line summary. +/// +/// More detailed description if needed. Can span multiple paragraphs. +/// +/// # Arguments +/// +/// * `param1` - Description of first parameter +/// * `param2` - Description of second parameter +/// +/// # Returns +/// +/// Description of what is returned +/// +/// # Errors +/// +/// Description of when and why this function returns an error +/// +/// # Panics +/// +/// Description of when this function might panic (if applicable) +/// +/// # Examples +/// +/// ``` +/// use dfraw_parser::function_name; +/// +/// let result = function_name(arg1, arg2); +/// assert_eq!(result, expected); +/// ``` +/// +/// # Safety +/// +/// (Only for unsafe functions) Explain safety invariants +pub fn function_name(param1: Type1, param2: Type2) -> Result { + // implementation +} +``` + +### Module Documentation + +Modules should have documentation explaining their purpose and contents: + +```rust +//! Brief description of what this module provides. +//! +//! More detailed explanation of the module's purpose and organization. +//! +//! # Examples +//! +//! ``` +//! use dfraw_parser::module_name; +//! +//! // Example usage +//! ``` +``` + +### Examples in Documentation + +#### Do ✅ + +```rust +/// Parses a creature definition from a raw file. +/// +/// # Arguments +/// +/// * `raw_text` - The raw text content to parse +/// +/// # Returns +/// +/// Returns a `Creature` object or an error if parsing fails. +/// +/// # Examples +/// +/// ``` +/// use dfraw_parser::Creature; +/// +/// let raw_text = "[CREATURE:DOG]"; +/// let creature = Creature::from_raw(raw_text)?; +/// ``` +pub fn from_raw(raw_text: &str) -> Result { + // implementation +} +``` + +#### Don't ❌ + +```rust +/// Parses creature +pub fn from_raw(raw_text: &str) -> Result { + // implementation +} +``` + +### Documentation for Different Item Types + +#### Functions and Methods + +```rust +/// Returns the identifier of this creature. +/// +/// The identifier is the unique name used in raw files to reference this creature. +/// +/// # Examples +/// +/// ``` +/// # use dfraw_parser::Creature; +/// let creature = Creature::new("DOG"); +/// assert_eq!(creature.get_identifier(), "DOG"); +/// ``` +#[must_use] +pub fn get_identifier(&self) -> &str { + &self.identifier +} +``` + +#### Structs and Enums + +```rust +/// Represents a parsed creature from Dwarf Fortress raw files. +/// +/// A creature definition includes all castes, body parts, behaviors, +/// and other properties defined in the raw files. +#[derive(Debug, Clone)] +pub struct Creature { + /// The unique identifier for this creature + pub identifier: String, + /// Optional description text shown in-game + pub description: Option, + // ... other fields +} + +/// Represents different material states in Dwarf Fortress. +#[derive(Debug, Clone, Copy)] +pub enum MaterialState { + /// Solid state (e.g., ice, stone) + Solid, + /// Liquid state (e.g., water, magma) + Liquid, + /// Gas state (e.g., steam, miasma) + Gas, +} +``` + +#### Traits + +```rust +/// Provides search functionality for game objects. +/// +/// Implement this trait for types that should be searchable by +/// generating search strings from their properties. +/// +/// # Examples +/// +/// ``` +/// use dfraw_parser::traits::Searchable; +/// +/// struct MyObject { +/// name: String, +/// description: String, +/// } +/// +/// impl Searchable for MyObject { +/// fn get_search_vec(&self) -> Vec { +/// vec![self.name.clone(), self.description.clone()] +/// } +/// } +/// ``` +pub trait Searchable { + /// Returns a vector of searchable strings for this object. + fn get_search_vec(&self) -> Vec; +} +``` + +See the [documentation reference](./DOCUMENTATION_REFERENCE.md) for more examples of how to +document this library. + +### Example Code Attributes + +Use these attributes as appropriate: + +- `no_run` - Code compiles but shouldn't be executed (e.g., needs external files) +- `ignore` - Skip this example in tests +- `should_panic` - Example is expected to panic +- `compile_fail` - Example should fail to compile (for demonstrating errors) + +```rust +/// # Examples +/// +/// ```no_run +/// use dfraw_parser::parse; +/// +/// // This needs actual game files to run +/// let result = parse(&options); +/// ``` +``` + +### Testing Documentation + +Run doc tests to ensure examples work: + +```bash +cargo test --doc +``` + +Generate and view documentation locally: + +```bash +cargo doc --no-deps --open +``` + +## Coding Standards + +### Rust Style Guidelines + +- Follow the [Rust API Guidelines](https://rust-lang.github.io/api-guidelines/) +- Use `rustfmt` for code formatting: `cargo fmt` +- Use `clippy` for linting: `cargo clippy` +- Avoid `unwrap()` in library code (use proper error handling) + +### Workspace Lints + +This project enforces strict lints: + +```toml +[workspace.lints.rust] +unsafe_code = "forbid" +missing_docs = "warn" +unreachable_code = "warn" +unreachable_patterns = "warn" + +[workspace.lints.clippy] +enum_glob_use = "deny" +pedantic = "deny" +nursery = "deny" +unwrap_used = "deny" +``` + +### Naming Conventions + +- Use `snake_case` for functions, methods, variables, and modules +- Use `CamelCase` for types (structs, enums, traits) +- Use `SCREAMING_SNAKE_CASE` for constants +- Use descriptive names that indicate purpose + +### Error Handling + +- Use `Result` for operations that can fail +- Define specific error types using `thiserror` +- Provide context in error messages +- Document error conditions in function docs + +```rust +/// # Errors +/// +/// Returns `ParserError::InvalidToken` if the token format is invalid. +/// Returns `ParserError::IoError` if file reading fails. +pub fn parse_file(path: &Path) -> Result { + // implementation +} +``` + +## Testing Guidelines + +### Test Organization + +- Unit tests go in a `tests` module within the same file +- Integration tests go in the `tests/` directory +- Use descriptive test names that indicate what is being tested + +```rust +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_valid_creature_tag() { + let result = parse_creature_tag("[CREATURE:DOG]"); + assert!(result.is_ok()); + assert_eq!(result.unwrap().identifier, "DOG"); + } + + #[test] + fn test_parse_invalid_creature_tag_returns_error() { + let result = parse_creature_tag("[INVALID]"); + assert!(result.is_err()); + } +} +``` + +### Test Coverage + +- Aim for high test coverage of public APIs +- Test both success and error cases +- Test edge cases and boundary conditions +- Use property-based testing for complex logic (consider `proptest`) + +## Pull Request Process + +### Before Submitting + +1. **Ensure tests pass**: `cargo test --workspace` +2. **Run clippy**: `cargo clippy --workspace -- -D warnings` +3. **Format code**: `cargo fmt --all` +4. **Update documentation**: Ensure all public APIs are documented +5. **Add/update tests**: Include tests for new functionality +6. **Update CHANGELOG.md**: Add an entry describing your changes + +### PR Description Template + +```markdown +## Description + +Brief description of what this PR does. + +## Motivation + +Why is this change needed? What problem does it solve? + +## Changes + +- List of specific changes made +- Use bullet points + +## Testing + +How was this tested? What test cases were added? + +## Documentation + +- [ ] Added/updated documentation comments +- [ ] Updated README if needed +- [ ] Added examples if appropriate + +## Checklist + +- [ ] Tests pass locally +- [ ] Code is formatted (`cargo fmt`) +- [ ] No clippy warnings (`cargo clippy`) +- [ ] Documentation is complete +- [ ] CHANGELOG.md is updated +``` + +### Review Process + +1. Maintainers will review your PR +2. Address any requested changes +3. Once approved, your PR will be merged +4. Celebrate your contribution! 🎉 + +## Questions? + +If you have questions or need help: + +- Open an issue with the question +- Check existing issues and discussions +- Review the README and documentation + +Thank you for contributing to dfraw_parser! diff --git a/Cargo.lock b/Cargo.lock index 50f444dd..9807c5e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,24 +9,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" [[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array", -] - -[[package]] -name = "aegis" -version = "0.9.7" +name = "adler2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae1572243695de9c6c8d16c7889899abac907d14c148f1939d837122bbeca79" -dependencies = [ - "cc", - "softaes", -] +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aes" @@ -39,20 +25,6 @@ dependencies = [ "cpufeatures", ] -[[package]] -name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - [[package]] name = "aho-corasick" version = "1.1.4" @@ -72,13 +44,10 @@ dependencies = [ ] [[package]] -name = "arc-swap" -version = "1.8.0" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e" -dependencies = [ - "rustversion", -] +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" @@ -86,6 +55,34 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e84ce723ab67259cfeb9877c6a639ee9eb7a27b28123abd71db7f0d5d0cc9d86" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a442ece363113bd4bd4c8b18977a7798dd4d3c3383f34fb61936960e8f4ad8" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "2.10.0" @@ -93,13 +90,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] -name = "built" -version = "0.7.7" +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "chrono", - "git2", + "generic-array", ] [[package]] @@ -109,36 +105,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] -name = "bytemuck" -version = "1.24.0" +name = "bytes" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" -dependencies = [ - "bytemuck_derive", -] +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] -name = "bytemuck_derive" -version = "1.10.2" +name = "bzip2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" dependencies = [ - "proc-macro2", - "quote", - "syn", + "libbz2-rs-sys", ] -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "cc" -version = "1.2.51" +version = "1.2.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" +checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" dependencies = [ "find-msvc-tools", "jobserver", @@ -146,6 +131,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.4" @@ -153,20 +144,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] -name = "cfg_block" -version = "0.1.1" +name = "cfg_aliases" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18758054972164c3264f7c8386f5fc6da6114cb46b619fd365d4e3b2dc3ae487" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-link", ] @@ -181,44 +173,59 @@ dependencies = [ "inout", ] +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + [[package]] name = "colored" -version = "1.9.4" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f741c91823341bebf717d4c71bda820630ce065443b58bd1b7451af008355" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ - "is-terminal", "lazy_static", - "winapi", + "windows-sys 0.59.0", ] [[package]] -name = "concurrent-queue" -version = "2.5.0" +name = "combine" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ - "crossbeam-utils", + "bytes", + "memchr", ] [[package]] -name = "const_format" -version = "0.2.35" +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "core-foundation" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ - "const_format_proc_macros", + "core-foundation-sys", + "libc", ] [[package]] -name = "const_format_proc_macros" -version = "0.2.34" +name = "core-foundation" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", + "core-foundation-sys", + "libc", ] [[package]] @@ -237,29 +244,28 @@ dependencies = [ ] [[package]] -name = "crossbeam-epoch" -version = "0.9.18" +name = "crc" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" dependencies = [ - "crossbeam-utils", + "crc-catalog", ] [[package]] -name = "crossbeam-skiplist" -version = "0.1.3" +name = "crc-catalog" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df29de440c58ca2cc6e587ec3d22347551a32435fbde9d2bff64e78a9ffa151b" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] -name = "crossbeam-utils" -version = "0.8.21" +name = "crc32fast" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] [[package]] name = "crypto-common" @@ -268,17 +274,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", - "rand_core 0.6.4", "typenum", ] [[package]] -name = "ctr" -version = "0.9.2" +name = "deflate64" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +checksum = "26bf8fc351c5ed29b5c2f0cbbac1b209b74f60ecd62e675a998df72c49af5204" + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ - "cipher", + "powerfmt", ] [[package]] @@ -292,6 +303,7 @@ name = "dfraw_json_parser" version = "0.17.5" dependencies = [ "dfraw_parser", + "dfraw_parser_sqlite_lib", "itertools", "serde", "serde_json", @@ -319,6 +331,7 @@ dependencies = [ name = "dfraw_parser" version = "0.18.0" dependencies = [ + "dfraw_parser_proc_macros", "directories", "encoding_rs", "encoding_rs_io", @@ -330,15 +343,50 @@ dependencies = [ "serde", "slug", "specta", - "strum 0.27.2", - "strum_macros 0.27.2", - "thiserror 1.0.69", + "strum", + "strum_macros", + "thiserror 2.0.17", "tracing", "typetag", "walkdir", "winreg", ] +[[package]] +name = "dfraw_parser_proc_macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dfraw_parser_sqlite_lib" +version = "0.1.0" +dependencies = [ + "chrono", + "dfraw_parser", + "rusqlite", + "serde", + "serde_json", + "specta", + "test_util", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + [[package]] name = "directories" version = "6.0.0" @@ -371,6 +419,12 @@ dependencies = [ "syn", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "either" version = "1.15.0" @@ -395,6 +449,12 @@ dependencies = [ "encoding_rs", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "erased-serde" version = "0.4.9" @@ -406,22 +466,18 @@ dependencies = [ "typeid", ] -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - [[package]] name = "fallible-iterator" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fastrand" version = "2.3.0" @@ -430,9 +486,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fern" -version = "0.6.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee" +checksum = "4316185f709b23713e41e3195f90edef7fb00c3ed4adc79769cf09cc762a3b29" dependencies = [ "colored", "log", @@ -440,9 +496,32 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.6" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" + +[[package]] +name = "flate2" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "form_urlencoded" @@ -453,6 +532,62 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -465,13 +600,15 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -481,32 +618,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasip2", + "wasm-bindgen", ] [[package]] -name = "ghash" -version = "0.5.1" +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ - "opaque-debug", - "polyval", + "foldhash", ] [[package]] -name = "git2" -version = "0.20.3" +name = "hashlink" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2b37e2f62729cdada11f0e6b3b6fe383c69c29fc619e391223e12856af308c" +checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230" dependencies = [ - "bitflags", - "libc", - "libgit2-sys", - "log", - "url", + "hashbrown", ] [[package]] @@ -516,16 +669,116 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "hermit-abi" -version = "0.5.2" +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] [[package]] -name = "hex" -version = "0.4.3" +name = "hyper-util" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] [[package]] name = "iana-time-zone" @@ -654,21 +907,22 @@ dependencies = [ ] [[package]] -name = "inout" -version = "0.1.4" +name = "indexmap" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ - "generic-array", + "equivalent", + "hashbrown", ] [[package]] -name = "intrusive-collections" -version = "0.9.7" +name = "inout" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ - "memoffset", + "generic-array", ] [[package]] @@ -681,32 +935,26 @@ dependencies = [ ] [[package]] -name = "io-uring" -version = "0.7.11" +name = "ipnet" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd7bddefd0a8833b88a4b68f90dae22c7450d11b354198baee3874fd811b344" -dependencies = [ - "bitflags", - "cfg-if", - "libc", -] +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] -name = "is-terminal" -version = "0.4.17" +name = "iri-string" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.61.2", + "memchr", + "serde", ] [[package]] name = "itertools" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] @@ -718,20 +966,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] -name = "jobserver" -version = "0.1.34" +name = "jni" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ - "getrandom 0.3.4", - "libc", + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", ] [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -739,9 +1009,9 @@ dependencies = [ [[package]] name = "lazy-regex" -version = "3.4.2" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "191898e17ddee19e60bccb3945aa02339e81edd4a8c50e21fd4d48cdecda7b29" +checksum = "c5c13b6857ade4c8ee05c3c3dc97d2ab5415d691213825b90d3211c425c1f907" dependencies = [ "lazy-regex-proc_macros", "once_cell", @@ -750,9 +1020,9 @@ dependencies = [ [[package]] name = "lazy-regex-proc_macros" -version = "3.4.2" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c35dc8b0da83d1a9507e12122c80dea71a9c7c613014347392483a83ea593e04" +checksum = "32a95c68db5d41694cea563c86a4ba4dc02141c16ef64814108cb23def4d5438" dependencies = [ "proc-macro2", "quote", @@ -773,48 +1043,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa0e2a1fcbe2f6be6c42e342259976206b383122fc152e872795338b5a3f3a7" [[package]] -name = "libc" -version = "0.2.178" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" - -[[package]] -name = "libgit2-sys" -version = "0.18.3+1.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487" -dependencies = [ - "cc", - "libc", - "libz-sys", - "pkg-config", -] - -[[package]] -name = "libloading" -version = "0.8.9" +name = "libbz2-rs-sys" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" -dependencies = [ - "cfg-if", - "windows-link", -] +checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" [[package]] -name = "libm" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" - -[[package]] -name = "libmimalloc-sys" -version = "0.1.44" +name = "libc" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870" -dependencies = [ - "cc", - "libc", -] +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libredox" @@ -827,38 +1065,22 @@ dependencies = [ ] [[package]] -name = "libz-sys" -version = "1.1.23" +name = "libsqlite3-sys" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" +checksum = "95b4103cffefa72eb8428cb6b47d6627161e51c2739fc5e3b734584157bc642a" dependencies = [ "cc", - "libc", "pkg-config", "vcpkg", ] -[[package]] -name = "linux-raw-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" - [[package]] name = "litemap" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - [[package]] name = "log" version = "0.4.29" @@ -866,49 +1088,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] -name = "memchr" -version = "2.7.6" +name = "lru-slab" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] -name = "memoffset" -version = "0.9.1" +name = "lzma-rust2" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +checksum = "1670343e58806300d87950e3401e820b519b9384281bbabfb15e3636689ffd69" dependencies = [ - "autocfg", + "crc", + "sha2", ] [[package]] -name = "miette" -version = "7.6.0" +name = "memchr" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" -dependencies = [ - "cfg-if", - "miette-derive", - "unicode-width", -] +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] -name = "miette-derive" -version = "7.6.0" +name = "miniz_oxide" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ - "proc-macro2", - "quote", - "syn", + "adler2", + "simd-adler32", ] [[package]] -name = "mimalloc" -version = "0.1.48" +name = "mio" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ - "libmimalloc-sys", + "libc", + "wasi", + "windows-sys 0.61.2", ] [[package]] @@ -920,6 +1145,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-traits" version = "0.2.19" @@ -936,10 +1167,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] -name = "opaque-debug" -version = "0.3.1" +name = "openssl-probe" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" [[package]] name = "option-ext" @@ -948,43 +1179,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] -name = "pack1" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6e7cd9bd638dc2c831519a0caa1c006cab771a92b1303403a8322773c5b72d6" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "parking_lot" -version = "0.12.5" +name = "pbkdf2" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ - "lock_api", - "parking_lot_core", + "digest", + "hmac", ] -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "percent-encoding" version = "2.3.2" @@ -993,29 +1196,30 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "phf" -version = "0.11.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ "phf_macros", "phf_shared", + "serde", ] [[package]] name = "phf_generator" -version = "0.11.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ + "fastrand", "phf_shared", - "rand 0.8.5", ] [[package]] name = "phf_macros" -version = "0.11.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" dependencies = [ "phf_generator", "phf_shared", @@ -1026,9 +1230,9 @@ dependencies = [ [[package]] name = "phf_shared" -version = "0.11.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" dependencies = [ "siphasher", ] @@ -1039,6 +1243,12 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.32" @@ -1046,92 +1256,114 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] -name = "polling" -version = "3.11.0" +name = "potential_utf" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix", - "windows-sys 0.61.2", + "zerovec", ] [[package]] -name = "pollster" -version = "0.4.0" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" -dependencies = [ - "pollster-macro", -] +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppmd-rust" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d558c559f0450f16f2a27a1f017ef38468c1090c9ce63c8e51366232d53717b4" [[package]] -name = "pollster-macro" -version = "0.4.0" +name = "ppv-lite86" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac5da421106a50887c5b51d20806867db377fbb86bacf478ee0500a912e0c113" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "proc-macro2", - "quote", - "syn", + "zerocopy", ] [[package]] -name = "polyval" -version = "0.6.2" +name = "proc-macro2" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ - "cfg-if", - "cpufeatures", - "opaque-debug", - "universal-hash", + "unicode-ident", ] [[package]] -name = "potential_utf" -version = "0.1.4" +name = "quick-xml" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" dependencies = [ - "zerovec", + "memchr", ] [[package]] -name = "ppv-lite86" -version = "0.2.21" +name = "quinn" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ - "zerocopy", + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.17", + "tokio", + "tracing", + "web-time", ] [[package]] -name = "proc-macro2" -version = "1.0.104" +name = "quinn-proto" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ - "unicode-ident", + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.17", + "tinyvec", + "tracing", + "web-time", ] [[package]] -name = "quick-xml" -version = "0.35.0" +name = "quinn-udp" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86e446ed58cef1bbfe847bc2fda0e2e4ea9f0e57b90c507d4781292590d72a4e" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" dependencies = [ - "memchr", + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.42" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -1142,15 +1374,6 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "rand_core 0.6.4", -] - [[package]] name = "rand" version = "0.9.2" @@ -1158,7 +1381,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", - "rand_core 0.9.3", + "rand_core", ] [[package]] @@ -1168,43 +1391,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.16", + "rand_core", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - [[package]] name = "redox_users" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "libredox", "thiserror 2.0.17", ] @@ -1239,13 +1444,72 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] -name = "roaring" -version = "0.11.3" +name = "reqwest" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rusqlite" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ba9ce64a8f45d7fc86358410bb1a82e8c987504c0d4900e9141d69a9f26c885" +checksum = "f1c93dd1c9683b438c392c492109cb702b8090b2bfc8fed6f6e4eb4523f17af3" dependencies = [ - "bytemuck", - "byteorder", + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", + "sqlite-wasm-rs", ] [[package]] @@ -1255,29 +1519,85 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] -name = "rustix" -version = "1.1.3" +name = "rustls" +version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", "windows-sys 0.61.2", ] [[package]] -name = "rustversion" -version = "1.0.22" +name = "rustls-platform-verifier-android" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] -name = "ryu" +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "same-file" @@ -1289,10 +1609,36 @@ dependencies = [ ] [[package]] -name = "scopeguard" -version = "1.2.0" +name = "schannel" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] [[package]] name = "serde" @@ -1326,9 +1672,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.148" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", @@ -1338,10 +1684,26 @@ dependencies = [ ] [[package]] -name = "sha1_smol" -version = "1.0.1" +name = "sha1" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] [[package]] name = "sharded-slab" @@ -1359,13 +1721,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] -name = "simsimd" -version = "6.5.12" +name = "simd-adler32" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dcd49d13a0950ae06cd46597cfad99a9d23797e4e9f9e2b29decdc016b3f7" -dependencies = [ - "cc", -] +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "siphasher" @@ -1373,6 +1732,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + [[package]] name = "slug" version = "0.1.6" @@ -1390,10 +1755,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] -name = "softaes" -version = "0.1.3" +name = "socket2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef461faaeb36c340b6c887167a9054a034f6acfc50a014ead26a02b4356b3de" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] [[package]] name = "specta" @@ -1401,6 +1770,7 @@ version = "2.0.0-rc.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab7f01e9310a820edd31c80fde3cae445295adde21a3f9416517d7d65015b971" dependencies = [ + "serde_json", "specta-macros", "thiserror 1.0.69", ] @@ -1439,16 +1809,16 @@ dependencies = [ ] [[package]] -name = "sqlite_lib" -version = "0.1.0" +name = "sqlite-wasm-rs" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e98301bf8b0540c7de45ecd760539b9c62f5772aed172f08efba597c11cd5d" dependencies = [ - "const_format", - "dfraw_parser", - "pollster", - "strum 0.27.2", - "tracing", - "tracing-subscriber", - "turso", + "cc", + "hashbrown", + "js-sys", + "thiserror 2.0.17", + "wasm-bindgen", ] [[package]] @@ -1457,34 +1827,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros 0.26.4", -] - [[package]] name = "strum" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - [[package]] name = "strum_macros" version = "0.27.2" @@ -1505,15 +1853,24 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.111" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -1526,16 +1883,35 @@ dependencies = [ ] [[package]] -name = "tempfile" -version = "3.24.0" +name = "system-configuration" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "fastrand", - "getrandom 0.3.4", - "once_cell", - "rustix", - "windows-sys 0.61.2", + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "test_util" +version = "0.1.0" +dependencies = [ + "chrono", + "dfraw_parser", + "dfraw_parser_sqlite_lib", + "reqwest", + "zip", ] [[package]] @@ -1587,6 +1963,25 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "time" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" + [[package]] name = "tinystr" version = "0.8.2" @@ -1598,140 +1993,118 @@ dependencies = [ ] [[package]] -name = "tracing" -version = "0.1.44" +name = "tinyvec" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", + "tinyvec_macros", ] [[package]] -name = "tracing-attributes" -version = "0.1.31" +name = "tinyvec_macros" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] -name = "tracing-core" -version = "0.1.36" +name = "tokio" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ - "once_cell", - "valuable", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", ] [[package]] -name = "tracing-log" -version = "0.2.0" +name = "tokio-rustls" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "log", - "once_cell", - "tracing-core", + "rustls", + "tokio", ] [[package]] -name = "tracing-subscriber" -version = "0.3.22" +name = "tokio-util" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ - "nu-ansi-term", - "sharded-slab", - "smallvec", - "thread_local", - "tracing-core", - "tracing-log", + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", ] [[package]] -name = "turso" -version = "0.3.2" +name = "tower" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c670345a51c5df2ebb0a582c9af67dea98192c8fd82ca2c50aca34b342bf0c10" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ - "mimalloc", - "thiserror 2.0.17", - "tracing", - "tracing-subscriber", - "turso_core", + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", ] [[package]] -name = "turso_core" -version = "0.3.2" +name = "tower-http" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd5a3044961c822886d08cbc94f5519524213b877f65fb66ab70133ed3c0a36d" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "aegis", - "aes", - "aes-gcm", - "arc-swap", "bitflags", - "built", - "bytemuck", - "cfg_block", - "chrono", - "crossbeam-skiplist", - "fallible-iterator", - "hex", - "intrusive-collections", - "io-uring", - "libc", - "libloading", - "libm", - "miette", - "pack1", - "parking_lot", - "paste", - "polling", - "rand 0.9.2", - "regex", - "regex-syntax", - "roaring", - "rustc-hash", - "rustix", - "ryu", - "simsimd", - "strum 0.26.3", - "strum_macros 0.26.4", - "tempfile", - "thiserror 2.0.17", - "tracing", - "turso_ext", - "turso_macros", - "turso_parser", - "twox-hash", - "uncased", - "uuid", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", ] [[package]] -name = "turso_ext" -version = "0.3.2" +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c744b2d657542911d6b1708f8aa2f047601cc0d452af8cd2d7f8fb1c05d40730" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ - "chrono", - "getrandom 0.3.4", - "turso_macros", + "pin-project-lite", + "tracing-attributes", + "tracing-core", ] [[package]] -name = "turso_macros" -version = "0.3.2" +name = "tracing-attributes" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4631f270381e6153833ab491b42fca3a1dbaad0f657a12ea1d676dec8f71e7b1" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -1739,28 +2112,46 @@ dependencies = [ ] [[package]] -name = "turso_parser" -version = "0.3.2" +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b64ae3d49b989ed156abe143ae0244d0f239f67fc42da72806369a567f295750" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ - "bitflags", - "miette", - "strum 0.26.3", - "strum_macros 0.26.4", - "thiserror 2.0.17", - "turso_macros", + "log", + "once_cell", + "tracing-core", ] [[package]] -name = "twox-hash" -version = "2.1.2" +name = "tracing-subscriber" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ - "rand 0.9.2", + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typeid" version = "1.0.3" @@ -1797,15 +2188,6 @@ dependencies = [ "syn", ] -[[package]] -name = "uncased" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" -dependencies = [ - "version_check", -] - [[package]] name = "unicode-ident" version = "1.0.22" @@ -1813,32 +2195,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "universal-hash" -version = "0.5.1" +name = "untrusted" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle", -] +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", @@ -1852,18 +2218,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" -[[package]] -name = "uuid" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" -dependencies = [ - "getrandom 0.3.4", - "js-sys", - "sha1_smol", - "wasm-bindgen", -] - [[package]] name = "valuable" version = "0.1.1" @@ -1892,6 +2246,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -1900,18 +2263,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", @@ -1920,11 +2283,25 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1932,9 +2309,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", @@ -1945,28 +2322,41 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] [[package]] -name = "winapi" -version = "0.3.9" +name = "web-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "webpki-root-certs" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" +dependencies = [ + "rustls-pki-types", +] [[package]] name = "winapi-util" @@ -1977,12 +2367,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-core" version = "0.62.2" @@ -2024,6 +2408,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.4.1" @@ -2044,11 +2439,38 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets", + "windows-targets 0.53.5", ] [[package]] @@ -2062,76 +2484,205 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.5" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.48.5" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.48.5" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.48.5" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winreg" -version = "0.52.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "writeable" @@ -2164,18 +2715,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", @@ -2203,6 +2754,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerotrie" version = "0.2.3" @@ -2236,8 +2807,81 @@ dependencies = [ "syn", ] +[[package]] +name = "zip" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9013f1222db8a6d680f13a7ccdc60a781199cd09c2fa4eff58e728bb181757fc" +dependencies = [ + "aes", + "bzip2", + "constant_time_eq", + "crc32fast", + "deflate64", + "flate2", + "generic-array", + "getrandom 0.3.4", + "hmac", + "indexmap", + "lzma-rust2", + "memchr", + "pbkdf2", + "ppmd-rust", + "sha1", + "time", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zlib-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" + [[package]] name = "zmij" -version = "1.0.2" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f4a4e8e9dc5c62d159f04fcdbe07f4c3fb710415aab4754bf11505501e3251d" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 12ea03ac..54f05db2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,21 +1,51 @@ [workspace] -members = ["lib", "cli", "jsonlib", "sqlite_lib"] +members = ["lib", "cli", "jsonlib", "sqlite_lib", "test_util", "proc_macros"] resolver = "3" # Some very strict lints. +# These lints can be used from the cli as well: +# +# cargo clippy -- -D clippy::pedantic -D clippy::nursery -D clippy::unwrap_used -D clippy::enum_glob_use -D unreachable_patterns -D unreachable_code -D missing_docs -D unsafe_code [workspace.lints.rust] unsafe_code = "forbid" missing_docs = "warn" unreachable_code = "warn" unreachable_patterns = "warn" [workspace.lints.clippy] -module_name_repetition = "allow" +module_name_repetitions = "allow" enum_glob_use = { level = "deny", priority = -1 } pedantic= { level = "deny", priority = -2 } nursery = { level = "deny", priority = 1 } unwrap_used = { level = "deny", priority = 2 } -# These lints can be used from the cli as well: -# -# cargo clippy -- -D clippy::pedantic -D clippy::nursery -D clippy::unwrap_used -D clippy::enum_glob_use -D unreachable_patterns -D unreachable_code -D missing_docs -D unsafe_code +[workspace.dependencies] +chrono = "0.4" +dfraw_json_parser = { version = "0", path = "jsonlib" } +dfraw_parser = { version = "0", path = "lib" } +dfraw_parser_sqlite_lib = { version = "0", path = "sqlite_lib" } +dfraw_parser_proc_macros = { version = "0", path = "proc_macros" } +directories = "6.0.0" +encoding_rs = "0.8.34" +encoding_rs_io = "0.1" +fern = "0.7.1" +itertools = "0.14.0" +lazy-regex = "3.1.0" +lexopt = "0.3" +once_cell = "1.19.0" +phf = "0.13.1" +quick-xml = "0.38.4" +rusqlite = "0.38.0" +serde = "1.0.228" +serde_json = "1.0.148" +slug = "0.1.5" +specta = "2.0.0-rc.22" +specta-typescript = "0.0.9" +strum = "0.27" +strum_macros = "0.27" +test_util = {version = "0", path = "test_util"} +thiserror = "2" +tracing = "0.1.43" +tracing-subscriber = "0.3.18" +typetag = "0.2" +walkdir = "2.5.0" diff --git a/DOCUMENTATION_REFERENCE.md b/DOCUMENTATION_REFERENCE.md new file mode 100644 index 00000000..a6c03b91 --- /dev/null +++ b/DOCUMENTATION_REFERENCE.md @@ -0,0 +1,243 @@ +# Documentation Quick Reference + +A quick reference guide for documenting dfraw_parser. + +## Common Patterns + +### Simple Getter + +```rust +/// Returns the identifier of this object. +/// +/// The identifier is the unique name used in raw files. +#[must_use] +pub fn get_identifier(&self) -> &str { + &self.identifier +} +``` + +### Getter Returning Option + +```rust +/// Returns the description of this object, if available. +/// +/// The description is the text shown in-game when examining this object. +#[must_use] +pub fn get_description(&self) -> Option<&str> { + self.description.as_deref() +} +``` + +### Method with Parameters + +```rust +/// Adds a tag to this object. +/// +/// * `tag` - The tag to add +/// +/// # Examples +/// +/// ``` +/// # use dfraw_parser::Creature; +/// let mut creature = Creature::new("DOG"); +/// creature.add_tag(tag); +/// ``` +pub fn add_tag(&mut self, tag: Tag) { + self.tags.push(tag); +} +``` + +### Function Returning Result + +```rust +/// Parses a raw file and returns the parsed objects. +/// +/// * `path` - Path to the raw file +/// +/// Returns a `ParseResult` containing all parsed objects. +/// +/// # Errors +/// +/// - [`ParserError::IOError`]: The file cannot be read. +/// - [`ParserError::InvalidRawFile`]: The file format is invalid. +/// +/// # Examples +/// +/// ```no_run +/// use dfraw_parser::parse_file; +/// use std::path::Path; +/// +/// let result = parse_file(Path::new("creature.txt"))?; +/// # Ok::<(), dfraw_parser::ParserError>(()) +/// ``` +pub fn parse_file(path: &Path) -> Result { + // implementation +} +``` + +### Associated Function (Constructor) + +```rust +/// Creates a new creature with the given identifier. +/// +/// * `identifier` - The unique identifier for this creature +/// +/// # Examples +/// +/// ``` +/// use dfraw_parser::Creature; +/// +/// let creature = Creature::new("DOG"); +/// assert_eq!(creature.get_identifier(), "DOG"); +/// ``` +#[must_use] +pub fn new(identifier: impl Into) -> Self { + Self { + identifier: identifier.into(), + ..Default::default() + } +} +``` + +### Trait Definition + +```rust +/// Provides search functionality for parsed objects. +/// +/// Types implementing this trait can be searched by generating +/// search strings from their properties. +pub trait Searchable { + /// Returns a vector of searchable strings. + /// + /// These strings typically include names, descriptions, and other + /// searchable attributes of the object. + fn get_search_vec(&self) -> Vec; +} +``` + +### Enum + +```rust +/// Represents the state of a material. +/// +/// This is used in material defintion raws. +#[derive(Debug, Clone, Copy)] +pub enum MaterialState { + /// Solid state (e.g., ice, stone) + Solid, + /// Liquid state (e.g., water, magma) + Liquid, + /// Gas state (e.g., steam, miasma) + Gas, +} +``` + +### Module + +```rust +//! Utilities for parsing and manipulating creature definitions. +//! +//! This module provides functions for working with creature raw files, +//! including parsing, validation, and transformation utilities. +//! +//! # Examples +//! +//! ``` +//! use dfraw_parser::creatures; +//! +//! // Example usage +//! ``` +``` + +### Const + +```rust +/// Default maximum age for creatures in ticks. +/// +/// This value is used when no specific max age is defined. +pub const DEFAULT_MAX_AGE: u32 = 1_000_000; +``` + +### Struct (representing raws) + +It's important to be clear about which raw object it maps to and what is required +for it to correctly parse an object. + +```rust +/// Represents a single creature definition from a raw file. +/// +/// A creature is defined by the `[CREATURE:ID]` token and encompasses +/// all properties until the next top-level object. See the wiki for more +/// information on what's in a creature raw file: +/// [Creature](https://dwarffortresswiki.org/index.php/Creature_token) +pub struct Creature { ... } +``` + +## Section Keywords + +Use these sections in your documentation: + +1. One line, concise description +2. Parameter descriptions (if parameters) +3. Returns +4. Extra details/description or links to supporting documentation (if relevant) +5. `# Errors` - Error conditions for Result types +6. `# Panics` - When the function might panic (if it panics) +7. `# Safety` - Safety requirements for unsafe functions +8. `# Examples` - Usage examples with descriptions of why it would be used + +## Code Example Attributes + +- `no_run` - Compiles but doesn't execute (needs external resources) +- `ignore` - Skip in documentation tests +- `should_panic` - Expected to panic +- `compile_fail` - Should fail to compile + +```rust +/// ```no_run +/// // This needs actual game files +/// let result = parse_game_files(); +/// ``` +``` + +## Quick Tips + +1. **First line is special** - Should be a complete sentence, ends with period +2. **Keep it concise** - Aim for clarity over verbosity +3. **Examples are valuable** - Show typical usage +4. **Document errors** - List what can go wrong +5. **Link to related items** - Use [backticks] for type references +6. **Test your examples** - Run `cargo test --doc` + +## Linking to Types + +Use square brackets to link to other types: + +```rust +/// Returns a [`Creature`] object. +/// +/// See also [`parse_file`] for parsing from files. +``` + +## Common Phrases for Dwarf Fortress Context + +- "The identifier is the unique name used in raw files" +- "Measured in game ticks" +- "Specified in the raw file using [TAG:VALUE]" +- "This value affects how the game processes this object" +- "Returns `None` if not specified in the raw definition" + +## Before Committing + +Run these commands: + +```bash +# Check for missing docs +cargo clippy --workspace -- -D missing_docs + +# Test documentation examples +cargo test --doc + +# View generated docs +cargo doc --no-deps --open +``` diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 70edd01a..df3ccbdf 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "dfraw_json_parser-cli" -version = "1.2.1" -edition = "2021" +version = "1.3.0" +edition = "2024" readme = "README.md" authors = ["Nicholas Westerhausen "] description = "CLI for parsing Dwarf Fortress raw files into JSON" @@ -14,16 +14,10 @@ categories = ["parsing"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -chrono = "0.4" -lexopt = "0.3" -tracing = "0.1.40" -tracing-subscriber = "0.3.18" -serde_json = "1.0" - -[dependencies.fern] -version = "0.6.2" -features = ["colored"] - -[dependencies.dfraw_json_parser] -path = "../jsonlib" -version = "0.17" +chrono = { workspace = true } +lexopt = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +serde_json = { workspace = true } +fern= { workspace = true, features = ["colored"] } +dfraw_json_parser= { workspace = true } diff --git a/cli/src/main.rs b/cli/src/main.rs index 709233a8..80727d97 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -176,7 +176,7 @@ fn parse_args() -> Result { args.locations.push(RawModuleLocation::Vanilla); } Long("mods") => { - args.locations.push(RawModuleLocation::Mods); + args.locations.push(RawModuleLocation::WorkshopMods); } Long("installed") => { args.locations.push(RawModuleLocation::InstalledMods); @@ -314,7 +314,9 @@ fn write_output_file>( tracing::info!("Opened {} for writing", output_path.as_ref().display()); if skip_info_files && skip_raws { - tracing::info!("Specified --skip-info-files and --skip-raws, so not writing anything to the output file"); + tracing::info!( + "Specified --skip-info-files and --skip-raws, so not writing anything to the output file" + ); return Ok(()); } diff --git a/jsonlib/Cargo.toml b/jsonlib/Cargo.toml index 0f33f43c..ed556fa1 100644 --- a/jsonlib/Cargo.toml +++ b/jsonlib/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "dfraw_json_parser" -version = "0.17.5" -edition = "2021" +version = "0.18.0" +edition = "2024" readme = "README.md" authors = ["Nicholas Westerhausen "] description = "Library which parses Dwarf Fortress raw files into JSON" @@ -19,22 +19,13 @@ crate-type = ["rlib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -itertools = "0.13.0" -serde_json = "1.0" -tracing = "0.1.40" -typetag = "0.2" -walkdir = "2" -specta-typescript = "0.0.9" - -[dependencies.specta] -version = "2.0.0-rc.22" -features = ["derive"] - -[dependencies.serde] -version = "1.0" -default-features = true -features = ["derive"] - -[dependencies.dfraw_parser] -path = "../lib" -version = "0" +itertools = { workspace = true } +serde_json = { workspace = true } +tracing = { workspace = true } +typetag = { workspace = true } +walkdir = { workspace = true } +specta-typescript = { workspace = true } +specta= { workspace = true, features = ["derive", "serde_json"] } +serde= { workspace = true, features = ["derive"] } +dfraw_parser= { workspace = true } +dfraw_parser_sqlite_lib= {workspace = true} diff --git a/jsonlib/bindings/DFRawParser.d.ts b/jsonlib/bindings/DFRawParser.d.ts index fe1b3d42..6e0010b1 100644 --- a/jsonlib/bindings/DFRawParser.d.ts +++ b/jsonlib/bindings/DFRawParser.d.ts @@ -3,11147 +3,10979 @@ /** * An enum representing a biome. */ -export type BiomeTag = - /** - * A mountain biome. - */ - | "Mountain" - /** - * A mountainous biome - */ - | "Mountains" - /** - * A glacier biome. - */ - | "Glacier" - /** - * A tundra biome. - */ - | "Tundra" - /** - * A temperate freshwater swamp - */ - | "SwampTemperateFreshwater" - /** - * A temperate saltwater swamp - */ - | "SwampTemperateSaltwater" - /** - * A temperate freshwater marsh - */ - | "MarshTemperateFreshwater" - /** - * A temperate saltwater marsh - */ - | "MarshTemperateSaltwater" - /** - * A tropical freshwater swamp - */ - | "SwampTropicalFreshwater" - /** - * A tropical saltwater swamp - */ - | "SwampTropicalSaltwater" - /** - * A mangrove swamp - */ - | "SwampMangrove" - /** - * A tropical freshwater marsh - */ - | "MarshTropicalFreshwater" - /** - * A tropical saltwater marsh - */ - | "MarshTropicalSaltwater" - /** - * A taiga forest - */ - | "ForestTaiga" - /** - * A taiga - */ - | "Taiga" - /** - * A temperate conifer forest - */ - | "ForestTemperateConifer" - /** - * A temperate broadleaf forest - */ - | "ForestTemperateBroadleaf" - /** - * A tropical conifer forest - */ - | "ForestTropicalConifer" - /** - * A tropical broadleaf forest - */ - | "ForestTropicalDryBroadleaf" - /** - * A tropical moist broadleaf forest - */ - | "ForestTropicalMoistBroadleaf" - /** - * A temperate grassland - */ - | "GrasslandTemperate" - /** - * A temperate savanna - */ - | "SavannaTemperate" - /** - * A temperate shrubland - */ - | "ShrublandTemperate" - /** - * A tropical grassland - */ - | "GrasslandTropical" - /** - * A tropical savanna - */ - | "SavannaTropical" - /** - * A tropical shrubland - */ - | "ShrublandTropical" - /** - * A badland desert - */ - | "DesertBadland" - /** - * A rocky desert - */ - | "DesertRock" - /** - * A sandy desert - */ - | "DesertSand" - /** - * A tropical ocean - */ - | "OceanTropical" - /** - * A temperate ocean - */ - | "OceanTemperate" - /** - * An arctic ocean - */ - | "OceanArctic" - /** - * A temperate freshwater pool - */ - | "PoolTemperateFreshwater" - /** - * A temperate brackishwater pool - */ - | "PoolTemperateBrackishwater" - /** - * A temperate saltwater pool - */ - | "PoolTemperateSaltwater" - /** - * A tropical freshwater pool - */ - | "PoolTropicalFreshwater" - /** - * A tropical brackishwater pool - */ - | "PoolTropicalBrackishwater" - /** - * A tropical saltwater pool - */ - | "PoolTropicalSaltwater" - /** - * A temperate freshwater lake - */ - | "LakeTemperateFreshwater" - /** - * A temperate brackishwater lake - */ - | "LakeTemperateBrackishwater" - /** - * A temperate saltwater lake - */ - | "LakeTemperateSaltwater" - /** - * A tropical freshwater lake - */ - | "LakeTropicalFreshwater" - /** - * A tropical brackishwater lake - */ - | "LakeTropicalBrackishwater" - /** - * A tropical saltwater lake - */ - | "LakeTropicalSaltwater" - /** - * A temperate freshwater river - */ - | "RiverTemperateFreshwater" - /** - * A temperate brackishwater river - */ - | "RiverTemperateBrackishwater" - /** - * A temperate saltwater river - */ - | "RiverTemperateSaltwater" - /** - * A tropical freshwater river - */ - | "RiverTropicalFreshwater" - /** - * A tropical brackishwater river - */ - | "RiverTropicalBrackishwater" - /** - * A tropical saltwater river - */ - | "RiverTropicalSaltwater" - /** - * A subterranean freshwater source - */ - | "SubterraneanWater" - /** - * A subterranean chasm - */ - | "SubterraneanChasm" - /** - * A subterranean magma pool - */ - | "SubterraneanLava" - /** - * All the main biomes - */ - | "AllMain" - /** - * Any land biome - */ - | "AnyLand" - /** - * Any ocean biome - */ - | "AnyOcean" - /** - * Any lake biome - */ - | "AnyLake" - /** - * Any temperate lake biome - */ - | "AnyTemperateLake" - /** - * Any tropical lake biome - */ - | "AnyTropicalLake" - /** - * Any river - */ - | "AnyRiver" - /** - * Any temperate river - */ - | "AnyTemperateRiver" - /** - * Any tropical river - */ - | "AnyTropicalRiver" - /** - * Any pool - */ - | "AnyPool" - /** - * Any non-freezing biome - */ - | "NotFreezing" - /** - * Any temperate biome - */ - | "AnyTemperate" - /** - * Any tropical biome - */ - | "AnyTropical" - /** - * Any forest biome - */ - | "AnyForest" - /** - * Any shrubland biome - */ - | "AnyShrubland" - /** - * Any grassland biome - */ - | "AnyGrassland" - /** - * Any savanna biome - */ - | "AnySavanna" - /** - * Any temperate forest biome - */ - | "AnyTemperateForest" - /** - * Any tropical forest biome - */ - | "AnyTropicalForest" - /** - * Any temperate broadleaf forest biome - */ - | "AnyTemperateBroadleaf" - /** - * Any tropical broadleaf forest biome - */ - | "AnyTropicalBroadleaf" - /** - * Any wetland biome - */ - | "AnyWetland" - /** - * Any temperate wetland biome - */ - | "AnyTemperateWetland" - /** - * Any tropical wetland biome - */ - | "AnyTropicalWetland" - /** - * Any tropical marsh biome - */ - | "AnyTropicalMarsh" - /** - * Any temperate marsh biome - */ - | "AnyTemperateMarsh" - /** - * Any tropical swamp biome - */ - | "AnyTropicalSwamp" - /** - * Any temperate swamp biome - */ - | "AnyTemperateSwamp" - /** - * Any desert biome - */ - | "AnyDesert" - /** - * An unknown token - */ - | "Unknown"; +export type BiomeTag = +/** + * A mountain biome. + */ +"Mountain" | +/** + * A mountainous biome + */ +"Mountains" | +/** + * A glacier biome. + */ +"Glacier" | +/** + * A tundra biome. + */ +"Tundra" | +/** + * A temperate freshwater swamp + */ +"SwampTemperateFreshwater" | +/** + * A temperate saltwater swamp + */ +"SwampTemperateSaltwater" | +/** + * A temperate freshwater marsh + */ +"MarshTemperateFreshwater" | +/** + * A temperate saltwater marsh + */ +"MarshTemperateSaltwater" | +/** + * A tropical freshwater swamp + */ +"SwampTropicalFreshwater" | +/** + * A tropical saltwater swamp + */ +"SwampTropicalSaltwater" | +/** + * A mangrove swamp + */ +"SwampMangrove" | +/** + * A tropical freshwater marsh + */ +"MarshTropicalFreshwater" | +/** + * A tropical saltwater marsh + */ +"MarshTropicalSaltwater" | +/** + * A taiga forest + */ +"ForestTaiga" | +/** + * A taiga + */ +"Taiga" | +/** + * A temperate conifer forest + */ +"ForestTemperateConifer" | +/** + * A temperate broadleaf forest + */ +"ForestTemperateBroadleaf" | +/** + * A tropical conifer forest + */ +"ForestTropicalConifer" | +/** + * A tropical broadleaf forest + */ +"ForestTropicalDryBroadleaf" | +/** + * A tropical moist broadleaf forest + */ +"ForestTropicalMoistBroadleaf" | +/** + * A temperate grassland + */ +"GrasslandTemperate" | +/** + * A temperate savanna + */ +"SavannaTemperate" | +/** + * A temperate shrubland + */ +"ShrublandTemperate" | +/** + * A tropical grassland + */ +"GrasslandTropical" | +/** + * A tropical savanna + */ +"SavannaTropical" | +/** + * A tropical shrubland + */ +"ShrublandTropical" | +/** + * A badland desert + */ +"DesertBadland" | +/** + * A rocky desert + */ +"DesertRock" | +/** + * A sandy desert + */ +"DesertSand" | +/** + * A tropical ocean + */ +"OceanTropical" | +/** + * A temperate ocean + */ +"OceanTemperate" | +/** + * An arctic ocean + */ +"OceanArctic" | +/** + * A temperate freshwater pool + */ +"PoolTemperateFreshwater" | +/** + * A temperate brackishwater pool + */ +"PoolTemperateBrackishwater" | +/** + * A temperate saltwater pool + */ +"PoolTemperateSaltwater" | +/** + * A tropical freshwater pool + */ +"PoolTropicalFreshwater" | +/** + * A tropical brackishwater pool + */ +"PoolTropicalBrackishwater" | +/** + * A tropical saltwater pool + */ +"PoolTropicalSaltwater" | +/** + * A temperate freshwater lake + */ +"LakeTemperateFreshwater" | +/** + * A temperate brackishwater lake + */ +"LakeTemperateBrackishwater" | +/** + * A temperate saltwater lake + */ +"LakeTemperateSaltwater" | +/** + * A tropical freshwater lake + */ +"LakeTropicalFreshwater" | +/** + * A tropical brackishwater lake + */ +"LakeTropicalBrackishwater" | +/** + * A tropical saltwater lake + */ +"LakeTropicalSaltwater" | +/** + * A temperate freshwater river + */ +"RiverTemperateFreshwater" | +/** + * A temperate brackishwater river + */ +"RiverTemperateBrackishwater" | +/** + * A temperate saltwater river + */ +"RiverTemperateSaltwater" | +/** + * A tropical freshwater river + */ +"RiverTropicalFreshwater" | +/** + * A tropical brackishwater river + */ +"RiverTropicalBrackishwater" | +/** + * A tropical saltwater river + */ +"RiverTropicalSaltwater" | +/** + * A subterranean freshwater source + */ +"SubterraneanWater" | +/** + * A subterranean chasm + */ +"SubterraneanChasm" | +/** + * A subterranean magma pool + */ +"SubterraneanLava" | +/** + * All the main biomes + */ +"AllMain" | +/** + * Any land biome + */ +"AnyLand" | +/** + * Any ocean biome + */ +"AnyOcean" | +/** + * Any lake biome + */ +"AnyLake" | +/** + * Any temperate lake biome + */ +"AnyTemperateLake" | +/** + * Any tropical lake biome + */ +"AnyTropicalLake" | +/** + * Any river + */ +"AnyRiver" | +/** + * Any temperate river + */ +"AnyTemperateRiver" | +/** + * Any tropical river + */ +"AnyTropicalRiver" | +/** + * Any pool + */ +"AnyPool" | +/** + * Any non-freezing biome + */ +"NotFreezing" | +/** + * Any temperate biome + */ +"AnyTemperate" | +/** + * Any tropical biome + */ +"AnyTropical" | +/** + * Any forest biome + */ +"AnyForest" | +/** + * Any shrubland biome + */ +"AnyShrubland" | +/** + * Any grassland biome + */ +"AnyGrassland" | +/** + * Any savanna biome + */ +"AnySavanna" | +/** + * Any temperate forest biome + */ +"AnyTemperateForest" | +/** + * Any tropical forest biome + */ +"AnyTropicalForest" | +/** + * Any temperate broadleaf forest biome + */ +"AnyTemperateBroadleaf" | +/** + * Any tropical broadleaf forest biome + */ +"AnyTropicalBroadleaf" | +/** + * Any wetland biome + */ +"AnyWetland" | +/** + * Any temperate wetland biome + */ +"AnyTemperateWetland" | +/** + * Any tropical wetland biome + */ +"AnyTropicalWetland" | +/** + * Any tropical marsh biome + */ +"AnyTropicalMarsh" | +/** + * Any temperate marsh biome + */ +"AnyTemperateMarsh" | +/** + * Any tropical swamp biome + */ +"AnyTropicalSwamp" | +/** + * Any temperate swamp biome + */ +"AnyTemperateSwamp" | +/** + * Any desert biome + */ +"AnyDesert" | +/** + * An unknown token + */ +"Unknown" + +/** + * Represents a creature's body size at a specific age. + * + * This structure is used to define growth stages for creatures in Dwarf Fortress raw files. + * It corresponds to the `[BODY_SIZE:YEARS:DAYS:SIZE_CM3]` tag. + */ +export type BodySize = { years: number; days: number; sizeCm3: number } + +/** + * A struct representing a creature caste. + * + * Castes are specific subgroups within a creature species, often representing + * biological sexes, specialized roles, or unique variations specified in the raw files. + */ +export type Caste = { +/** + * The unique name used in raw files for this caste (e.g., "MALE", "FEMALE"). + */ +identifier: string; +/** + * A collection of tags assigned to this caste. + */ +tags: CasteTag[] | null; +/** + * Flavor text shown in-game when examining a creature of this caste. + */ +description: string | null; +/** + * The specific name for a creature in its baby stage. + */ +babyName: Name | null; +/** + * The name used specifically for this caste. + */ +casteName: Name | null; +/** + * The name for a creature in its child stage. + */ +childName: Name | null; +/** + * The range of eggs produced per clutch, measured as `[min, max]`. + */ +clutchSize: [number, number] | null; +/** + * The range of offspring produced per birth, measured as `[min, max]`. + */ +litterSize: [number, number] | null; +/** + * The range of life expectancy in game ticks, measured as `[min, max]`. + */ +maxAge: [number, number] | null; +/** + * The age in game ticks at which a creature ceases to be a baby. + */ +baby: number | null; +/** + * The age in game ticks at which a creature ceases to be a child. + */ +child: number | null; +/** + * A rating used to determine the challenge level of the creature. + */ +difficulty: number | null; +/** + * The size of eggs laid by this caste, measured in cubic centimeters. + */ +eggSize: number | null; +/** + * The distance or frequency at which this creature tramples grass. + */ +grassTrample: number | null; +/** + * The grazing requirement for the creature to survive. + */ +grazer: number | null; +/** + * The level of vision the creature has in dark environments. + */ +lowLightVision: number | null; +/** + * The value assigned to the creature when kept as a pet. + */ +petValue: number | null; +/** + * The relative frequency this caste appears in wild populations. + */ +popRatio: number | null; +/** + * The percentage change applied to the base body size. + */ +changeBodySizePercentage: number | null; +/** + * The classes or categories this caste belongs to for targeting. + */ +creatureClass: string[] | null; +/** + * Growth stages and volume measurements. + */ +bodySize: BodySize[] | null; +/** + * Material and frequency information for milking. + */ +milkable: Milkable | null; +/** + * Character and color data for map representation. + */ +tile: Tile | null; +/** + * The gaits by which the creature can move. + */ +gaits: Gait[] | null } + +/** + * Tokens that can be found in a creature's caste definitions. + */ +export type CasteTag = +/** + * Prevents tamed creature from being made available for adoption, instead allowing it to automatically adopt whoever it wants. + * The basic requirements for adoption are intact, and the creature will only adopt individuals who have a preference for their species. + * Used by cats in the vanilla game. When viewing a tame creature with this token, the message "This animal isn't interested in your + * wishes" will appear instead of "This adorable animal can't work" or "This animal is waiting to be trained". + * + * Appears as `ADOPTS_OWNER` + */ +"AdoptsOwner" | +/** + * Makes the creature need alcohol to get through the working day; it will choose to drink booze instead of water if possible. + * Going sober for too long reduces speed. + * + * Appears as `ALCOHOL_DEPENDENT` + */ +"AlcoholDependent" | +/** + * Sets the creature to be active during the day, night, and twilight in Adventurer Mode. Seems to be a separate value from `[DIURNAL]/[NOCTURNAL]/[CREPUSCULAR]`, + * rather than implying them. + * + * Appears as `ALL_ACTIVE` + */ +"AllActive" | +/** + * Caste-specific version of `[Creature::AltTile]`. Requires `[Tile]`. + * + * Appears as `CASTE_ALTTILE:SomeTile` + */ +{ AltTile: { +/** + * The tile to use + */ +tile: TileCharacter } } | +/** + * Found on `[LargePredator]`s who ambush their prey. Instead of charging relentlessly at prey, the predator will wait till the prey is + * within a few squares before charging. May or may not work on other creatures. + * + * Appears as `AMBUSHPREDATOR` + */ +"AmbushPredator" | +/** + * Allows a creature to breathe both in and out of water (unlike [Aquatic]) - does not prevent drowning in magma. + * + * Appears as `AMPHIBIOUS` + */ +"Amphibious" | +/** + * Applies the specified creature variation with the given arguments to the creature. See `[ApplyCreatureVariation]` for more information. + * + * Appears as `APPLY_CREATURE_VARIATION:SOME_VARIATION` or `APPLY_CREATURE_VARIATION:SOME_VARIATION:ARG1:ARG2:ARG3` + */ +{ ApplyCreatureVariation: { +/** + * Creature variation ID to apply + */ +id: string; +/** + * (Optional) any number of arguments to pass to the creature variation + */ +args: number[] } } | +/** + * Applies the effects of all pending `[CV_ADD_TAG]` and `[CV_REMOVE_TAG]` tokens that have been defined in the current creature (so far). + * + * Appears as `APPLY_CURRENT_CREATURE_VARIATION` + */ +"ApplyCurrentCreatureVariation" | +/** + * Enables the creature to breathe in water, but causes it to air-drown on dry land. + * + * Appears as `AQUATIC` + */ +"Aquatic" | +/** + * Causes the creature to be excluded from the object testing arena's creature spawning list. Typically applied to spoileriffic creatures. + * + * Appears as `ARENA_RESTRICTED` + */ +"ArenaRestricted" | +/** + * Prevents the creature from attacking or frightening creatures with the [Natural] tag. + * + * Appears as `AT_PEACE_WITH_WILDLIFE` + */ +"AtPeaceWithWildlife" | +/** + * Defines the attack name, and the body part used. + * + * Appears as `ATTACK:NAME:BODYPART:BY_CATEGORY:HORN` or similar + */ +{ Attack: { +/** + * The verb for the attack + */ +verb: string; +/** + * The body part selector used for the attack + */ +selector: string[] } } | +/** + * Specifies when a megabeast or semi-megabeast will attack the fortress. The attacks will start occurring when at least one of the requirements is met. + * Setting a value to 0 disables the trigger. + * + * Appears as `ATTACK_TRIGGER:0:1:2` + */ +{ AttackTrigger: { +/** + * Population trigger + */ +population: number; +/** + * Exported wealth trigger + */ +exported_wealth: number; +/** + * Created wealth trigger + */ +created_wealth: number } } | +/** + * Age at which creature is considered a child, the default is zero. One can think of this as the duration of the baby stage. + * + * Appears as `BABY:12` + */ +{ Baby: { +/** + * The age at which the creature is considered a child + */ +age: number } } | +/** + * Defines a new name for a creature in baby state at the caste level. For non-caste-specific baby names, see `[CreatureToken::GeneralBabyName]`. + * + * Appears as `BABYNAME:SomeName:SomeNames` + */ +{ BabyName: { +/** + * Singular name for the baby + */ +singular: string; +/** + * Plural name for the baby + */ +plural: string } } | +/** + * Creature may be subject to beaching, becoming stranded on shores, where they will eventually air-drown. The number indicates the frequency of the occurrence. + * Presumably requires the creature to be [Aquatic]. Used by orcas, sperm whales and sea nettle jellyfish in the vanilla game. + * + * Appears as `BEACH_FREQUENCY:100` + */ +{ BeachFrequency: { +/** + * Frequency of beaching + */ +frequency: number } } | +/** + * The creature is non-aggressive by default, and will never automatically be engaged by companions or soldiers, running away from any creatures + * that are not friendly to it, and will only defend itself if it becomes enraged. Can be thought of as the counterpoint of the `[LargePredator]` tag. + * When tamed, animals with this tag will be useless for fortress defense. + * + * Appears as `BENIGN` + */ +"Benign" | +/** + * Specifies what the creature's blood is made of. + * + * Appears as `BLOOD:SomeMaterial:SubMaterial?:SomeToken` + */ +{ Blood: { +/** + * Blood material + */ +material: string[]; +/** + * Blood token + */ +state: string } } | +/** + * Causes vampire-like behavior; the creature will suck the blood of unconscious victims when its thirst for blood grows sufficiently large. When controlling the + * creature in adventure mode, this can be done at will. Seems to be required to make the creature denouncable (in-world) as a creature of the night. + * + * Appears as `BLOODSUCKER` + */ +"BloodSucker" | +/** + * Draws body parts from `OBJECT:BODY` files (such as `body_default.txt`) + * + * Appears as `BODY:BODY_WITH_HEAD_FLAG:HEART:GUTS:BRAIN:MOUTH` + */ +{ Body: { +/** + * Body parts arguments + */ +body_parts: string[] } } | +/** + * These body modifiers give individual creatures different characteristics. In the case of `HEIGHT`, `BROADNESS` and `LENGTH`, the modifier is also a percentage change to + * the `BODY_SIZE` of the individual creature. The seven numbers afterward give a distribution of ranges. Each interval has an equal chance of occurring. + * + * Example: `BODY_APPEARANCE_MODIFIER:HEIGHT:90:95:98:100:102:105:110` + * + * * `HEIGHT`: marks the height to be changed + * * `90:95:98:100:102:105:110`: sets the range from the shortest (90% of the average height) to the tallest (110% of the average height) creature variation. + */ +{ BodyAppearanceModifier: { +/** + * Body part to modify + */ +attribute: string; +/** + * Range of values, spread from lowest to median to highest + */ +values: [number, number, number, number, number, number, number] } } | +/** + * Loads a plan from listed `OBJECT:BODY_DETAIL_PLAN` files, such as `b_detail_plan_default.txt`. Mass applies `USE_MATERIAL_TEMPLATE`, mass alters RELSIZE, alters + * body part positions, and will allow tissue layers to be defined. Tissue layers are defined in order of skin to bone here. + * + * Example: `BODY_DETAIL_PLAN:VERTEBRATE_TISSUE_LAYERS:SKIN:FAT:MUSCLE:BONE:CARTILAGE` + * + * This creates the detailed body of a fox, the skin, fat, muscle, bones and cartilage out of the vertebrate tissues. A maggot would only need: + * + * `BODY_DETAIL_PLAN:EXOSKELETON_TISSUE_LAYERS:SKIN:FAT:MUSCLE` + */ +{ BodyDetailPlan: { +/** + * Body detail plan to load + */ +body_plan: string; +/** + * Body detail plan arguments + */ +arguments: string[] } } | +/** + * Sets size at a given age. Size is in cubic centimeters, and for normal body materials, is roughly equal to the creature's average weight in grams. + * + * Appears as `BODY_SIZE:0:0:1000` + */ +{ BodySize: { +/** + * Year at which the size is set + */ +year: number; +/** + * Days at which the size is set + */ +days: number; +/** + * Size in cubic centimeters + */ +size: number } } | +/** + * Substitutes body part text with replacement text. Draws gloss information from `OBJECT:BODY`files (such as `body_default.txt`) + * + * Appears as `BODYGLOSS:SomeGloss` + */ +{ BodyGloss: { +/** + * The gloss to use on the body part + */ +gloss: string } } | +/** + * Creature eats bones. Implies [Carnivore]. Adventurers with this token are currently unable to eat bones. + * + * Appears as `BONECARN` + */ +"BoneCarn" | +/** + * Adds a type to a body part - used with `[SetBodyPartGroup]`. In vanilla DF, this is used for adding the type `[Geldable]` to the lower body of certain creatures. + * + * Appears as `BP_ADD_TYPE:SomeBodyPartType` + */ +{ BodyPartAddType: { +/** + * The body part type to add + */ +body_part_type: string } } | +/** + * Sets up the breadth of possibilities for appearance qualities for a selected `BP` group. e.g.: + * + * * Eyes (`CLOSE_SET`, `DEEP_SET`, `ROUND_VS_NARROW`, `LARGE_IRIS`) + * * Lips (`THICKNESS`) + * * Nose (`BROADNESS`, `LENGTH`, `UPTURNED`, `CONVEX`) + * * Ear (`SPLAYED_OUT`, `HANGING_LOBES`, `BROADNESS`, `HEIGHT`) + * * Tooth (`GAPS`) + * * Skull (`HIGH_CHEEKBONES`, `BROAD_CHIN`, `JUTTING CHIN`, `SQUARE_CHIN`) + * * Neck (`DEEP_VOICE`, `RASPY_VOICE`) + * * Head (`BROADNESS`, `HEIGHT`) + * + * Appears as `BP_APPEARANCE_MODIFIER:SomeQuality:0:0:0:0:0:0:0` + */ +{ BodyPartAppearanceModifier: { +/** + * The quality that can appear + */ +quality: string; +/** + * The spread of the quality, from lowest to median to highest + */ +spread: [number, number, number, number, number, number, number] } } | +/** + * Removes a type from a body part. Used with `[SetBodyPartGroup]`. + * + * Appears as `BP_REMOVE_TYPE:SomeBodyPartType` + */ +{ BodyPartRemoveType: { +/** + * The body part type to remove + */ +body_part_type: string } } | +/** + * Allows a creature to destroy furniture and buildings. Value `1` targets mostly doors, hatches, furniture and the like. Value `2` targets + * anything not made with the b + C commands. + * + * Appears as `BUILDINGDESTROYER:1` + */ +{ BuildingDestroyer: { +/** + * Whether the creature focuses on doors, hatches, furniture, etc. (`1`) or anything not made with the b + C commands (`2`) + */ +door_and_furniture_focused: boolean } } | +/** + * The creature can perform an interaction. + * + * Appears as `CAN_DO_INTERACTION:SomeInteraction` + */ +{ CanDoInteraction: { +/** + * Interaction to allow + */ +interaction: string } } | +/** + * The creature gains skills and can have professions. If a member of a civilization (even a pet) has this token, it'll need to eat, drink and sleep. + * Note that this token makes the creature unable to be eaten by an adventurer, so it is not recommended for uncivilized monsters. Adventurers lacking + * this token can allocate but not increase attributes and skills. Skills allocated will disappear on start. + * + * Appears as `CAN_LEARN` + */ +"CanLearn" | +/** + * Can talk. Note that this is not necessary for a creature to gain social skills. + * + * Appears as `CAN_SPEAK` + */ +"CanSpeak" | +/** + * Creature cannot climb, even if it has free grasp parts. + * + * Appears as `CANNOT_CLIMB` + */ +"CannotClimb" | +/** + * Creature cannot jump. + * + * Appears as `CANNOT_JUMP` + */ +"CannotJump" | +/** + * Acts like `[NotLiving]`, except that `[OpposedToLife]` creatures will attack them. + * + * Appears as `CANNOT_UNDEAD` + */ +"CannotUndead" | +/** + * Allows the creature to open doors that are set to be unpassable for pets. In adventure mode, creatures lacking this token will be unable to pass through door + * tiles except whilst these are occupied by other creatures. Not currently useful in Fortress mode as doors can no longer be set unpassable for pets. + * + * Appears as `CANOPENDOORS` + */ +"CanOpenDoors" | +/** + * Creature only eats meat. If the creature goes on rampages in worldgen, it will often devour the people/animals it kills. + * Does not seem to affect the diet of the adventurer in adventure mode. + * + * Appears as `CARNIVORE` + */ +"Carnivore" | +/** + * Gives the creature a bonus in caves. Also causes cave adaptation. + * + * Appears as `CAVE_ADAPT` + */ +"CaveAdaptation" | +/** + * Multiplies body size by a factor of (integer)%. 50 halves size, 200 doubles. + * + * Appears as `CHANGE_BODY_SIZE_PERC:100` + */ +{ ChangeBodySizePercent: { +/** + * The percentage to change the body size by + */ +percent: number } } | +/** + * Age at which creature is considered an adult - one can think of this as the duration of the child stage. Allows the creature's offspring to be + * rendered fully tame if trained during their childhood. + * + * Appears as `CHILD:12` + */ +{ Child: { +/** + * The age at which the creature is considered an adult + */ +age: number } } | +/** + * Defines a name for the creature in child state at the caste level. For non-caste-specific child names, see `[CreatureToken::GeneralChildName]`. + * + * Appears as `CHILDNAME:SomeName:SomeNames` + */ +{ ChildName: { +/** + * Singular name for the child + */ +singular: string; +/** + * Plural name for the child + */ +plural: string } } | +/** + * Number of eggs laid in one sitting. + * + * Appears as `CLUTCH_SIZE:1:1` + */ +{ ClutchSize: { +/** + * Minimum number of eggs laid in one sitting + */ +min: number; +/** + * Maximum number of eggs laid in one sitting + */ +max: number } } | +/** + * Caste-specific color + * + * Arguments: + * + * * `foreground`: The foreground color + * * `background`: The background color + * * `brightness`: The brightness of the color + * + * Appears as `CASTE_COLOR:0:0:0` + */ +{ Color: { +/** + * The foreground color + */ +foreground: number; +/** + * The background color + */ +background: number; +/** + * The brightness of the color + */ +brightness: number } } | +/** + * When combined with any of `[Pet]`, `[PackAnimal]`, `[WagonPuller]` and/or `[Mount]`, the creature is guaranteed to be domesticated by any civilization with + * `[EntityToken::CommonDomesticPet]`, `[EntityToken::CommonDomesticPackAnimal]`, `[EntityToken::CommonDomesticWagonPuller]` and/or `[EntityToken::CommonDomesticMount]` + * respectively. Such civilizations will always have access to the creature, even in the absence of wild populations. This token is invalid on `[CreatureToken::Fanciful]` creatures. + * + * Appears as `COMMON_DOMESTIC` + */ +"CommonDomestic" | +/** + * Creatures of this caste's species with the `[SpouseConverter]` and `[NightCreatureHunter]` tokens will kidnap `[SpouseConversionTarget]`s of an appropriate + * sex and convert them into castes with `CONVERTED_SPOUSE`. + * + * Appears as `CONVERTED_SPOUSE` + */ +"ConvertedSpouse" | +/** + * Set this to allow the creature to be cooked in meals without first being butchered/cleaned. Used by some water-dwelling vermin such as mussels, nautiluses and oysters. + * + * Appears as `COOKABLE_LIVE` + */ +"CookableLive" | +/** + * Creature is 'berserk' and will attack all other creatures, except members of its own species that also have the CRAZED tag. It will show Berserk in the unit list. + * Berserk creatures go on rampages during worldgen much more frequently than non-berserk ones. + * + * Appears as `CRAZED` + */ +"Crazed" | +/** + * An arbitrary creature classification. Can be set to anything, but the only vanilla uses are `GENERAL_POISON` (used in syndromes), `EDIBLE_GROUND_BUG` + * (used as targets for `[GobbleVerminClass]`), `MAMMAL`, and `POISONOUS` (both used for kobold pet eligibility). A single creature can have multiple classes. + * + * Appears as `CREATURE_CLASS:SomeClass` + */ +{ CreatureClass: { +/** + * The creature class + */ +class: string } } | +/** + * Sets the creature to be active at twilight in adventurer mode. + * + * Appears as `CREPUSCULAR` + */ +"Crepuscular" | +/** + * Allows a creature to steal and eat edible items from a site. It will attempt to grab a food item and immediately make its way to the map's edge, + * where it will disappear with it. If the creature goes on rampages during worldgen, it will often steal food instead of attacking. Trained and tame instances + * of the creature will no longer display this behavior. + * + * Appears as `CURIOUSBEAST_EATER` + */ +"CuriousBeastEater" | +/** + * Allows a creature to (very quickly) drink your alcohol. Or spill the barrel to the ground. Also affects undead versions of the creature. Unlike food or item thieves, + * drink thieves will consume your alcohol on the spot rather than run away with one piece of it. Trained and tame instances of the creature will no longer display this behavior. + * + * Appears as `CURIOUSBEAST_GUZZLER` + */ +"CuriousBeastGuzzler" | +/** + * Allows a creature to steal things (apparently, of the highest value it can find). It will attempt to grab an item of value and immediately make its way to the map's edge, + * where it will disappear with it. If a creature with any of the CURIOUSBEAST tokens carries anything off the map, even if it is a caravan's pack animal, it will be reported + * as stealing everything it carries. If the creature goes on rampages in worldgen, it will often steal items instead of attacking - kea birds are infamous for this. + * Trained and tame instances of the creature will no longer display this behavior. Also, makes the creature unable to drop hauled items until it enters combat. + * + * Appears as `CURIOUSBEAST_ITEM` + */ +"CuriousBeastItem" | +/** + * Adds a tag. Used in conjunction with creature variation templates. + * + * Appears as `CV_ADD_TAG:SomeTag` + */ +{ CreatureVariationAddTag: { +/** + * The tag to add + */ +tag: string } } | +/** + * Removes a tag. Used in conjunction with creature variation templates. + * + * Appears as `CV_REMOVE_TAG:SomeTag` + */ +{ CreatureVariationRemoveTag: { +/** + * The tag to remove + */ +tag: string } } | +/** + * Found on generated demons. Marks the caste to be used in the initial wave after breaking into the underworld, and by the demon civilizations created during world-gen breachings + * + * Appears as `DEMON` + */ +"Demon" | +/** + * A brief description of the creature type, as displayed when viewing the creature's description/thoughts & preferences screen. + */ +{ Description: { +/** + * The description to use + */ +description: string } } | +/** + * Causes the creature to die upon attacking. Used by honey bees to simulate them dying after using their stingers. + * + * Appears as `DIE_WHEN_VERMIN_BITE` + */ +"DieWhenVerminBite" | +/** + * Increases experience gain during adventure mode. Creatures with a difficulty of 11 or higher are not assigned for quests in adventure mode. + * + * Appears as `DIFFICULTY:10` + */ +{ Difficulty: { +/** + * The difficulty of the creature + */ +difficulty: number } } | +/** + * Sets the creature to be active during the day in Adventurer Mode. + * + * Appears as `DIURNAL` + */ +"Diurnal" | +/** + * The creature hunts vermin by diving from the air. On tame creatures, it has the same effect as `[HuntsVermin]`. Found on peregrine falcons. + * + * Appears as `DIVE_HUNTS_VERMIN` + */ +"DiveHuntsVermin" | +/** + * Defines the item that the creature drops upon being butchered. Used with `[ExtraButcherObject]`. + * + * Appears as `EBO_ITEM:SomeItem:SomeMaterial` + */ +{ ExtraButcherObjectItem: { +/** + * The item to add + */ +item: string; +/** + * The material of the item + */ +material: string[] } } | +/** + * The shape of the creature's extra butchering drop. Used with `[ExtraButcherObject]`. + * + * Appears as `EBO_SHAPE:SomeShape` + */ +{ ExtraButcherObjectShape: { +/** + * The shape to add + */ +shape: string } } | +/** + * Defines the material composition of eggs laid by the creature. Egg-laying creatures in the default game define this 3 times, using `LOCAL_CREATURE_MAT:EGGSHELL`, + * `LOCAL_CREATURE_MAT:EGG_WHITE`, and then `LOCAL_CREATURE_MAT:EGG_YOLK`. Eggs will be made out of eggshell. Edibility is determined by tags on whites or yolk, + * but they otherwise do not exist. + * + * Appears as `EGG_MATERIAL:SomeMaterial:SomeState` + */ +{ EggMaterial: { +/** + * The material to use + */ +material: string[]; +/** + * The state of the material + */ +state: string } } | +/** + * Determines the size of laid eggs. Doesn't affect hatching or cooking, but bigger eggs will be heavier, and may take longer to be hauled depending on the hauler's strength. + * + * Appears as `EGG_SIZE:100` + */ +{ EggSize: { +/** + * The size of the egg + */ +size: number } } | +/** + * Allows the creature to wear or wield items. + * + * Appears as `EQUIPS` + */ +"Equips" | +/** + * The creature drops an additional object when butchered, as defined by `[ExtraButcherObjectItem]` and `[ExtraButcherObjectShape]`. + * Used for gizzard stones in default creatures. For some materials, needs to be defined after caste definitions with `SELECT_CASTE:ALL` + * + * Appears as `EXTRA_BUTCHER_OBJECT` + */ +{ ExtraButcherObject: { +/** + * Details about the extra butcher object + */ +object_type: string; +/** + * Arguments for the extra butcher object + */ +arguments: string[] } } | +/** + * Defines a creature extract which can be obtained via small animal dissection. + * + * Appears as `EXTRACT:SomeExtract` + */ +{ Extract: { +/** + * The extract material + */ +material: string } } | +/** + * The creature can see regardless of whether it has working eyes and has full 360 degree vision, making it impossible to strike the creature from a blind spot + * in combat. Invisible creatures will also be seen, namely intelligent undead using a "vanish" power. + * + * Appears as `EXTRAVISION` + */ +"Extravision" | +/** + * Found on subterranean animal-man tribals. Currently defunct. In previous versions, it caused these creatures to crawl out of chasms and underground rivers. + * + * Appears as `FEATURE_ATTACK_GROUP` + */ +"FeatureAttackGroup" | +/** + * Found on forgotten beasts. Presumably makes it act as such, initiating underground attacks on fortresses, or leads to the pop-up message upon encountering one. + * Hides the creature from displaying in a `world_sites_and_pops.txt` file. Does not create historical figures like generated forgotten beasts do. + * + * Requires specifying a `[Biome]` in which the creature will live, and both surface and subterranean biomes are allowed. Does not stack with `[LargeRoaming]` and if + * both are used the creature will not spawn. Appears to be incompatible with [Demon] even if used in separate castes. + * + * Appears as `FEATURE_BEAST` + */ +"FeatureBeast" | +/** + * Makes the creature biologically female, enabling her to bear young. Usually specified inside a caste. + * + * Appears as `FEMALE` + */ +"Female" | +/** + * Makes the creature immune to FIREBALL and FIREJET attacks, and allows it to path through high temperature zones, like lava or fires. Does not, by itself, + * make the creature immune to the damaging effects of burning in fire, and does not prevent general heat damage or melting on its own (this would require adjustments + * to be made to the creature's body materials - see the dragon raws for an example). + * + * Appears as `FIREIMMUNE` + */ +"FireImmune" | +/** + * Like `[FireImmune]`, but also renders the creature immune to `DRAGONFIRE` attacks. + * + * Appears as `FIREIMMUNE_SUPER` + */ +"FireImmuneSuper" | +/** + * The creature's corpse is a single `FISH_RAW` food item that needs to be cleaned (into a FISH item) at a fishery to become edible. Before being cleaned the item is + * referred to as "raw". The food item is categorized under "fish" on the food and stocks screens, and when uncleaned it is sorted under "raw fish" in the stocks + * (but does not show up on the food screen). + * + * Without this or `[CookableLive]`, fished vermin will turn into food the same way as non-vermin creatures, resulting in multiple units of food (meat, brain, lungs, + * eyes, spleen etc.) from a single fished vermin. These units of food are categorized as meat by the game. + * + * Appears as `FISHITEM` + */ +"FishItem" | +/** + * The creature's body is constantly at this temperature, heating up or cooling the surrounding area. Alters the temperature of the creature's inventory and all + * adjacent tiles, with all the effects that this implies - may trigger wildfires at high enough values. Also makes the creature immune to extreme heat or cold, as + * long as the temperature set is not harmful to the materials that the creature is made from. Corpses and body parts of creatures with a fixed temperature maintain + * their temperature even after death. + * + * Note that temperatures of 12000 and higher may cause pathfinding issues in fortress mode. + * + * Appears as `FIXED_TEMP:10000` + */ +{ FixedTemp: { +/** + * The temperature of the creature + */ +temperature: number } } | +/** + * If engaged in combat, the creature will flee at the first sign of resistance. Used by kobolds in the vanilla game. + * + * Appears as `FLEEQUICK` + */ +"FleeQuick" | +/** + * Allows a creature to fly, independent of it having wings or not. Fortress Mode pathfinding only partially incorporates flying - flying creatures need a land path + * to exist between them and an area in order to access it, but as long as one such path exists, they do not need to use it, instead being able to fly over intervening + * obstacles. Winged creatures with this token can lose their ability to fly if their wings are crippled or severed. Winged creatures without this token will be unable + * to fly. (A 'wing' in this context refers to any body part with its own FLIER token). + * + * Appears as `FLIER` + */ +"Flier" | +/** + * Defines a gait by which the creature can move. Typically defined by using `APPLY_CREATURE_VARIATION:STANDARD_GAIT:xxx` in the creature's raws, instead of + * by using this token directly. See [Gait] for more detailed information. + * + * Since it's a bit complicated, we let the [Gait] `from_value()` handle parsing this token. + * + * Appears (typically) as `CV_NEW_TAG:GAIT:WALK:Sprint:!ARG4:10:3:!ARG2:50:LAYERS_SLOW:STRENGTH:AGILITY:STEALTH_SLOWS:50` + */ +{ Gait: { +/** + * The value of the token + */ +gait_values: string[] } } | +/** + * Has the same function as `[MaterialForceMultiplier]`, but applies to all attacks instead of just those involving a specific material. Appears to be overridden by + * `[MaterialForceMultiplier]` (werebeasts, for example, use both tokens to provide resistance to all materials, with one exception to which they are especially vulnerable). + * + * When struck with a weapon made of the any material, the force exerted will be multiplied by A/B. + * + * Appears as `GENERAL_MATERIAL_FORCE_MULTIPLIER:1:1` + */ +{ GeneralMaterialForceMultiplier: { +/** + * The material to apply the multiplier to + */ +value_a: number; +/** + * The multiplier to apply + */ +value_b: number } } | +/** + * Makes the creature get infections from necrotic tissue. + * + * Appears as `GETS_INFECTIONS_FROM_ROT` + */ +"GetsInfectionsFromRot" | +/** + * Makes the creature's wounds become infected if left untreated for too long. + * + * Appears as `GETS_WOUND_INFECTIONS` + */ +"GetsWoundInfections" | +/** + * Caste-specific glow color. + * + * Arguments: + * + * * `foreground`: The foreground color + * * `background`: The background color + * * `brightness`: The brightness of the color + * + * Appears as `CASTE_GLOWCOLOR:0:0:0` + */ +{ GlowColor: { +/** + * The foreground color + */ +foreground: number; +/** + * The background color + */ +background: number; +/** + * The brightness of the color + */ +brightness: number } } | +/** + * Caste-specific glow tile. + * + * Appears as `CASTE_GLOWTILE:SomeTile` + */ +{ GlowTile: { +/** + * The tile to use + */ +tile: TileCharacter } } | +/** + * The creature can and will gnaw its way out of animal traps and cages using the specified verb, depending on the material from which it is made (normally wood). + * + * Appears as `GNAWER:SomeVerb` + */ +{ Gnawer: { +/** + * The verb to use + */ +verb: string } } | +/** + * The creature eats vermin of the specified class. + * + * Appears as `GOBBLE_VERMIN_CLASS:SomeVerminClass` + */ +{ GobbleVerminClass: { +/** + * The vermin class to eat + */ +vermin_class: string } } | +/** + * The creature eats a specific vermin. + * + * Appears as `GOBBLE_VERMIN_CREATURE:SomeVerminCreature:SomeVerminCaste` + */ +{ GobbleVerminCreature: { +/** + * The vermin creature to eat + */ +vermin_creature: string; +/** + * The vermin caste to eat + */ +vermin_caste: string } } | +/** + * The value determines how rapidly grass is trampled when a creature steps on it - a value of 0 causes the creature to never damage grass, + * while a value of 100 causes grass to be trampled as rapidly as possible. + * + * Defaults to 5. + * + * Appears as `GRASS_TRAMPLE:5` + */ +{ GrassTrample: { +/** + * The trample value + */ +trample: number } } | +/** + * Used in Creature Variants. This token changes the adult body size to the average of the old adult body size and the target value and scales all intermediate + * growth stages by the same factor. + * + * Appears as `GRAVITATE_BODY_SIZE:25` + */ +{ GravitateBodySize: { +/** + * The target body size of the creature when it is an adult (fully grown) + */ +target: number } } | +/** + * The creature is a grazer - if tamed in fortress mode, it needs a pasture to survive. The higher the number, the less frequently it needs to eat in order to live. + * + * Not used since 0.40.12, replaced by `[StandardGrazer]` to fix Bug 4113. + * + * Appears as `GRAZER:100` + */ +{ Grazer: { +/** + * The grazer value + */ +grazer: number } } | +/** + * Defines certain behaviors for the creature. The habit types are: + * + * * `COLLECT_TROPHIES` + * * `COOK_PEOPLE` + * * `COOK_VERMIN` + * * `GRIND_VERMIN` + * * `COOK_BLOOD` + * * `GRIND_BONE_MEAL` + * * `EAT_BONE_PORRIDGE` + * * `USE_ANY_MELEE_WEAPON` + * * `GIANT_NEST` + * * `COLLECT_WEALTH` + * + * These require the creature to have a [Lair] to work properly, and also don't seem to work on creatures who are not a `[SemiMegabeast]`, `[Megabeast]`, or `[NightCreatureHunter]`. + * + * Appears as `HABIT:SomeHabit` + */ +{ Habit: { +/** + * The habit to add + */ +habit: string } } | +/** + * "If you set `HABIT_NUM` to a number, it should give you that exact number of habits according to the weights.". All lists of `HABIT`s are preceded by `[HABIT_NUM:TEST_ALL]` + * + * Appears as `HABIT_NUM:2` or `HABIT_NUM:TEST_ALL` + */ +{ HabitNumber: { +/** + * The number of habits to add. A value of `TEST_ALL` will add all habits and will cause number to be 0. + */ +number: HabitCount } } | +/** + * The creature has nerves in its muscles. Cutting the muscle tissue can sever motor and sensory nerves. + * + * Appears as `HAS_NERVES` + */ +"HasNerves" | +/** + * The creature has a shell. Seemingly no longer used - holdover from previous versions. + * + * Appears as `HASSHELL` + */ +"HasShell" | +/** + * Default 'NONE'. The creature's normal body temperature. Creature ceases maintaining temperature on death unlike fixed material temperatures. + * Provides minor protection from environmental temperature to the creature. + * + * Appears as `HOMEOTHERM:10000` + */ +{ Homeotherm: { +/** + * The temperature of the creature, as number or `NONE` (zero) which is the default + */ +temperature: number } } | +/** + * Creature hunts and kills nearby vermin, randomly walking between places with food laying on the ground or in stockpiles, to check for possible `[VerminEater]` vermin, + * but they'll kill any other vermin too. + */ +"HuntsVermin" | +/** + * The creature cannot move. Found on sponges. Will also stop a creature from breeding in fortress mode (MALE and FEMALE are affected, if one is IMMOBILE no breeding will happen). + * + * Appears as `IMMOBILE` + */ +"Immobile" | +/** + * The creature is immobile while on land. Only works on [Aquatic] creatures which can't breathe on land. + * + * Appears as `IMMOBILE_LAND` + */ +"ImmobileLand" | +/** + * The creature radiates fire. It will ignite, and potentially completely destroy, items the creature is standing on. Also gives the vermin a high chance of escaping from animal + * traps and cages made of any flammable materials (specifically ones that could be ignited by magma). + * + * Appears as `IMMOLATE` + */ +"Immolate" | +/** + * Alias for `[CanSpeak]` + `[CanLearn]`. + * + * Appears as `INTELLIGENT` + */ +"Intelligent" | +/** + * Specifies interaction details following a `[CanDoInteraction]` token. + * + * Appears as `[CDI:TYPE:SomeArgs..]`: + * + * * `[CDI:TOKEN:SPIT]` + * * `[CDI:ADV_NAME:Spit]` + * * `[CDI:USAGE_HINT:NEGATIVE_SOCIAL_RESPONSE]` + * * etc. + */ +{ InteractionDetail: { +/** + * The type of detail described + */ +label: string; +/** + * Arbitrary arguments for the interaction + */ +args: string[] } } | +/** + * Determines if the creature leaves behind a non-standard corpse (i.e. wood, statue, bars, stone, pool of liquid, etc.). Ethics may prevent actually using the item in jobs or reactions. + * + * Appears as `ITEMCORPSE:ItemToken:MaterialToken` + */ +{ ItemCorpse: { +/** + * The item token to use + */ +item: string; +/** + * The material token to use + */ +material: string[] } } | +/** + * The quality of an item-type corpse left behind. Valid values are: 0 for ordinary, 1 for well-crafted, 2 for finely-crafted, 3 for superior, 4 for exceptional, 5 for masterpiece. + * + * Appears as `ITEMCORPSE_QUALITY:3` + */ +{ ItemCorpseQuality: { +/** + * The quality of the item + */ +quality: number } } | +/** + * Found on megabeasts, semimegabeasts, and night creatures. The creature will seek out sites of this type and take them as lairs. The lair types are: + * + * * `SIMPLE_BURROW` + * * `SIMPLE_MOUND` + * * `WILDERNESS_LOCATION` + * * `SHRINE` + * * `LABYRINTH` + * + * Appears as `LAIR:SomeLair:Probability` + */ +{ Lair: { +/** + * The lair type + */ +lair: string; +/** + * The probability of the lair + */ +probability: number } } | +/** + * Defines certain features of the creature's lair. The only valid characteristic is `HAS_DOORS`. + * + * Appears as `LAIR_CHARACTERISTIC:SomeCharacteristic` + */ +{ LairCharacteristic: { +/** + * The characteristic to add + */ +characteristic: string } } | +/** + * This creature will actively hunt adventurers in its lair. + * + * Appears as `LAIR_HUNTER` + */ +"LairHunter" | +/** + * What this creature says while hunting adventurers in its lair. + * + * Appears as `LAIR_HUNTER_SPEECH:SomeSpeech` + */ +{ LairHunterSpeech: { +/** + * The file containing what the creature says + */ +speech_file: string } } | +/** + * Will attack things that are smaller than it (like dwarves). Only one group of "large predators" (possibly two groups on "savage" maps) will appear on any given map. + * In adventure mode, large predators will try to ambush and attack you (and your party will attack them back). When tamed, large predators tend to be much more + * aggressive to enemies than non-large predators, making them a good choice for an animal army. They may go on rampages in worldgen, and adventurers may receive quests + * to kill them. Also, they can be mentioned in the intro paragraph when starting a fortress e.g. "ere the wolves get hungry." + * + * Appears as `LARGE_PREDATOR` + */ +"LargePredator" | +/** + * Creature lays eggs instead of giving birth to live young. + * + * Appears as `LAYS_EGGS` + */ +"LaysEggs" | +/** + * Creature lays the specified item instead of regular eggs. + * + * Appears as `LAYS_UNUSUAL_EGGS:SomeItem:SomeMaterial` + */ +{ LaysUnusualEggs: { +/** + * The item to lay + */ +item: string; +/** + * The material of the item + */ +material: string[] } } | +/** + * The creature has ligaments in its `[CONNECTIVE_TISSUE_ANCHOR]` tissues (bone or chitin by default). Cutting the bone/chitin tissue severs the ligaments, + * disabling motor function if the target is a limb. + * + * Appears as `LIGAMENTS:SomeMaterial:HealingRate` + */ +{ Ligaments: { +/** + * The material to use + */ +material: string[]; +/** + * The healing rate + */ +healing_rate: number } } | +/** + * The creature will generate light, such as in adventurer mode at night. + * + * Appears as `LIGHT_GEN` + */ +"LightGen" | +/** + * The creature will attack enemies rather than flee from them. This tag has the same effect on player-controlled creatures - including modded dwarves. + * Retired as of v0.40.14 in favor of `[LargePredator]`. + * + * Appears as `LIKES_FIGHTING` + */ +"LikesFighting" | +/** + * Creature uses "sssssnake talk" (multiplies 'S' when talking - "My name isss Recisssiz."). Used by serpent men and reptile men in the vanilla game. + * C's with the same pronunciation (depending on the word) are not affected by this token. + * + * Appears as `LISP` + */ +"Lisp" | +/** + * Determines the number of offspring per one birth; default 1-3, not used in vanilla raws. + * + * Appears as `LITTERSIZE:1:3` + */ +{ LitterSize: { +/** + * The minimum number of offspring + */ +min: number; +/** + * The maximum number of offspring + */ +max: number } } | +/** + * Lets a creature open doors that are set to forbidden in fortress mode. + * + * Appears as `LOCKPICKER` + */ +"LockPicker" | +/** + * Determines how well a creature can see in the dark - higher is better. Dwarves have 10000, which amounts to perfect nightvision. + * + * Appears as `LOW_LIGHT_VISION:10000` + */ +{ LowLightVision: { +/** + * The vision value + */ +vision: number } } | +/** + * According to Toady One, this is completely interchangeable with `[AtPeaceWithWildlife]` and might have been used in very early versions of the game by + * wandering wizards or the ent-type tree creatures that used to be animated by elves. + * + * Appears as `MAGICAL` + */ +"Magical" | +/** + * The creature is able to see while submerged in magma. + * + * Appears as `MAGMA_VISION` + */ +"MagmaVision" | +/** + * Makes the creature biologically male. + * + * Appears as `MALE` + */ +"Male" | +/** + * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. + * + * Appears as `MANNERISM_LAUGH` + */ +"MannerismLaugh" | +/** + * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. + * + * Appears as `MANNERISM_SMILE` + */ +"MannerismSmile" | +/** + * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. + * + * Appears as `MANNERISM_WALK` + */ +"MannerismWalk" | +/** + * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. + * + * Appears as `MANNERISM_SIT` + */ +"MannerismSit" | +/** + * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. + * + * Appears as `MANNERISM_BREATH` + */ +"MannerismBreath" | +/** + * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. + * + * Appears as `MANNERISM_POSTURE` + */ +"MannerismPosture" | +/** + * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. + * + * Appears as `MANNERISM_STRETCH` + */ +"MannerismStretch" | +/** + * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. + * + * Appears as `MANNERISM_EYELIDS` + */ +"MannerismEyelids" | +/** + * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. + * + * Appears as `MANNERISM_FINGERS:SomeFinger:SomeFingers` + */ +{ MannerismFingers: { +/** + * The finger mannerism to add + */ +finger: string; +/** + * The fingers mannerism to add + */ +fingers: string } } | +/** + * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. + * + * Appears as `MANNERISM_NOSE:SomeNose` + */ +{ MannerismNose: { +/** + * The nose mannerism to add + */ +nose: string } } | +/** + * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. + * + * Appears as `MANNERISM_EAR:SomeEar` + */ +{ MannerismEar: { +/** + * The ear mannerism to add + */ +ear: string } } | +/** + * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. + * + * Appears as `MANNERISM_HEAD:SomeHead` + */ +{ MannerismHead: { +/** + * The head mannerism to add + */ +head: string } } | +/** + * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. + * + * Appears as `MANNERISM_EYES:SomeEyes` + */ +{ MannerismEyes: { +/** + * The eyes mannerism to add + */ +eyes: string } } | +/** + * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. + * + * Appears as `MANNERISM_MOUTH:SomeMouth` + */ +{ MannerismMouth: { +/** + * The mouth mannerism to add + */ +mouth: string } } | +/** + * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. + * + * Appears as `MANNERISM_HAIR:SomeHair` + */ +{ MannerismHair: { +/** + * The hair mannerism to add + */ +hair: string } } | +/** + * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. + * + * Appears as `MANNERISM_KNUCKLES:SomeKnuckles` + */ +{ MannerismKnuckles: { +/** + * The knuckles mannerism to add + */ +knuckles: string } } | +/** + * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. + * + * Appears as `MANNERISM_LIPS:SomeLips` + */ +{ MannerismLips: { +/** + * The lips mannerism to add + */ +lips: string } } | +/** + * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. + * + * Appears as `MANNERISM_CHEEK:SomeCheek` + */ +{ MannerismCheek: { +/** + * The cheek mannerism to add + */ +cheek: string } } | +/** + * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. + * + * Appears as `MANNERISM_NAILS:SomeNails` + */ +{ MannerismNails: { +/** + * The nails mannerism to add + */ +nails: string } } | +/** + * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. + * + * Appears as `MANNERISM_FEET:SomeFeet` + */ +{ MannerismFeet: { +/** + * The feet mannerism to add + */ +feet: string } } | +/** + * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. + * + * Appears as `MANNERISM_ARMS:SomeArms` + */ +{ MannerismArms: { +/** + * The arms mannerism to add + */ +arms: string } } | +/** + * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. + * + * Appears as `MANNERISM_HANDS:SomeHands` + */ +{ MannerismHands: { +/** + * The hands mannerism to add + */ +hands: string } } | +/** + * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. Appears to be unused. + * + * Appears as `MANNERISM_TONGUE:SomeTongue` + */ +{ MannerismTongue: { +/** + * The tongue mannerism to add + */ +tongue: string } } | +/** + * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. Appears to be unused. + * + * Appears as `MANNERISM_LEG:SomeLeg` + */ +{ MannerismLeg: { +/** + * The leg mannerism to add + */ +leg: string } } | +/** + * Sets the creature to be active at dawn in adventurer mode. + * + * Appears as `MATUTINAL` + */ +"Matutinal" | +/** + * Determines the creature's natural lifespan, using the specified minimum and maximum age values (in years). Each individual creature with this token is generated with a + * predetermined date (calculated down to the exact tick!) between these values, at which it is destined to die of old age, should it live long enough. Note that the + * probability of death at any given age does not increase as the creature gets older [3]. + * + * Creatures which lack this token are naturally immortal. The `NO_AGING` syndrome tag will prevent death by old age from occurring. Also note that, among civilized creatures, + * castes which lack this token will refuse to marry others with it, and vice versa. + * + * Appears as `MAXAGE:100:150` + */ +{ MaxAge: { +/** + * The minimum age of the creature + */ +min: number; +/** + * The maximum age of the creature + */ +max: number } } | +/** + * Makes the creature slowly stroll around, unless it's in combat or performing a job. If combined with `[CanLearn]`, will severely impact their pathfinding and lead the creature + * to move extremely slowly when not performing any task. Problematically applies to animal people based on the animal and war trained animals. + * + * Appears as `MEANDERER` + */ +"Meanderer" | +/** + * A 'boss' creature. A small number of the creatures are created during worldgen, their histories and descendants (if any) will be tracked in worldgen (as opposed to simply 'spawning'), + * and they will occasionally go on rampages, potentially leading to worship if they attack the same place multiple times. Their presence and number will also influence age names. + * When appearing in fortress mode, they will have a pop-up message announcing their arrival. They will remain hostile to military even after being tamed. + * + * Requires specifying a `[Biome]` in which the creature will live. Subterranean biomes appear to not be allowed. Does stack with `[LargeRoaming]` and if both are used the creature will spawn + * as both historical bosses and as wild animals. + * + * Appears as `MEGABEAST` + */ +"Megabeast" | +/** + * Default is 200. This means you can increase your attribute to 200% of its starting value (or the average value + your starting value if that is higher). + * + * Arguments: + * + * * `attribute`: The attribute to modify + * * `percentage`: The percentage to modify the attribute by + * + * Appears as `MENT_ATT_CAP_PERC:Attribute:200` + */ +{ MentalAttributeCapPercentage: { +/** + * The attribute to modify + */ +attribute: string; +/** + * The percentage to modify the attribute by + */ +percentage: number } } | +/** + * Sets up a mental attribute's range of values (0-5000). All mental attribute ranges default to 200:800:900:1000:1100:1300:2000. + * + * Arguments: + * + * * `attribute`: The attribute to modify + * * `ranges`: The ranges from lowest to highest with 7 steps + * + * Appears as `MENT_ATT_RANGE:Attribute:200:800:900:1000:1100:1300:2000` + */ +{ MentalAttributeRange: { +/** + * The attribute to modify + */ +attribute: string; +/** + * The ranges from lowest to highest with 7 steps + */ +ranges: [number, number, number, number, number, number, number] } } | +/** + * Mental attribute gain/decay rates. Lower numbers in the last three slots make decay occur faster. Defaults are 500:4:5:4. + * + * Arguments: + * + * * `attribute`: The attribute to modify + * * `improvement_cost`: The cost to improve the attribute + * * `decay_rate_unused`: The decay rate of the attribute when it is unused + * * `decay_rate_rusty`: The decay rate of the attribute when it is rusty + * * `decay_rate_demotion`: The decay rate of the attribute when it is demoted + * + * Appears as `MENT_ATT_RATE:Attribute:500:4:5:4` + */ +{ MentalAttributeRate: { +/** + * The attribute to modify + */ +attribute: string; +/** + * The cost to improve the attribute + */ +improvement_cost: number; +/** + * The decay rate of the attribute when it is unused + */ +decay_rate_unused: number; +/** + * The decay rate of the attribute when it is rusty + */ +decay_rate_rusty: number; +/** + * The decay rate of the attribute when it is demoted + */ +decay_rate_demotion: number } } | +/** + * Allows the creature to be milked in the farmer's workshop. The frequency is the amount of ticks the creature needs to "recharge" (i.e. how much time needs to pass before + * it can be milked again). Does not work on sentient creatures, regardless of ethics. + * + * Arguments: + * + * * `material`: The material of the milk + * * `frequency`: The frequency the creature can be milked + * + * Appears as `MILKABLE:SomeMaterial:1000` + */ +{ Milkable: { +/** + * The material of the milk + */ +material: string[]; +/** + * The frequency the creature can be milked + */ +frequency: number } } | +/** + * The creature spawns stealthed and will attempt to path into the fortress, pulling any levers it comes across. It will be invisible on the map and unit list until spotted by a citizen, + * at which point the game will pause and recenter on the creature. + * + * Used by gremlins in the vanilla game. "They go on little missions to mess with various fortress buildings, not just levers." + * + * Appears as `MISCHIEVOUS` or `MISCHIEVIOUS` (sic) + */ +"Mischievous" | +/** + * Seemingly no longer used. + * + * Appears as `MODVALUE:SomeValue` + */ +{ ModValue: { +/** + * The value to modify + */ +value: string } } | +/** + * Creature may be used as a mount. No use for the player in fortress mode, but enemy sieging forces may arrive with cavalry. Mounts are usable in adventure mode. + * + * Appears as `MOUNT` + */ +"Mount" | +/** + * Creature may be used as a mount, but civilizations cannot domesticate it in worldgen without certain exceptions. + * + * Appears as `MOUNT_EXOTIC` + */ +"MountExotic" | +/** + * Allows the creature to have all-around vision as long as it has multiple heads that can see. + * + * Appears as `MULTIPART_FULL_VISION` + */ +"MultipartFullVision" | +/** + * Makes the species usually produce a single offspring per birth, with a 1/500 chance of using the [LITTERSIZE] as usual. Requires [FEMALE]. + * + * Appears as `MULTIPLE_LITTER_RARE` + */ +"MultipleLitterRare" | +/** + * Name of the caste + * + * Arguments: + * + * * `singular`: The singular name of the caste + * * `plural`: The plural name of the caste + * * `adjective`: The adjective form of the caste + * + * Appears as `CASTE_NAME:SomeName:SomeNames:SomeAdjective` + */ +{ Name: { +/** + * The singular name of the caste + */ +singular: string; +/** + * The plural name of the caste + */ +plural: string; +/** + * The adjective form of the caste + */ +adjective: string } } | +/** + * Animal is considered to be natural. NATURAL animals will not engage creatures tagged with `[AtPeaceWitHWildlife]` in combat unless they are + * members of a hostile entity and vice-versa. + * + * Appears as `NATURAL` or `NATURAL_ANIMAL` + */ +"Natural" | +/** + * The creature possesses the specified skill at this level inherently - that is, it begins with the skill at this level, and the skill may never + * rust below that. A value of 15 is legendary. + * + * Arguments: + * + * * `skill`: The skill token to add + * * `level`: The level of the skill + * + * Appears as `NATURAL_SKILL:SomeSkill:15` + */ +{ NaturalSkill: { +/** + * The skill token to add + */ +skill: string; +/** + * The level of the skill + */ +level: number } } | +/** + * Creatures with this token can appear in bogeyman ambushes in adventure mode, where they adopt classical bogeyman traits such as stalking the adventurer + * and vaporising when dawn breaks. Such traits do not manifest if the creature is encountered outside of a bogeyman ambush (for instance, as a megabeast + * or a civilised being). In addition, their corpses and severed body parts turn into smoke after a short while. Note that setting the "Number of Bogeyman Types" + * in advanced world generation to 0 will only remove randomly-generated bogeymen. + * + * Appears as `NIGHT_CREATURE_BOGEYMAN` + */ +"NightCreatureBogeyman" | +/** + * Found on some necromancers. Creatures with this tag may periodically "perform horrible experiments" offscreen, during which they can use creature-targeting + * interactions with an `[I_SOURCE:EXPERIMENT]` tag on living creatures in their area. Worlds are generated with a list of procedurally-generated experiments, + * allowing necromancers to turn living people and animals into ghouls and other experimental creatures, and these will automatically be available to all experimenters; + * it does not appear possible to prevent this. You can mod in your own custom experiment interactions, but these are used very infrequently due to the large number + * of generated experiments. + * + * Appears as `NIGHT_CREATURE_EXPERIMENTER` + */ +"NightCreatureExperimenter" | +/** + * Found on night trolls and werebeasts. Implies that the creature is a night creature, and shows its description in legends mode entry. The creature is always hostile and + * will start no quarter combat with any nearby creatures, except for members of its own race. Note that this tag does not override the creature's normal behavior in fortress + * mode except for the aforementioned aggression, and doesn't prevent the creature from fleeing the battles it started. It also removes the creature's materials from stockpile + * settings list, making them be stored there regardless of settings. + * + * Does stack with `[LARGE_ROAMING]` and if both are used the creature will spawn as both historical hunters and as wild animals; this requires specifying a [BIOME] in which the + * creature will live, and subterranean biomes are allowed. + * + * This tag causes the usual behaviour of werebeasts in worldgen, that is, fleeing towns upon being cursed and conducting raids from a lair. If this tag is absent from a deity + * curse, the accursed will simply be driven out of towns in a similar manner to vampires. When paired with `SPOUSE_CONVERTER`, a very small population of the creature will be + * created during worldgen (sometimes only a single individual will be created), and their histories will be tracked (that is, they will not spawn spontaneously later, they must + * either have children or convert other creatures to increase their numbers). The creature will settle in a lair and go on rampages during worldgen. It will actively attempt to + * seek out potential conversion targets to abduct, convert, and have children with (if possible). + * + * Appears as `NIGHT_CREATURE_HUNTER` + */ +"NightCreatureHunter" | +/** + * Found on nightmares. Corpses and severed body parts derived from creatures with this token turn into smoke after a short while. + * + * Appears as `NIGHT_CREATURE_NIGHTMARE` + */ +"NightCreatureNightmare" | +/** + * Creature doesn't require connected body parts to move; generally used on undead creatures with connections that have rotted away. + * + * Appears as `NO_CONNECTIONS_FOR_MOVEMENT` + */ +"NoConnectionsForMovement" | +/** + * Creature cannot become dizzy. + * + * Appears as `NO_DIZZINESS` + */ +"NoDizziness" | +/** + * Creature does not need to drink. + * + * Appears as `NO_DRINK` + */ +"NoDrink" | +/** + * Creature does not need to eat. + * + * Appears as `NO_EAT` + */ +"NoEat" | +/** + * The creature caste does not appear in autumn. + * + * Appears as `NO_AUTUMN` + */ +"NoFall" | +/** + * Creature cannot suffer fevers. + * + * Appears as `NO_FEVERS` + */ +"NoFevers" | +/** + * The creature is biologically sexless. Makes the creature unable to breed. + * + * Appears as `NO_GENDER` + */ +"NoGender" | +/** + * The creature cannot raise any physical attributes. + * + * Appears as `NO_PHYS_ATT_GAIN` + */ +"NoPhysicalAttributeGain" | +/** + * The creature cannot lose any physical attributes. + * + * Appears as `NO_PHYS_ATT_RUST` + */ +"NoPhysicalAttributeRust" | +/** + * Creature does not need to sleep. Can still be rendered unconscious by other means. + * + * Appears as `NO_SLEEP` + */ +"NoSleep" | +/** + * The creature caste does not appear in spring. + * + * Appears as `NO_SPRING` + */ +"NoSpring" | +/** + * The creature caste does not appear in summer. + * + * Appears as `NO_SUMMER` + */ +"NoSummer" | +/** + * Creature doesn't require an organ with the [THOUGHT] tag to survive or attack; generally used on creatures that don't have brains. + * + * Appears as `NO_THOUGHT_CENTER_FOR_MOVEMENT` + */ +"NoThoughtCenterForMovement" | +/** + * Prevents creature from selecting its color based on its profession (e.g. Miner, Hunter, Wrestler). + * + * Appears as `NO_UNIT_TYPE_COLOR` + */ +"NoUnitTypeColor" | +/** + * Likely prevents the creature from leaving broken vegetation tracks + * + * Appears as `NO_VEGETATION_PERTURB` + */ +"NoVegetationDisturbance" | +/** + * The creature caste does not appear in winter. + * + * Appears as `NO_WINTER` + */ +"NoWinter" | +/** + * Creature has no bones. + * + * Appears as `NOBONES` + */ +"NoBones" | +/** + * Creature doesn't need to breathe or have [BREATHE] parts in body, nor can it drown or be strangled. Creatures living in magma must have this tag, + * otherwise they will drown. + * + * Appears as `NOBREATHE` + */ +"NoBreathe" | +/** + * Sets the creature to be active at night in adventure mode. + * + * Appears as `NOCTURNAL` + */ +"Nocturnal" | +/** + * Creature has no emotions. It is immune to the effects of stress and unable to rage, and its needs cannot be fulfilled in any way. + * Used on undead in the vanilla game. + * + * Appears as `NOEMOTION` + */ +"NoEmotion" | +/** + * Creature can't become tired or over-exerted from taking too many combat actions or moving at full speed for extended periods of time. + * + * Appears as `NOEXERT` + */ +"NoExert" | +/** + * Creature doesn't feel fear and will never flee from battle, and will be immune to ghosts' attempts to 'scare it to death'. + * Additionally, it causes bogeymen and nightmares to become friendly towards the creature. + * + * Appears as `NOFEAR` + */ +"NoFear" | +/** + * Creature will not be hunted or fed to wild beasts. + * + * Appears as `NOMEAT` + */ +"NoMeat" | +/** + * Creature isn't nauseated by gut hits and cannot vomit. + * + * Appears as `NONAUSEA` + */ +"NoNausea" | +/** + * Creature doesn't feel pain. + * + * Appears as `NOPAIN` + */ +"NoPain" | +/** + * Creature will not drop a hide when butchered. + * + * Appears as `NOSKIN` + */ +"NoSkin" | +/** + * Creature will not drop a skull on butchering, rot, or decay of severed head. + * + * Appears as `NOSKULL` + */ +"NoSkull" | +/** + * Does not produce miasma when rotting. + * + * Appears as `NOSMELLYROT` + */ +"NoSmellyRot" | +/** + * Weapons can't get stuck in the creature. + * + * Appears as `NOSTUCKINS` + */ +"NoStuckIns" | +/** + * Creature can't be stunned and knocked unconscious by pain or head injuries. Creatures with this tag never wake up from sleep in Fortress Mode. + * If this creature needs to sleep while playing, it will die. + * + * Appears as `NOSTUN` + */ +"NoStun" | +/** + * Cannot be butchered. + * + * Appears as `NOT_BUTCHERABLE` + */ +"NotButcherable" | +/** + * Cannot be raised from the dead by necromancers or evil clouds. Implies the creature is not a normal living being. Used by vampires, mummies and + * inorganic creatures like the amethyst man and bronze colossus. Creatures who are `[OPPOSED_TO_LIFE]` (undead) will be docile towards creatures with this token. + * + * Appears as `NOT_LIVING` + */ +"NotLiving" | +/** + * Creature doesn't require a [THOUGHT] body part to survive. Has the added effect of preventing speech, though directly controlling creatures that would otherwise + * be capable of speaking allows them to engage in conversation. + * + * Appears as `NOTHOUGHT` + */ +"NoThought" | +/** + * How easy the creature is to smell. The higher the number, the easier the creature is to sniff out. Defaults to 50. + * Vanilla creatures have values from 0 (undetectable) to 90 (noticeable by humans and dwarves). + * + * Appears as `ODOR_LEVEL:50` + */ +{ OdorLevel: { +/** + * The odor level, defaults to 50 + */ +odor_level: number } } | +/** + * What the creature smells like. If no odor string is defined, the creature name (not the caste name) is used. + * + * Appears as `ODOR_STRING:SomeOdor` + */ +{ OdorString: { +/** + * The odor string to use + */ +odor_string: string } } | +/** + * Is hostile to all creatures except undead and other non-living ones and will show Opposed to life in the unit list. Used by undead in the vanilla game. + * Functions without the `[NOT_LIVING]` token, and seems to imply said token as well. Undead will not be hostile to otherwise-living creatures given this token. + * Living creatures given this token will attack living creatures that lack it, while ignoring other living creatures that also have this token. + * + * Appears as `OPPOSED_TO_LIFE` + */ +"OpposedToLife" | +/** + * Determines caste's likelihood of having sexual attraction to certain sexes. Values default to 75:20:5 for the same sex and 5:20:75 for the opposite sex. + * The first value indicates how likely to be entirely uninterested in the sex, the second decides if the creature will be able to become lovers with that sex, + * the third decides whether they will be able to marry in worldgen and post-worldgen world activities (which implies being able to become lovers). Marriage seems + * to be able to happen in fort mode play regardless, as long as they are lovers first. + * + * Arguments: + * + * * `caste`: The caste to set orientation to (MALE or FEMALE typically) + * * `disinterested_chance`: The chance of being disinterested in `caste` + * * `casual_chance`: The chance of being casually interested in `caste` + * * `strong_chance`: The chance of being strongly interested in `caste` + * + * Appears as `ORIENTATION:SomeCaste:75:20:5` + */ +{ Orientation: { +/** + * The caste to set orientation to + */ +caste: string; +/** + * The chance of being disinterested in `caste` + */ +disinterested_chance: number; +/** + * The chance of being casually interested in `caste` + */ +casual_chance: number; +/** + * The chance of being strongly interested in `caste` + */ +strong_chance: number } } | +/** + * Lets you play as an outsider of this species in adventure mode. + * + * Appears as `OUTSIDER_CONTROLLABLE` + */ +"OutsiderControllable" | +/** + * Allows the creature to be used as a pack animal. Used by merchants without wagons and adventurers. Also prevents creature from dropping hauled items on its own + * + * Note: do not use for player-controllable creatures! May lead to the creature being domesticated during worldgen, even if it doesn't have `[COMMON_DOMESTIC]`. + * + * Appears as `PACK_ANIMAL` + */ +"PackAnimal" | +/** + * The creature is immune to all paralyzing special attacks. + * + * Appears as `PARALYZEIMMUNE` + */ +"ParalyzeImmune" | +/** + * Used to control the bat riders with paralyze-dart blowguns that flew through the 2D chasm. Doesn't do anything now. + * + * Appears as `PATTERNFLIER` + */ +"PatternFlier" | +/** + * In earlier versions, creature would generate pearls. Does nothing in the current version + * + * Appears as `PEARL` + */ +"Pearl" | +/** + * Controls the ability of vermin to find a way into containers when they are eating food from your stockpiles. + * + * Objects made of most materials (e.g. metal) roll a number from 0-100, and if the resulting number is greater than the penetrate power, their contents escape for the time + * being. Objects made of wood, leather, amber, or coral roll 0-95, and items made of cloth roll 0-90. + * + * Appears as `PENETRATEPOWER:100` + */ +{ PenetratePower: { +/** + * The penetration power + */ +penetrate_power: number } } | +/** + * Determines the range and chance of personality traits. Standard is 0:50:100. + * + * Arguments: + * + * * `personality_trait`: The trait to modify + * * `low`: The lowest chance of having the trait + * * `median`: The median chance of having the trait + * * `high`: The highest chance of having the trait + * + * Appears as `PERSONALITY:SomeTrait:0:50:100` + */ +{ Personality: { +/** + * The trait to modify + */ +personality_trait: string; +/** + * The lowest chance of having the trait + */ +low: number; +/** + * The median chance of having the trait + */ +median: number; +/** + * The highest chance of having the trait + */ +high: number } } | +/** + * Allows the creature to be tamed in Fortress mode. Prerequisite for all other working animal roles. Civilizations that encounter it in worldgen + * will tame and domesticate it for their own use. Adding this to civilization members will classify them as pets instead of citizens, with all the + * problems that entails. However, you can solve these problems using the popular plugin Dwarf Therapist, which is completely unaffected by the tag. + * + * Appears as `PET` + */ +"Pet" | +/** + * Allows the creature to be tamed in Fortress mode. Prerequisite for all other working animal roles. Civilizations cannot domesticate it in worldgen, + * with certain exceptions. Adding this to civilization members will classify them as pets instead of citizens, with all the problems that entails. + * + * Appears as `PET_EXOTIC` + */ +"PetExotic" | +/** + * How valuable a tamed animal is. Actual cost in points in the embarking screen is 1+(PETVALUE/2) for an untrained animal, 1+PETVALUE for a war/hunting one. + * + * Appears as `PETVALUE:100` + */ +{ PetValue: { +/** + * The pet value + */ +pet_value: number } } | +/** + * Divides the creature's [PETVALUE] by the specified number. Used by honey bees to prevent a single hive from being worth a fortune. + * + * Appears as `PETVALUE_DIVISOR:2` + */ +{ PetValueDivisor: { +/** + * The divisor + */ +divisor: number } } | +/** + * Default is 200. This means you can increase your attribute to 200% of its starting value (or the average value + your starting value if that is higher). + * + * Appears as `PHYS_ATT_CAP_PERC:Attribute:200` + */ +{ PhysicalAttributeCapPercentage: { +/** + * The attribute to modify + */ +attribute: string; +/** + * The percentage to modify the attribute by + */ +percentage: number } } | +/** + * Sets up a physical attribute's range of values (0-5000). All physical attribute ranges default to 200:700:900:1000:1100:1300:2000. + * + * Appears as `PHYS_ATT_RANGE:Attribute:200:700:900:1000:1100:1300:2000` + */ +{ PhysicalAttributeRange: { +/** + * The attribute to modify + */ +attribute: string; +/** + * The ranges from lowest to highest with 7 steps + */ +ranges: [number, number, number, number, number, number, number] } } | +/** + * Physical attribute gain/decay rates. Lower numbers in the last three slots make decay occur faster. Defaults for `STRENGTH`, `AGILITY`, `TOUGHNESS`, and `ENDURANCE` + * are `500:3:4:3`, while `RECUPERATION` and `DISEASE_RESISTANCE` default to `500:NONE:NONE:NONE`. + * + * Arguments: + * + * * `attribute`: The attribute to modify + * * `improvement_cost`: The cost to improve the attribute + * * `decay_rate_unused`: The decay rate of the attribute when it is unused + * * `decay_rate_rusty`: The decay rate of the attribute when it is rusty + * * `decay_rate_demotion`: The decay rate of the attribute when it is demoted + * + * Appears as `PHYS_ATT_RATE:Attribute:500:3:4:3` + */ +{ PhysicalAttributeRate: { +/** + * The attribute to modify + */ +attribute: string; +/** + * The cost to improve the attribute + */ +improvement_cost: number; +/** + * The decay rate of the attribute when it is unused + */ +decay_rate_unused: number; +/** + * The decay rate of the attribute when it is rusty + */ +decay_rate_rusty: number; +/** + * The decay rate of the attribute when it is demoted + */ +decay_rate_demotion: number } } | +/** + * Adds a body part group to selected body part group. Presumably used immediately after `[SET_BP_GROUP]`. + * + * Arguments: + * + * * `selector`: the selector for the specific body part + * + * Appears as `PLUS_BP_GROUP:SomeBodyPartSelector:SomeBodyPartGroup` + */ +{ PlusBodyPartGroup: { +/** + * The body part selector + */ +selector: string[] } } | +/** + * Weighted population of caste; Lower is rarer. Not to be confused with [FREQUENCY]. + * + * Appears as `POP_RATIO:100` + */ +{ PopulationRatio: { +/** + * The population ratio + */ +pop_ratio: number } } | +/** + * Allows the being to represent itself as a deity, allowing it to become the leader of a civilized group. Used by unique demons in the vanilla game. + * Requires `[CAN_SPEAK]` to actually do anything more than settle at a location (e.g. write books, lead armies, profane temples). Doesn't appear to do anything + * for creatures that are already civilized. Once the creature ascends to a position of leadership, it will proceed to act as a standard ruler for their + * entity and fulfill the same functions (hold tournaments, tame creatures, etc.). + * + * Appears as `POWER` + */ +"Power" | +/** + * Caste-specific profession name. + * + * Arguments: + * + * * `profession`: The profession name / unit type token ID + * * `singular`: The singular name of the profession + * * `plural`: The plural name of the profession + * + * Appears as `CASTE_PROFESSION_NAME:SomeProfession:SomeName:SomeNames` + */ +{ ProfessionName: { +/** + * The profession name / unit type token ID + */ +profession: string; +/** + * The singular name of the profession + */ +singular: string; +/** + * The plural name of the profession + */ +plural: string } } | +/** + * Creature has a percentage chance to flip out at visible non-friendly creatures. Enraged creatures attack anything regardless of timidity and get a + * strength bonus to their hits. This is what makes badgers so hardcore. + * + * Appears as `PRONE_TO_RAGE:100` + */ +{ ProneToRage: { +/** + * The rage chance + */ +rage_chance: number } } | +/** + * The creature has pus. Specifies the stuff secreted by infected wounds. + * + * Arguments: + * + * * `material`: The material of the pus + * * `material_state`: The material state of the pus + * + * Appears as `PUS:SomeMaterial:SomeMaterialState` + */ +{ Pus: { +/** + * The material of the pus + */ +material: string[]; +/** + * The material state of the pus + */ +state: string } } | +/** + * Specifies a new relative size for a part than what is stated in the body plan. For example, dwarves have larger livers. + * + * Arguments: + * + * * `selector`: the selector for the specific body part + * * `relative_size`: The relative size of the body part (by percentage?) + * + * Appears as `RELATIVE_SIZE:SomeBodyPartSelector:SomeBodyPart:100` + */ +{ RelativeSize: { +/** + * The body part selector + */ +selector: string[]; +/** + * The relative size of the body part (by percentage?) + */ +relative_size: number } } | +/** + * What the creature's remains are called. + * + * Appears as `REMAINS:SomeRemain:SomeRemains` + */ +{ Remains: { +/** + * The singular name of the remains + */ +singular: string; +/** + * The plural name of the remains + */ +plural: string } } | +/** + * What color the creature's remains are. + * + * Appears as `REMAINS_COLOR:SomeColor` + */ +{ RemainsColor: { +/** + * The color of the remains + */ +remains_color: string } } | +/** + * Goes with `[VERMIN_BITE]` and `[DIE_WHEN_VERMIN_BITE]`, the vermin creature will leave remains on death when biting. + * Leaving this tag out will cause the creature to disappear entirely after it bites. + * + * Appears as `REMAINS_ON_VERMIN_BITE_DEATH` + */ +"RemainsOnVerminBiteDeath" | +/** + * Unknown remains variation. + * + * Appears as `REMAINS_UNDETERMINED` + */ +"RemainsUndetermined" | +/** + * The creature will retract into the specified body part(s) when threatened. It will be unable to move or attack, but enemies will only be able to attack the specified body part(s). When one of the specified body part is severed off, the creature automatically unretracts and cannot retract anymore. More than one body part can be selected by using `BY_TYPE` or `BY_CATEGORY`. + * + * Second-person descriptions are used for adventurer mode natural ability. `""` can be used in the descriptions, being replaced with the proper pronoun (or lack thereof) in-game. + * + * Undead curled up creatures are buggy, specifically those that retract into their upper bodies: echidnas, hedgehogs and pangolins. The upper body is prevented from collapsing by a separate body part (the middle spine), which cannot be attacked when the creature is retracted. See `[PREVENTS_PARENT_COLLAPSE]`. Living creatures eventually succumb to blood loss, but undead creatures do not. Giant creatures also take a very long time to bleed out. + * + * Arguments: + * + * * `body_part_selector`: The body part selector to use + * * `body_part`: The body part to retract + * * `second_person`: Description using "you" and "your" + * * `third_person`: Description using "it" and "its" + * * `second_person_cancel`: Description using "you" and "your" when the creature is no longer retracted + * * `third_person_cancel`: Description using "it" and "its" when the creature is no longer retracted + * + * Appears as `RETRACT_INTO_BP:SomeBodyPartSelector:SomeBodyPart:SomeSecondPerson:SomeThirdPerson:SomeSecondPersonCancel:SomeThirdPersonCancel` + */ +{ RetractIntoBodyPart: { +/** + * The body part selector to use + */ +body_part_selector: string; +/** + * The body part to retract + */ +body_part: string; +/** + * Description using "you" and "your" + */ +second_person: string; +/** + * Description using "it" and "its" + */ +third_person: string; +/** + * Description using "you" and "your" when the creature is no longer retracted + */ +second_person_cancel: string; +/** + * Description using "it" and "its" when the creature is no longer retracted + */ +third_person_cancel: string } } | +/** + * Cat behavior. If it kills a vermin creature and has an owner, it carries the remains in its mouth and drops them at their feet. Requires `[HUNTS_VERMIN]`. + * + * Appears as `RETURNS_VERMIN_KILLS_TO_OWNER` + */ +"ReturnsVerminKillsToOwner" | +/** + * Creature will occasionally root around in the grass, looking for insects. Used for flavor in Adventurer Mode, spawns vermin edible for this creature in Fortress Mode. Creatures missing the specified body part will be unable to perform this action. The action produces a message (visible in adventure mode) in the form: + * + * [creature] [verb text] the [description of creature's location] + * + * In adventure mode, the "rooting around" ability will be included in the "natural abilities" menu, represented by its second person verb text. + * + * Arguments: + * + * * `body_part_selector`: the selector for the specific body part + * * `second_person_verb`: Verb to use in second person tense ("you") + * * `third_person_verb`: Verb to use in third person tense ("it") + * + * Appears as `ROOT_AROUND:SomeBodyPartSelector:SomeBodyPart:SomeSecondPersonVerb:SomeThirdPersonVerb` + */ +{ RootAround: { +/** + * The body part selector + */ +body_part_selector: string[]; +/** + * Verb to use in second person tense ("you") + */ +second_person_verb: string; +/** + * Verb to use in third person tense ("it") + */ +third_person_verb: string } } | +/** + * Causes the specified tissue layer(s) of the indicated body part(s) to secrete the designated material. A size 100 ('covering') contaminant is created over the affected body part(s) in its specified material state (and at the temperature appropriate to this state) when the trigger condition is met, as long as one of the secretory tissue layers is still intact. Valid triggers are: + * + * * `CONTINUOUS`: Secretion occurs once every 40 ticks in fortress mode, and every tick in adventurer mode. + * * `EXERTION`: Secretion occurs continuously (at the rate described above) whilst the creature is at minimum Tired following physical exertion. Note that this cannot occur if the creature has [NOEXERT]. + * * `EXTREME_EMOTION`: Secretion occurs continuously (as above) whilst the creature is distressed. Cannot occur in creatures with [NOEMOTION]. + * + * Arguments: + * + * * `material`: The material of the secretion + * * `material_state`: The material state of the secretion + * * `body_part_selector`: the selector for the specific body part + * * `tissue_layer`: The tissue layer to use + * * `trigger`: The trigger to use (`CONTINUOUS`, `EXERTION`, `EXTREME_EMOTION`) + * + * Appears as `SECRETION:SomeMaterial:SomeMaterialState:SomeBodyPartSelector:SomeBodyPart:SomeTissueLayer:SomeTrigger` + */ +{ Secretion: { +/** + * The material of the secretion + */ +material: string[]; +/** + * The material state of the secretion + */ +material_state: string; +/** + * The body part selector + */ +body_part_selector: string[]; +/** + * The tissue layer to use + */ +tissue_layer: string; +/** + * The trigger to use (`CONTINUOUS`, `EXERTION`, `EXTREME_EMOTION`) + */ +trigger: string } } | +/** + * Essentially the same as [MEGABEAST], but more of them are created during worldgen. See the semi-megabeast page for details. + * + * Appears as `SEMIMEGABEAST` + */ +"SemiMegabeast" | +/** + * Gives the creature the ability to sense creatures belonging to the specified creature class even when they lie far beyond line of sight, including through walls and floors. + * It also appears to reduce or negate the combat penalty of blind units when fighting creatures they can sense. In adventure mode, the specified tile will be used to represent + * sensed creatures when they cannot be seen directly. + * + * Arguments: + * + * * `creature_class`: The creature class to sense + * * `tile`: The tile to use + * * `color`: via foreground, background, and brightness values + * + * Appears as `SENSE_CREATURE_CLASS:SomeCreatureClass:SomeTile:0:0:0` + */ +{ SenseCreatureClass: { +/** + * The creature class to sense + */ +creature_class: string; +/** + * The tile to use + */ +tile: string; +/** + * The foreground color to use + */ +foreground: number; +/** + * The background color to use + */ +background: number; +/** + * The brightness to use + */ +brightness: number } } | +/** + * Begins a selection of body parts. + * + * Arguments: + * + * * `body_part_selector`: the selector for the specific body part + * + * Appears as `SET_BP_GROUP:SomeBodyPartSelector:SomeBodyPart` + */ +{ SetBodyPartGroup: { +/** + * The body part selector + */ +body_part_selector: string[] } } | +/** + * The rate at which this creature learns this skill. Requires `[CAN_LEARN]` or `[INTELLIGENT]` to function. + * + * Arguments: + * + * * `skill`: The skill to modify + * * `rate`: The rate to modify the skill by (percentage) + * + * Appears as `SKILL_LEARN_RATE:SomeSkill:100` + */ +{ SkillLearnRate: { +/** + * The skill to modify + */ +skill: string; +/** + * The rate to modify the skill by + */ +rate: number } } | +/** + * The rate at which this creature learns all skills. Requires `[CAN_LEARN]` or `[INTELLIGENT]` to function. + * + * Arguments: + * + * * `rate`: The rate to modify the skill by (percentage) + * + * Appears as `SKILL_LEARN_RATES:100` + */ +{ SkillLearnRates: { +/** + * The rate to modify the skill by + */ +rate: number } } | +/** + * Like `[SKILL_RATES]`, but applies to individual skills instead. Requires `[CAN_LEARN]` or `[INTELLIGENT]` to function. + * + * Arguments: + * + * * `skill`: The skill to modify + * * `improvement_rate`: The improvement rate to modify the skill by (percentage) + * * `decay_rate_unused`: The decay rate of the skill when it is unused + * * `decay_rate_rusty`: The decay rate of the skill when it is rusty + * * `decay_rate_demotion`: The decay rate of the skill when it is demoted + * + * Appears as `SKILL_RATE:SomeSkill:100:3:4:3` + */ +{ SkillRate: { +/** + * The skill to modify + */ +skill: string; +/** + * The improvement rate to modify the skill by + */ +improvement_rate: number; +/** + * The decay rate of the skill when it is unused + */ +decay_rate_unused: number; +/** + * The decay rate of the skill when it is rusty + */ +decay_rate_rusty: number; +/** + * The decay rate of the skill when it is demoted + */ +decay_rate_demotion: number } } | +/** + * Affects skill gain and decay. Lower numbers in the last three slots make decay occur faster (`[SKILL_RATES:100:1:1:1]` would cause rapid decay). + * The counter (decay) rates may also be replaced with NONE. + * + * Default is `[SKILL_RATES:100:8:16:16]`. Requires `[CAN_LEARN]` or `[INTELLIGENT]` to function. + * + * Arguments: + * + * * `improvement_rate`: The improvement rate to modify the skill by (percentage) + * * `decay_rate_unused`: The decay rate of the skill when it is unused + * * `decay_rate_rusty`: The decay rate of the skill when it is rusty + * * `decay_rate_demotion`: The decay rate of the skill when it is demoted + * + * Appears as `SKILL_RATES:100:8:16:16` + */ +{ SkillRates: { +/** + * The improvement rate to modify the skill by + */ +improvement_rate: number; +/** + * The decay rate of the skill when it is unused + */ +decay_rate_unused: number; +/** + * The decay rate of the skill when it is rusty + */ +decay_rate_rusty: number; +/** + * The decay rate of the skill when it is demoted + */ +decay_rate_demotion: number } } | +/** + * The rate at which this skill decays. Lower values cause the skill to decay faster. Requires `[CAN_LEARN]` or `[INTELLIGENT]` to function. + * + * Arguments: + * + * * `skill`: The skill to modify + * * `decay_rate_unused`: The decay rate of the skill when it is unused + * * `decay_rate_rusty`: The decay rate of the skill when it is rusty + * * `decay_rate_demotion`: The decay rate of the skill when it is demoted + * + * Appears as `SKILL_RUST_RATE:SomeSkill:3:4:3` + */ +{ SkillRustRate: { +/** + * The skill to modify + */ +skill: string; +/** + * The decay rate of the skill when it is unused + */ +decay_rate_unused: number; +/** + * The decay rate of the skill when it is rusty + */ +decay_rate_rusty: number; +/** + * The decay rate of the skill when it is demoted + */ +decay_rate_demotion: number } } | +/** + * The rate at which all skills decay. Lower values cause the skills to decay faster. Requires `[CAN_LEARN]` or `[INTELLIGENT]` to function. + * + * Arguments: + * + * * `decay_rate_unused`: The decay rate of the skill when it is unused + * * `decay_rate_rusty`: The decay rate of the skill when it is rusty + * * `decay_rate_demotion`: The decay rate of the skill when it is demoted + * + * Appears as `SKILL_RUST_RATES:3:4:3` + */ +{ SkillRustRates: { +/** + * The decay rate of the skill when it is unused + */ +decay_rate_unused: number; +/** + * The decay rate of the skill when it is rusty + */ +decay_rate_rusty: number; +/** + * The decay rate of the skill when it is demoted + */ +decay_rate_demotion: number } } | +/** + * Caste-specific `[SLAIN_SPEECH]`. + * + * Appears as `SLAIN_CASTE_SPEECH:SomeSpeechSet` + */ +{ SlainSpeech: { +/** + * The speech set to use + */ +speech_file: string } } | +/** + * Shorthand for `[CAN_LEARN]` + `[SKILL_LEARN_RATES:50]`. Used by a number of 'primitive' creatures (like ogres, giants and troglodytes) in the vanilla game. + * Applicable to player races. Prevents a player from recruiting nobility, even basic ones. Subterranean creatures with this token combined with [EVIL] will become + * servants of goblins in their civilizations, in the style of trolls. + * + * Appears as `SLOW_LEARNER` + */ +"SlowLearner" | +/** + * Creature leaves "remains" instead of a corpse. Used by vermin. + * + * Appears as `SMALL_REMAINS` + */ +"SmallRemains" | +/** + * Caste-specific solider tile. + * + * Appears as `CASTE_SOLDIER_TILE:SomeTile` + */ +{ SoldierTile: { +/** + * The tile to use + */ +tile: TileCharacter } } | +/** + * Caste-specific solider alt tile. + * + * Appears as `CASTE_SOLDIER_ALTTILE:SomeTile` + */ +{ SoldierAltTile: { +/** + * The tile to use + */ +tile: TileCharacter } } | +/** + * Creature makes sounds periodically, which can be heard in Adventure mode. + * + * For example, with `SOUND:PEACEFUL_INTERMITTENT:100:1000:VOCALIZATION:bark:barks:a loud bark` + * + * * First-person reads "You 'bark'" + * * Third-person reads "The capybara 'barks'" + * * Out of sight reads "You hear 'a loud bark'" + * + * Arguments: + * + * * `sound_type`: The sound type to use (`ALERT` or `PEACEFUL_INTERMITTENT`) + * * `sound_range`: The range of the sound (in tiles) + * * `sound_interval`: A delay before the sound is produced again (in ticks) + * * `requires_breathing`: Whether the creature needs to breathe to make the sound + * (indicated by `VOCALIZATION` for true or `NONE` for false) + * * `first_person`: The first-person description of the sound + * * `third_person`: The third-person description of the sound + * * `out_of_sight`: The out-of-sight description of the sound + * + * Appears as `SOUND:SomeSoundType:100:1000:SomeFirstPerson:SomeThirdPerson:SomeOutOfSight` + */ +{ Sound: { +/** + * The sound type to use (`ALERT` or `PEACEFUL_INTERMITTENT`) + */ +sound_type: string; +/** + * The range of the sound (in tiles) + */ +sound_range: number; +/** + * A delay before the sound is produced again (in ticks) + */ +sound_interval: number; +/** + * Whether the creature needs to breathe to make the sound + */ +requires_breathing: boolean; +/** + * The first-person description of the sound + */ +first_person: string; +/** + * The third-person description of the sound + */ +third_person: string; +/** + * The out-of-sight description of the sound + */ +out_of_sight: string } } | +/** + * Creature will only appear in biomes with this plant or creature available. + * Grazers given a specific type of grass (such as pandas and bamboo) will only eat that grass and nothing else, risking starvation if there's none available. + * + * Arguments: + * + * * `food_type`: The type of the required food + * * `identifier`: The identifier of the required plant or creature + * + * Appears as `SPECIFIC_FOOD:PLANT:Bamboo` or `SPECIFIC_FOOD:CREATURE:Tiger` + */ +{ SpecificFood: { +/** + * The type of the required food + */ +food_type: ObjectType; +/** + * The identifier of the required plant or creature + */ +identifier: string } } | +/** + * This creature can be converted by a night creature with `[SPOUSE_CONVERTER]`. + * + * Appears as `SPOUSE_CONVERSION_TARGET` + */ +"SpouseConversionTarget" | +/** + * If the creature has the `[NIGHT_CREATURE_HUNTER]` tag, it will kidnap `[SPOUSE_CONVERSION_TARGET]`s and transform them into the caste of its species + * with the `[CONVERTED_SPOUSE]` tag during worldgen. It may also start families this way. + * + * Appears as `SPOUSE_CONVERTER` + */ +"SpouseConverter" | +/** + * If the creature rules over a site, it will cause the local landscape to be corrupted into evil surroundings associated with the creature's spheres. + * The creature must have at least one of the following spheres for this to take effect: BLIGHT, DEATH, DISEASE, DEFORMITY, NIGHTMARES. The first three kill vegetation, + * while the others sometimes do. The last two get evil plants and evil animals sometimes. NIGHTMARES gets bogeymen. [4] Used by demons in the vanilla game. + * + * Appears as `SPREAD_EVIL_SPHERES_IF_RULER` + */ +"SpreadEvilSpheresIfRuler" | +/** + * Caste does not require [GRASP] body parts to climb -- it can climb with [STANCE] parts instead. + * + * Appears as `STANCE_CLIMBER` + */ +"StanceClimber" | +/** + * Acts as [GRAZER] but set to 20000*G*(max size)^(-3/4), where G defaults to 100 but can be set in `d_init`, and the whole thing is trapped between 150 and 3 million. + * Used for all grazers in the default creature raws. + * + * Appears as `STANDARD_GRAZER` + */ +"StandardGrazer" | +/** + * The creature will get strange moods in fortress mode and can produce artifacts. + * + * Appears as `STRANGE_MOODS` + */ +"StrangeMoods" | +/** + * Gives the creature knowledge of any secrets with `[SUPERNATURAL_LEARNING_POSSIBLE]` that match its spheres and also prevents it from becoming a vampire or werebeast. + * Other effects are unknown. + * + * Appears as `SUPERNATURAL` + */ +"Supernatural" | +/** + * The creature naturally knows how to swim perfectly and does not use the swimmer skill, as opposed to `[SWIMS_LEARNED]` below. + * However, Fortress mode AI never paths into water anyway, so it's less useful there. + * + * Appears as `SWIMS_INNATE` + */ +"SwimsInnate" | +/** + * The creature swims only as well as their present swimming skill allows them to. + * + * Appears as `SWIMS_LEARNED` + */ +"SwimsLearned" | +/** + * Dilutes the effects of syndromes which have the specified identifier. A percentage of 100 is equal to the regular syndrome effect severity, higher percentages reduce severity. + * + * Arguments: + * + * * `syndrome`: The syndrome to modify + * * `percentage`: The percentage to modify the syndrome by + * + * Appears as `SYNDROME_DILUTION_FACTOR:SomeSyndrome:100` + */ +{ SyndromeDilutionFactor: { +/** + * The syndrome to modify + */ +syndrome: string; +/** + * The percentage to modify the syndrome by + */ +percentage: number } } | +/** + * The creature has tendons in its `[CONNECTIVE_TISSUE_ANCHOR]` tissues (bone or chitin by default). + * Cutting the bone/chitin tissue severs the tendons, disabling motor function if the target is a limb. + * + * Arguments: + * + * * `material`: The material of the tendons + * * `healing_rate`: The rate at which the tendons heal (lower is faster) + * + * Appears as `TENDONS:SomeMaterial:100` + */ +{ Tendons: { +/** + * The material of the tendons + */ +material: string[]; +/** + * The rate at which the tendons heal (lower is faster) + */ +healing_rate: number } } | +/** + * The creature's webs can catch larger creatures. + * + * Appears as `THICKWEB` + */ +"ThickWeb" | +/** + * Caste-specific tile. + * + * Appears as `CASTE_TILE:SomeTile` + */ +{ Tile: { +/** + * The tile to use + */ +tile: TileCharacter } } | +/** + * Adds the tissue layer to wherever it is required. + * + * Non-argument Locations can be FRONT, RIGHT, LEFT, TOP, BOTTOM. Argument locations are AROUND and CLEANS, requiring a further body part and a % of coverage/cleansing + * + * Arguments: + * + * * `body_part_selector`: the selector for the specific body part + * * `tissue`: The name of the tissue to use + * * `location`: The location to use (`FRONT`, `RIGHT`, `LEFT`, `TOP`, `BOTTOM`) or with an additional argument, (`AROUND`, `CLEANS`) with a body part and a percentage + * + * Appears as `[TISSUE_LAYER:SomeBodyPartSelector:SomeBodyPart:SomeTissue:SomeLocation]` or `[TISSUE_LAYER:SomeBodyPartSelector:SomeBodyPart:SomeTissue:SomeLocation:SomeBodyPart:100]` + * ALSO appears as `[TISSUE_LAYER_OVER:SomeBodyPartSelector:SomeBodyPart:SomeTissue:SomeLocation]` or `[TISSUE_LAYER_OVER:SomeBodyPartSelector:SomeBodyPart:SomeTissue:SomeLocation:SomeBodyPart:100]` + */ +{ TissueLayer: { +/** + * The body part selector + */ +body_part_selector: string[]; +/** + * The tissue to apply (e.g. NAIL) + */ +tissue: string; +/** + * The remaining tokens defining location/positioning. + * e.g. ["FRONT"] or ["ABOVE", "BY_CATEGORY", "EYE"] or [] + */ +positioning: string[] } } | +/** + * Adds the tissue layer under a given part. + * + * For example, an iron man has a gaseous poison within, and this tissue (GAS is its name) has the token `[TISSUE_LEAKS]` and its state is GAS, so when you puncture the iron outside and + * damage this tissue it leaks gas (can have a syndrome by using a previous one in the creature sample.) + * `[TISSUE_LAYER_UNDER:BY_CATEGORY:ALL:{tissue}]` + * + * `{tissue}` is what will be under the `TISSUE_LAYER`; here is an example Tissue from the Iron Man: + * + * `[TISSUE:GAS] [TISSUE_NAME:gas:NP] [TISSUE_MATERIAL:LOCAL_CREATURE_MAT:GAS] [TISSUE_MAT_STATE:GAS] [RELATIVE_THICKNESS:50] [TISSUE_LEAKS] [TISSUE_SHAPE:LAYER]` + * + * Arguments: + * + * * `body_part_selector`: The body part selector to use (`BY_TYPE`, `BY_CATEGORY`, `BY_TOKEN`) + * * `body_part`: The body part to use (via category, type or token) + * * `tissue`: The name of the tissue to use + * + * Appears as `TISSUE_LAYER_UNDER:SomeBodyPartSelector:SomeBodyPart:SomeTissue` + */ +{ TissueLayerUnder: { +/** + * The body part selector to use (`BY_TYPE`, `BY_CATEGORY`, `BY_TOKEN`) + */ +body_part_selector: string; +/** + * The body part to use (via category, type or token) + */ +body_part: string; +/** + * The name of the tissue to use + */ +tissue: string } } | +/** + * Found on titans. Cannot be specified in user-defined raws. + * + * Appears as `TITAN` + */ +"Titan" | +/** + * How much the creature can carry when used by merchants. 1000 by default. If a civilization uses a custom pack animal via `ALWAYS_PACK`, you must manually add a capacity to the raws of that + * creature itself. Capacity defaults to null leading to empty caravans. + * + * Arguments: + * + * * `capacity`: The capacity of the creature + * + * Appears as `TRADE_CAPACITY:1000` + */ +{ TradeCapacity: { +/** + * The capacity of the creature + */ +capacity: number } } | +/** + * Shortcut for `[TRAINABLE_HUNTING]` + `[TRAINABLE_WAR]`. + * + * Appears as `TRAINABLE` + */ +"Trainable" | +/** + * Can be trained as a hunting beast, increasing speed. + * + * Appears as `TRAINABLE_HUNTING` + */ +"TrainableHunting" | +/** + * Can be trained as a war beast, increasing strength and endurance. + * + * Appears as `TRAINABLE_WAR` + */ +"TrainableWar" | +/** + * Allows the creature to go into martial trances. Used by dwarves in the vanilla game. + * + * Appears as `TRANCES` + */ +"Trances" | +/** + * The creature will never trigger traps it steps on. Used by a number of creatures. Doesn't make the creature immune to remotely activated traps (like retractable spikes being triggered + * while the creature is standing over them). TRAPAVOID creatures lose this power if they're immobilized while standing in a trap, be it by stepping on thick web, being paralyzed or being + * knocked unconscious. + * + * Appears as `TRAPAVOID` + */ +"TrapAvoid" | +/** + * The creature is displayed as blue when in 7/7 water. Used on fish and amphibious creatures which swim under the water. + * + * Appears as `UNDERSWIM` + */ +"UnderSwim" | +/** + * Found on generated demons; causes the game to create a single named instance of the demon which will emerge from the underworld and take over civilizations during worldgen. + * + * Appears as `UNIQUE_DEMON` + */ +"UniqueDemon" | +/** + * Like `[AT_PEACE_WITH_WILDLIFE]`, but also makes the creature more valued in artwork by civilisations with the PLANT sphere. [5] Used by grimelings in the vanilla game. + * + * Appears as `VEGETATION` + */ +"Vegetation" | +/** + * Enables vermin to bite other creatures, injecting the specified material. See `[SPECIALATTACK_INJECT_EXTRACT]` for details about injection - this token presumably works in a similar manner. + * + * Arguments: + * + * * `chance`: The chance to inject the material + * * `verb`: The verb to use (e.g. "bitten, stung") + * * `material`: The material to inject + * * `material_state`: The material state to inject + * + * Appears as `VERMIN_BITE:100:bitten:SomeMaterial:SomeMaterialState` + */ +{ VerminBite: { +/** + * The chance to inject the material + */ +chance: number; +/** + * The verb to use (e.g. "bitten, stung") + */ +verb: string; +/** + * The material to inject + */ +material: string[]; +/** + * The material state to inject + */ +material_state: string } } | +/** + * Some dwarves will hate the creature and get unhappy thoughts when around it. See the list of hateable vermin for details. + * + * Appears as `VERMIN_HATEABLE` + */ +"VerminHateable" | +/** + * This makes the creature move in a swarm of creatures of the same race as it (e.g. swarm of flies, swarm of ants). + * + * Appears as `VERMIN_MICRO` + */ +"VerminMicro" | +/** + * The creature cannot be caught by fishing. + * + * Appears as `VERMIN_NOFISH` + */ +"VerminNoFish" | +/** + * The creature will not be observed randomly roaming about the map. + * + * Appears as `VERMIN_NOROAM` + */ +"VerminNoRoam" | +/** + * The creature cannot be caught in baited animal traps; however, a "catch live land animal" task may still be able to capture one if a dwarf finds one roaming around. + * + * Appears as `VERMIN_NOTRAP` + */ +"VerminNoTrap" | +/** + * Old shorthand for "does cat stuff". Contains `[AT_PEACE_WITH_WILDLIFE]` + `[RETURNS_VERMIN_KILLS_TO_OWNER]` + `[HUNTS_VERMIN]` + `[ADOPTS_OWNER]`. + * + * Appears as `VERMINHUNTER` + */ +"VerminHunter" | +/** + * Sets the creature to be active during the evening in adventurer mode. + * + * Appears as `VESPERTINE` + */ +"Vespertine" | +/** + * Value should determine how close you have to get to a critter before it attacks (or prevents adv mode travel etc.) Default is 20. + * + * Appears as `VIEWRANGE:20` + */ +{ ViewRange: { +/** + * The view range of the creature, default is 20 + */ +view_range: number } } | +/** + * The width of the creature's vision arcs, in degrees (i.e. 0 to 360). The first number is binocular vision, the second is non-binocular vision. + * Binocular vision has a minimum of about 10 degrees, monocular, a maximum of about 350 degrees. Values past these limits will be accepted, but will default to ~10 degrees + * and ~350 degrees respectively. + * + * Defaults are 60:120. + * + * Appears as `VISION_ARC:60:120` + */ +{ VisionArc: { +/** + * The binocular vision arc of the creature, default is 60 + */ +binocular: number; +/** + * The non-binocular vision arc of the creature, default is 120 + */ +non_binocular: number } } | +/** + * Allows the creature to pull caravan wagons. If a civilization doesn't have access to any, it is restricted to trading with pack animals. + * + * Appears as `WAGON_PULLER` + */ +"WagonPuller" | +/** + * Allows the creature to create webs, and defines what the webs are made of. + * + * Arguments: + * + * * `material`: The material of the webs + * + * Appears as `WEBBER:SomeMaterial` + */ +{ Webber: { +/** + * The material of the webs + */ +material: string[] } } | +/** + * The creature will not get caught in thick webs. Used by creatures who can shoot thick webs (such as giant cave spiders) in order to make them immune to their own attacks. + * + * Appears as `WEBIMMUNE` + */ +"WebImmune" | +/** + * An unknown token. + */ +"Unknown" | +/** + * A night creature + */ +"NightCreature" | +/** + * Not fire immune + */ +"NotFireImmune" | +/** + * Has blood + */ +"HasBlood" | +/** + * Can grasp + */ +"Grasp" | +/** + * The gait of the race + */ +"RaceGait" | +/** + * Cannot breathe water + */ +"CannotBreatheWater" | +/** + * Is a natural animal + */ +"NaturalAnimal" | +/** + * Is a curious beast + */ +"CuriousBeast" | +/** + * Is a flying curious beast + */ +"CannotBreatheAir" + +/** + * Options for configuring the database client behavior. + */ +export type ClientOptions = { +/** + * If true, the database will be wiped and re-initialized on startup. + */ +resetDatabase: boolean; +/** + * If true, existing raw definitions will be updated if the identifier exists in the module. + */ +overwriteRaws: boolean } + +/** + * Represents a Dwarf Fortress color triplet. + * + * This format is used throughout the game raws to define the foreground, + * background, and brightness/intensity of tiles and text. + */ +export type Color = { +/** + * The foreground color index (0-7). + */ +foreground: number; +/** + * The background color index (0-7). + */ +background: number; +/** + * The brightness or intensity toggle (0 or 1). + */ +brightness: number } + +/** + * The color modification of the tile + */ +export type ColorModificationTag = +/** + * The color is as is + */ +"asIs" + +/** + * A condition that can be applied to a tile/entity + */ +export type ConditionTag = +/** + * No condition + */ +"none" | +/** + * A portrait of the creature, used when interacting with them + */ +"portrait" | +/** + * The start of a condition + */ +"condition" | +/** + * Used when defining a default sprite image + * + * `[DEFAULT:...]` + */ +"default" | +/** + * Used when defining a child sprite image + * + * `[CHILD:...]` + */ +"childPrime" | +/** + * Used when defining a baby sprite image + * + * `[BABY:...]` + */ +"babyPrime" | +/** + * Displayed if the creature is raised from the dead, although it is not + * known how this is decided. Raised status is not related to having a + * syndrome with the class from `[CONDITION_SYN_CLASS]` or from having + * `[NOT_LIVING]`/`[OPPOSED_TO_LIFE]`. + * + * Used when defining a sprite image. + * + * `[ANIMATED:...]` + */ +"animated" | +/** + * Displayed as soon as the creature dies. + * + * `[CORPSE:...]` + */ +"corpse" | +/** + * Displayed in menus. Useful for large images that would extend beyond the + * menu boxes otherwise. + * + * `[LIST_ICON]` + */ +"listIcon" | +/** + * Displayed in interaction menus in Adventure Mode, overrides `LIST_ICON` when + * specified in a creature `CAN_DO_INTERACTION` using `CDI:TOKEN:token_name`. + * + * Might accept referenced token_name before standard secondaries. + * + * `[CDI_LIST_ICON]` + */ +"cdiListIcon" | +/** + * Condition of being trained for hunting + */ +"trainedHunter" | +/** + * Condition of being trained for war + */ +"trainedWar" | +/** + * Condition of being a skeleton + */ +"skeleton" | +/** + * Condition of being a skeleton with a skull + */ +"skeletonWithSkull" | +/** + * Condition of being a zombie + */ +"zombie" | +/** + * Condition of being a necromancer + */ +"necromancer" | +/** + * Condition of being male + */ +"male" | +/** + * Condition of being female + */ +"female" | +/** + * `[CONDITION_BABY]` + */ +"baby" | +/** + * Condition of being a vampire + */ +"vampireCursed" | +/** + * Condition of being a ghoul + */ +"ghoul" | +/** + * Condition of being a disturbed dead + */ +"disturbedDead" | +/** + * Condition of being remains + */ +"remains" | +/** + * Displayed if the unit escorts a tax collector (unused). + * + * `[TAX_ESCORT]` + */ +"taxEscort" | +/** + * Displayed if the unit is law enforcement. + * + * `[LAW_ENFORCE]` + */ +"lawEnforcement" | +/** + * Displayed if the creature is an adventurer. + * + * `[ADVENTURER]` + */ +"adventurer" | +/** + * The creature is in the dark. Graphical replacement for `[GLOWTILE]`. + * + * `[GLOW]` + */ +"glow" | +/** + * As `[GLOW]`, but with their left eye missing. If the sprite is facing forwards, then the + * visually leftmost eye should remain. + * + * `[GLOW_LEFT_GONE]` + */ +"glowLeftGone" | +/** + * As `[GLOW]`, but with their left eye missing. If the sprite is facing forwards, then the + * visually leftmost eye should remain. + * + * `[GLOW_RIGHT_GONE]` + */ +"glowRightGone" | +/** + * A child creature is in darkness. Does not have wound states. + * + * `[GLOW_CHILD]` + */ +"glowChild" | +/** + * The sprite for a clutch of eggs. + * + * `[EGG]` + */ +"egg" | +/** + * The default graphic for this vermin. + * + * `[VERMIN]` + */ +"vermin" | +/** + * The alternating graphic for this vermin. Image cycles every 1 second. + * + * `[VERMIN_ALT]` + */ +"verminAlt" | +/** + * For swarming vermin like flies and fairies in small groups. + * + * `[SWARM_SMALL]` + */ +"swarmSmall" | +/** + * For swarming vermin like flies and fairies in medium-sized groups. + * + * `[SWARM_MEDIUM]` + */ +"swarmMedium" | +/** + * For swarming vermin like flies and fairies in large groups. + * + * `[SWARM_LARGE]` + */ +"swarmLarge" | +/** + * Light-producing vermin, for fireflies etc. Does not replace `[VERMIN]`. + * + * `[LIGHT_VERMIN]` + */ +"lightVermin" | +/** + * The alternating graphic for this light-producing vermin. Image cycles every 1 second. + * + * `[LIGHT_VERMIN_ALT]` + */ +"lightVerminAlt" | +/** + * For swarming vermin like flies and fairies in small groups. + * + * `[LIGHT_SWARM_SMALL]` + */ +"lightSwarmSmall" | +/** + * For swarming vermin like flies and fairies in medium-sized groups. + * + * `[LIGHT_SWARM_MEDIUM]` + */ +"lightSwarmMedium" | +/** + * For swarming vermin like flies and fairies in large groups. + * + * `[LIGHT_SWARM_LARGE]` + */ +"lightSwarmLarge" | +/** + * Vermin hives. + * + * `[HIVE]` + */ +"hive" | +/** + * Condition of being not an artifact + */ +"notArtifact" | +/** + * Condition of being a crafted artifact + */ +"craftedArtifact" | +/** + * Condition of being a crop + */ +"crop" | +/** + * Condition of being a seed + */ +"seed" | +/** + * Condition of being a plant (picked) + */ +"picked" | +/** + * Condition of being a shrub + */ +"shrub" | +/** + * Condition of being a sapling + */ +"sapling" | +/** + * Condition of being a crop sprout + */ +"cropSprout" | +/** + * Condition of being a large crop + */ +"cropL" | +/** + * Condition of being a medium crop + */ +"cropM" | +/** + * Condition of being a small crop + */ +"cropR" | +/** + * Condition of being a dead shrub + */ +"shrubDead" | +/** + * Checks if the creature is a child or baby. + * + * `[CONDITION_CHILD]` + */ +"child" | +/** + * Checks if the creature is an adult. + * + * `[CONDITION_NOT_CHILD]` + */ +"notChild" | +/** + * Counts how many items the creature is hauling. Used for `[PACK_ANIMAL]`s in vanilla. + * + * `[CONDITION_HAUL_COUNT_MIN:count]` + */ +"haulCountMin" | +/** + * Counts how many items the creature is hauling. Used for `[PACK_ANIMAL]`s in vanilla. + * + * `[CONDITION_HAUL_COUNT_MAX:count]` + */ +"haulCountMax" | +/** + * Condition of being a class + */ +"class" | +/** + * Defines a body part graphic using standard body token selection criteria. + * + * Selection is done with `BY_TYPE`, `BY_CATEGORY`, or `BY_TOKEN` + * + * `[CONDITION_BP:selection:category, type, or token]` + */ +"bodyPart" | +/** + * Checks if current `[CONDITION_BP]`'s `[BP_APPEARANCE_MODIFIER]` falls within the chosen range. + * + * `[BP_APPEARANCE_MODIFIER_RANGE]` + */ +"bodyPartAppearanceModifierRange" | +/** + * Checks if the current `[CONDITION_BP]` is present and not destroyed, pulped, or severed. Can also be applied to + * `[LG_CONDITION_BP]`. + * + * `[BP_PRESENT]` + */ +"bodyPartPresent" | +/** + * Checks if the current `[CONDITION_BP]` is scarred. Seems to also require `[BP_PRESENT]` to avoid illogical results. + * + * `[BP_SCARRED]` + */ +"bodyPartScarred" | +/** + * True if creature size is greater than defined size. + * + * `[CONDITION_BODY_SIZE_MIN:size]` + */ +"bodySizeMin" | +/** + * True if creature size is less than defined size. + * + * `[CONDITION_BODY_SIZE_MAX:size]` + */ +"bodySizeMax" | +/** + * Changes graphics based on any syndromes the creature is affected by. Vanilla values include: + * - `ZOMBIE` + * - `NECROMANCER` + * - `VAMPCURSE` + * - `RAISED_UNDEAD` + * - `DISTURBED_DEAD` + * - `GHOUL` + * + * `[CONDITION_SYN_CLASS:class]` + */ +"syndromeClass" | +/** + * Selects a tissue layer to use for checking other conditions. + * + * `[CONDITION_TISSUE_LAYER:BY_CATEGORY:ALL:SKIN]` + * + * `[CONDITION_TISSUE_LAYER:BY_CATEGORY:bp category or 'ALL':tissue layer or 'ALL']` + */ +"tissueLayer" | +/** + * Chooses a random layer among layers with a `CONDITION_RANDOM_PART_INDEX` with the same identifier. Index + * is which option this condition is, out of Range number of options. + * + * `[CONDITION_RANDOM_PART_INDEX:HEAD:3:4]` is the third possible random head out of four total options. + * One of these random conditions each will be put into a set of four different sprites to add some random + * variation in the appearance of the creature's head. + * + * `[CONDITION_RANDOM_PART_INDEX:identifier:index:range]` + */ +"randomPartIndex" | +/** + * Checks if the creature is a ghost. + * + * `[CONDITION_GHOST]` + */ +"ghost" | +/** + * Checks the selected tissue's color. Accepts multiple color tokens, and is true if the any of the colors + * is present in the selected tissues. + * + * `[TISSUE_MAY_HAVE_COLOR:color token:more color tokens]` + */ +"tissueMayHaveColor" | +/** + * Checks the current `[CONDITION_TISSUE_LAYER]`'s `LENGTH` appearance modifier. Is true if the `LENGTH` is + * greater than the integer input. + * + * `[TISSUE_MIN_LENGTH:length]` + */ +"tissueMinLength" | +/** + * Checks the current `[CONDITION_TISSUE_LAYER]`'s `LENGTH` appearance modifier. Is true if the `LENGTH` is + * less than the integer input. + * + * `[TISSUE_MAX_LENGTH:length]` + */ +"tissueMaxLength" | +/** + * Checks the current `[CONDITION_TISSUE_LAYER]`'s `DENSITY` appearance modifier. Is true if the `DENSITY` is + * greater than the integer input. + * + * `[TISSUE_MIN_DENSITY:desnsity]` + */ +"tissueMinDensity" | +/** + * Checks the current `[CONDITION_TISSUE_LAYER]`'s `DENSITY` appearance modifier. Is true if the `DENSITY` is + * less than the integer input. + * + * `[TISSUE_MAX_DENSITY:desnsity]` + */ +"tissueMaxDensity" | +/** + * Condition of being a tissue at least so curly + */ +"tissueMinCurly" | +/** + * Condition of being a tissue at most so curly + */ +"tissueMaxCurly" | +/** + * Checks the current `[CONDITION_TISSUE_LAYER]`'s shaping (hairstyle). Valid tokens are + * - `NEATLY_COMBED` + * - `BRAIDED` + * - `DOUBLE_BRAIDS` + * - `PONY_TAILS` + * - `CLEAN_SHAVEN ` + * - `STANDARD_HAIR/BEARD/MOUSTACHE/SIDEBURNS_SHAPINGS` + * + * `[TISSUE_MAY_HAVE_SHAPING:styling token]` + */ +"tissueMayHaveShaping" | +/** + * Checks the current `[CONDITION_TISSUE_LAYER]`'s color. Accepts multiple color tokens, and is true if the + * any of the colors is present in the selected tissues. + * + * `[TISSUE_NOT_SHAPED]` + */ +"tissueNotShaped" | +/** + * Checks if a tissue is sufficiently curly, and if so swaps to display a different image. The new image + * is defined by the tile page ID, x position, and y position. + * + * This condition should be within a `[LAYER:... ]` that has a similar graphic to the on in the `TISSUE_SWAP`. + * The current `[CONDITION_TISSUE_LAYER]` group must also include a `[TISSUE_MIN_LENGTH]`. + * + * `[TISSUE_SWAP:IF_MIN_CURLY:curl amount:tile page id:x pos:y pos]` + */ +"tissueSwap" | +/** + * Condition of being a specific layer (start layer definition) + */ +"layer" | +/** + * Condition of being the upper body + */ +"bodyUpper" | +/** + * Condition of being a copy of a template + */ +"copyOfTemplate" | +/** + * Checks the current [CONDITION_ITEM_WORN]'s quality. 0 is base quality, 5 is masterwork. + * See `[CONDITION_MATERIAL_FLAG:NOT_ARTIFACT]` for non-artifact-quality items. + * + * `[ITEM_QUALITY:quality id]` + */ +"itemQuality" | +/** + * Begins a layer group. Only the first-matching layer in a group will be rendered, so list more + * specific items at the beginning of the layer group and more general items towards the end. + * + * `[LAYER_GROUP]` + */ +"layerGroup" | +/** + * Condition of being a specific layer group set of layers + * Begins defining a layer set for a creature's graphics. + * + * `[LAYER_SET:condition]` + */ +"layerSet" | +/** + * Begins defining a palette for the layer set. Its name can then be referenced by `[USE_PALETTE]`. + * Unlike the palettes used to render all descriptor color tokens, it can be of arbitrary length. + * + * `[LS_PALETTE:name]` + */ +"layerSetPalette" | +/** + * The file name of the 8bit RGBA (sometimes called 32bit) in the /graphics/images folder of the mod, + * such as `images/portraits/dwarf_portrait_body_palette.png`. + * + * `[LS_PALETTE_FILE:file path]` + */ +"layerSetPaletteFile" | +/** + * Defines the default row of a layer set palette, conventionally 0. The exact color values on this row + * will be replaced on layer images with the colors in the same column, based on what row is passed as + * an argument to `[USE_PALETTE]`. + * + * `[LS_PALETTE_DEFAULT:integer]` + */ +"layerSetPaletteDefault" | +/** + * Allows the entire layer group (rather than an individual layer) to be switched on and off depending on the + * conditions of a body part. Should accept the same tokens `[CONDITION_BP]` does. + * + * Selection is done with `BY_TYPE`, `BY_CATEGORY`, or `BY_TOKEN` + * + * `[LG_CONDITION_BP:selection:cateogry, type, or token]` + */ +"layerGroupBodyPart" | +/** + * Explicitly marks the end of a layer group, which allows layers after to not belong to any layer group. + * + * `[END_LAYER_GROUP]` + */ +"endLayerGroup" | +/** + * Defines a clothing or armor graphic by the specific part it is equipped to, the type of armor it is, and the + * internal ID of that item. Additional arguments can be supplied to check for additional subtypes. Valid if any + * matching items are worn. + * + * For example, a condition representing a right handed mitten or glove would be defined as: + * + * `[CONDITION_ITEM_WORN:BY_TOKEN:RH:GLOVES:ITEM_GLOVES_MITTENS]` Also accepts the input `ANY_HELD` or `WIELD` + * (e.g. `WIELD:WEAPON:ANY`), though `ANY_HELD` has been bugged since v50.14. + * + * Selection is done with `BY_CATEGORY` or `BY_TOKEN` + * + * `[CONDITION_ITEM_WORN:selection:cateogry or token:armor type:item id]` + */ +"itemWorn" | +/** + * Causes the current layer to not be rendered if the creature has one of the items worn or equipped. Also accepts + * the input `ANY_HELD` or `WIELD` (e.g. `WIELD:WEAPON:ANY`). Note that `ANY_HELD` has been bugged since v50.14. + * + * Selection is done with `BY_CATEGORY` or `BY_TOKEN` + * + * `[SHUT_OFF_IF_ITEM_PRESENT:selection:cateogry or token:armor type:item id]` + */ +"shutOffIfItemPresent" | +/** + * Displays this layer if the creature is this caste. Only one caste is accepted for each condition, but multiple + * caste conditions can be used in one layer and the layer will be displayed if any of them match. + * + * `[CONDITION_CASTE:caste name]` + */ +"caste" | +/** + * Represents which color the clothing is dyed. Partially-working.v50.15 + * + * Takes a descriptor color. Vanilla dye options: + * + * - MIDNIGHT_BLUE - (Dimple cup) + * - EMERALD - (Blade weed) + * - RED - (Hide root) + * - BLACK - (Sliver barb) + * + * `[CONDITION_DYE:cye color]` + */ +"dye" | +/** + * Checks if the clothing is dyed.v50.15 + * + * `[CONDITION_NOT_DYED]` + */ +"notDyed" | +/** + * Changes graphics based on the material an equipped item is made of. Specifying multiple of this condition for a + * layer uses the "AND" instead of "OR" logical operator, whether placed in the same line or on separate lines. Valid + * material flags are similar to reactant conditions including: + * + * - `WOVEN_ITEM` + * - `ANY_X_MATERIAL` with X being: + * - `PLANT`, `SILK`, `YARN`, `LEATHER`, `WOOD`, `SHELL`, `BONE`, `STONE`, `GEM`, `TOOTH`, `HORN`, `PEARL` + * - `IS_DIVINE_MATERIAL` + * - `NOT_ARTIFACT` + * - `IS_CRAFTED_ARTIFACT` (Note that this token might not have ever worked.) + * - `METAL_ITEM_MATERIAL` + * - `GLASS_MATERIAL` + * - `FIRE_BUILD_SAFE` + * - `MAGMA_BUILD_SAFE` + * - `GROWN_NOT_CRAFTED` + * + * `[CONDITION_MATERIAL_FLAG:flag]` + */ +"materialFlag" | +/** + * Changes graphics based on the material an equipped item is made of. Valid material types are `INORGANIC` or `METAL:IRON` + * where "iron" can be replaced with any weapons-grade metal. General material tokens are not functional. + * + * `[CONDITION_MATERIAL_FLAG]` is a better option for any material condition other than metal. + * + * `[CONDITION_MATERIAL_TYPE:material token]` + */ +"materialType" | +/** + * Colors the layer using that row of either the layer-set-specific `[LS_PALETTE]` or a predefined palette such as `DEFAULT`. + * + * `[USE_PALETTE:layer set palette:row] + */ +"usePalette" | +/** + * Uses the default palette to render the layer based on the color of the current `[CONDITION_ITEM_WORN]`. + * + * `[USE_STANDARD_PALETTE_FROM_ITEM]` + */ +"useStandardPaletteFromItem" | +/** + * Note: This condition is bugged and doesn't work since DFv50.14. + * + * Checks the profession category of the creature to act as a condition. Multiple profession category tokens can be supplied as additional arguments, and will be valid for any of them. You can also use multiple of these tokens instead of listing them all in a single line, but this is functionally identical. Valid Profession tokens which are not categories will be ignored; values that do not match any existing Profession will be treated as NONE and thus apply to doctors, military, etc.. + * + * `[CONDITION_PROFESSION_CATEGORY:prefession tokens (one ore more)] + */ +"professionCategory" | +/** + * Hammerman profession + */ +"hammerman" | +/** + * Master Hammerman profession + */ +"masterHammerman" | +/** + * Spearman profession + */ +"spearman" | +/** + * Master Spearman profession + */ +"masterSpearman" | +/** + * Wrestler profession + */ +"wrestler" | +/** + * Master Wrestler profession + */ +"masterWrestler" | +/** + * Axeman profession + */ +"axeman" | +/** + * Master Axeman profession + */ +"masterAxeman" | +/** + * Swordsman profession + */ +"swordsman" | +/** + * Master Swordsman profession + */ +"masterSwordsman" | +/** + * Maceman profession + */ +"maceman" | +/** + * Master Maceman profession + */ +"masterMaceman" | +/** + * Pikeman profession + */ +"pikeman" | +/** + * Master Pikeman profession + */ +"masterPikeman" | +/** + * Recruit profession + */ +"recruit" | +/** + * Thief profession + */ +"thief" | +/** + * Master Thief profession + */ +"masterThief" | +/** + * Lasher profession + */ +"lasher" | +/** + * Master Lasher profession + */ +"masterLasher" | +/** + * Monster slayer profession + */ +"monsterSlayer" | +/** + * Crossbowman profession + */ +"crossbowman" | +/** + * Master Crossbowman profession + */ +"masterCrossbowman" | +/** + * Bowman profession + */ +"bowman" | +/** + * Master Bowman profession + */ +"masterBowman" | +/** + * Blowgunman profession + */ +"blowgunman" | +/** + * Master Blowgunman profession + */ +"masterBlowgunman" | +/** + * Beat hunter profession + */ +"beastHunter" | +/** + * Scout profession + */ +"scout" | +/** + * Ranger profession + */ +"ranger" | +/** + * Hunter profession + */ +"hunter" | +/** + * Sage profession + */ +"sage" | +/** + * Scholar profession + */ +"scholar" | +/** + * Philosopher profession + */ +"philosopher" | +/** + * Mathematician profession + */ +"mathematician" | +/** + * Historian profession + */ +"historian" | +/** + * Astronomer profession + */ +"astronomer" | +/** + * Naturalist profession + */ +"naturalist" | +/** + * Chemist profession + */ +"chemist" | +/** + * Geographer profession + */ +"geographer" | +/** + * Scribe profession + */ +"scribe" | +/** + * Bookbinder profession + */ +"bookbinder" | +/** + * Performer profession + */ +"performer" | +/** + * Poet profession + */ +"poet" | +/** + * Bard profession + */ +"bard" | +/** + * Dancer profession + */ +"dancer" + +/** + * The `Creature` struct represents a creature in a Dwarf Fortress, with the properties + * that can be set in the raws. Not all the raws are represented here, only the ones that + * are currently supported by the library. + * + * Some items like `CREATURE_VARIATION` and `CREATURE_VARIATION_CASTE` are saved in their raw + * format. `SELECT_CREATURE` is saved here as a sub-creature object with all the properties + * from that raw. This is because the `SELECT_CREATURE` raws are used to create new creatures + * based on the properties of the creature they are applied to. But right now the application + * of those changes is not applied, in order to preserve the original creature. So instead, + * they are saved and can be applied later (at the consumer's discretion). + */ +export type Creature = { +/** + * The `metadata` field is of type `RawMetadata` and is used to provide additional information + * about the raws the `Creature` is found in. + */ +metadata: Metadata | null; +/** + * The `identifier` field is a string that represents the identifier of the creature. It is used + * to uniquely identify the creature (however it is not guaranteed to be unique across object types + * or all raws parsed, *especially* if you are parsing multiple versions of the same raws). + */ +identifier: string; +/** + * The `castes` field is a vector of `Caste` objects. Each `Caste` object represents a caste of the + * creature. For example, a creature may have a `MALE` and `FEMALE` caste. Each `Caste` object has + * its own properties, such as `name`, `description`, `body`, `flags`, etc. + * + * A lot of the properties of the `Creature` object are actually properties of a special `Caste`, `ALL`. + */ +castes: Caste[]; +/** + * Any tags that are not parsed into their own fields are stored in the `tags` field. + */ +tags: CreatureTag[] | null; +/** + * The biomes that this creature can be found in + */ +biomes: BiomeTag[] | null; +/** + * Pref strings are things that make dwarves (or others?) like or dislike the creature. + */ +prefStrings: string[] | null; +/** + * The tile that represents the creature in the game (classic mode) + */ +tile: Tile | null; +/** + * Determines the chances of a creature appearing within its environment, with higher values resulting in more frequent appearance. + * + * Also affects the chance of a creature being brought in a caravan for trading. The game effectively considers all creatures that + * can possibly appear and uses the FREQUENCY value as a weight - for example, if there are three creatures with frequencies 10/25/50, + * the creature with `[FREQUENCY:50]` will appear approximately 58.8% of the time. + * + * Defaults to 50 if not specified. + * + * Minimum value is 0, maximum value is 100. + * + * Note: not to be confused with `[POP_RATIO]`. + */ +frequency: number | null; +/** + * The minimum/maximum numbers of how many creatures per spawned cluster. Vermin fish with this token in combination with + * temperate ocean and river biome tokens will perform seasonal migrations. + * + * Defaults to [1,1] if not specified. + */ +clusterNumber: [number, number] | null; +/** + * The minimum/maximum numbers of how many of these creatures are present in each world map tile of the appropriate region. + * + * Defaults to [1,1] if not specified. + */ +populationNumber: [number, number] | null; +/** + * Depth that the creature appears underground. Numbers can be from 0 to 5. 0 is actually 'above ground' and can be used if the + * creature is to appear both above and below ground. Values from 1-3 are the respective cavern levels, 4 is the magma sea and + * 5 is the HFS. + * + * A single argument may be used instead of min and max. + * + * Civilizations that can use underground plants or animals will only export (via the embark screen or caravans) things that are available at depth 1. + * + * Default [0, 0] (aboveground) + */ +undergroundDepth: [number, number] | null; +/** + * Like `[BABYNAME]`, but applied regardless of caste. + */ +generalBabyName: Name | null; +/** + * Like `[CHILDNAME]`, but applied regardless of caste. + */ +generalChildName: Name | null; +/** + * The generic name for any creature of this type - will be used when distinctions between caste are unimportant. For names for specific castes, + * use `[CASTE_NAME]` instead. If left undefined, the creature will be labeled as "nothing" by the game. + */ +name: Name; +/** + * Copies another specified creature. This will override any definitions made before it; essentially, it makes this creature identical to the other one, + * which can then be modified. Often used in combination with `[APPLY_CREATURE_VARIATION]` to import standard variations from a file. + * + * The vanilla giant animals and animal peoples are examples of this token combination. + */ +copyTagsFrom: string | null; +/** + * Applies the specified creature variation. + * + * These are stored "in the raw", i.e. how they appear in the raws. They are not handled until the end of the parsing process. + */ +applyCreatureVariation: string[] | null; +/** + * A generated field that is used to uniquely identify this object. It is generated from the `metadata`, `identifier`, and `ObjectType`. + * + * This field is always serialized. + */ +objectId: string; +/** + * Various `SELECT_CREATUR` modifications. + */ +selectCreatureVariation: SelectCreature[] | null } + +/** + * A creature effect. + */ +export type CreatureEffect = { severity: number; probability: number; affectedBodyPartsByCategory: string[] | null; affectedBodyPartsByType: string[] | null; affectedBodyPartsByToken: string[] | null; tags: CreatureEffectPropertyTag[] | null; start: number; peak: number; end: number; dwfStretch: number | null } + +/** + * An enum representing a creature effect property tag. + */ +export type CreatureEffectPropertyTag = +/** + * The severity of the effect. Higher values appear to be worse, with SEV:1000 `CE_NECROSIS` causing a part to near-instantly become rotten. + */ +"Severity" | +/** + * The probability of the effect actually manifesting in the victim, as a percentage. 100 means always, 1 means a 1 in 100 chance. + */ +"Probability" | +/** + * (Optional) Determines if the effect can be hindered by the target creature's disease resistance attribute. + * Without this token, disease resistance is ignored. (yes, it's spelled incorrectly) + */ +"Resistible" | +/** + * (Optional) This token presumably causes the severity of the effect to scale with the size of the creature compared + * to the size of the dose of contagion they received, but has yet to be extensively tested. + */ +"SizeDilutes" | +/** + * (Optional) As above, this token has yet to be tested but presumably delays the onset of an effect according to the size of the victim. + */ +"SizeDelays" | +/** + * (Optional; overrides BP tokens) This tag causes an effect to ignore all BP tokens and then forces the game to attempt to apply the effect to + * the limb that came into contact with the contagion - i.e. the part that was bitten by the creature injecting the syndrome material, + * or the one that was splattered by a contact contagion. If an effect can not be applied to the contacted limb (such as `IMPAIR_FUNCTION` on a non-organ) + * then this token makes the effect do nothing. This token also makes inhaled syndromes have no effect. + */ +"Localized" | +/** + * (Optional) This effect only affects tissue layers with the VASCULAR token. + */ +"VascularOnly" | +/** + * (Optional) This effect only affects tissue layers with the MUSCULAR token. + */ +"MuscularOnly" | +/** + * (Optional; overridden by LOCALIZED) Specifies which body parts and tissues the effect is to be applied to. Not every effect requires a target! + * For example, if you wanted to target the lungs of a creature, you would use `BP:BY_CATEGORY:LUNG:ALL`. The effect would act on all body parts + * within the creature with the CATEGORY tag LUNG and affect all tissue layers. For another example, say you wanted to cause the skin to rot off a creature - + * you could use `BP:BY_CATEGORY:ALL:SKIN`, targeting the SKIN tissue on all body parts. Multiple targets can be given in one effect by placing the BP tokens end to end. + * This is one of the most powerful and useful aspects of the syndrome system, as it allows you to selectively target body parts relevant to the contagion, + * like lungs for coal dust inhalation, or the eyes for exposure to an acid gas. + */ +"BodyPart" | +/** + * `BY_CATEGORY:X` to target body parts with a matching `[CATEGORY:X]` body token (or `ALL` to affect everything) + */ +"ByCategory" | +/** + * `BY_TYPE:X` to target body parts having a particular type (`UPPERBODY`, `LOWERBODY`, `HEAD`, `GRASP`, or `STANCE`) + */ +"ByType" | +/** + * `BY_TOKEN:X` to target individual body parts by their ID as specified by the `[BP]` token of the body plan definition. + */ +"ByToken" | +/** + * Determines the time after exposure, in ticks, when the effect starts. Required for all effects. + */ +"Start" | +/** + * (Optional) Determines the time after exposure, in ticks, when the effect reaches its peak intensity. + */ +"Peak" | +/** + * (Optional) Determines the time after exposure, in ticks, when the effect ends. + */ +"End" | +/** + * (Optional) Multiplies the duration values of the effect by the specified amount in Fortress mode. + */ +"DwfStretch" | +/** + * (Optional) Makes the effect begin immediately rather than ramping up. + */ +"AbruptStart" | +/** + * (Optional) Makes the effect end immediately rather than ramping down. + */ +"AbruptEnd" | +/** + * (Optional) Combination of `ABRUPT_START` and `ABRUPT_END`. + */ +"Abrupt" | +/** + * (Optional) Can be hidden by a unit assuming a secret identity, such as a vampire. + */ +"CanBeHidden" | +/** + * Unknown value for default. + */ +"Unknown" + +/** + * An enum representing a creature effect tag. + */ +export type CreatureEffectTag = +/** + * Afflicts the targeted body part with intense pain. If no target is specified this applies to all body parts. + */ +"Pain" | +/** + * Causes the targeted body part to swell up. Extreme swelling may lead to necrosis. + */ +"Swelling" | +/** + * Causes pus to ooze from the afflicted body part. + */ +"Oozing" | +/** + * Causes the targeted body part to undergo bruising. + */ +"Bruising" | +/** + * Covers the targeted body part with blisters. + */ +"Blisters" | +/** + * Causes numbness in the affected body part, blocking pain. Extreme numbness may lead to sensory nerve damage. + * If no target is specified this applies to all body parts. + */ +"Numbness" | +/** + * Causes complete paralysis of the affected body part. Paralysis on a limb may lead to motor nerve damage. + * If no target is specified this causes total paralysis, which can lead to suffocation of smaller creatures. + */ +"Paralysis" | +/** + * Causes the Fever condition. + */ +"Fever" | +/** + * Causes the targeted body part to start bleeding, with heavy enough bleeding resulting in the death of the sufferer. + * Some conditions seem to cause bleeding to be fatal no matter how weak. + */ +"Bleeding" | +/** + * This effect results in the sufferer periodically coughing blood, which stains the tile they're on and requires cleanup. + * It doesn't appear to be lethal, but may cause minor bleeding damage. + */ +"CoughingBlood" | +/** + * This effect results in the sufferer periodically vomiting blood, which stains the tile they're on and requires cleanup. + * It doesn't appear to be lethal, but may cause minor bleeding damage. + */ +"VomitingBlood" | +/** + * Causes the Nausea condition, and heavy vomiting. Can eventually lead to dehydration and death. + */ +"Nausea" | +/** + * Renders the creature unconscious. + */ +"Unconsciousness" | +/** + * Causes the targeted body part to rot, with associated tissue damage, miasma emission and bleeding. + * The victim slowly bleeds to death if the wound is not treated. Badly necrotic limbs will require amputation. + */ +"Necrosis" | +/** + * An organ afflicted with this effect is rendered inoperable. + * E.g., if both lungs are impaired the creature can't breathe and will suffocate. This token only affects organs, not limbs. + */ +"ImpairFunction" | +/** + * Causes the Drowsiness condition + */ +"Drowsiness" | +/** + * Inflicts the Dizziness condition, occasional fainting and a general slowdown in movement and work speed. + */ +"Dizziness" | +/** + * Decreases the severity of pain produced by wounds or syndrome effects on the targeted body part. + * The SEV value probably controls by how much the pain is decreased. + */ +"ReducePain" | +/** + * Decreases the severity of swelling on the targeted body part. + */ +"ReduceSwelling" | +/** + * Decreases the severity of any paralysis effects on the targeted body part. + */ +"ReduceParalysis" | +/** + * Decreases the severity of any dizziness the creature has. + */ +"ReduceDizziness" | +/** + * Decreases the severity of any nausea the creature has. + */ +"ReduceNausea" | +/** + * Decreases the severity of any fever the creature has. + */ +"ReduceFever" | +/** + * Decreases the severity of the bleeding of any wounds or syndrome effects on the targeted body part. + * The SEV value probably controls by how much the bleeding is decreased. + */ +"StopBleeding" | +/** + * Closes any wounds on the targeted body part with speed depending on the SEV value. + */ +"CloseOpenWounds" | +/** + * Probably decreases the severity of the infection from infected wounds over time. + */ +"CureInfection" | +/** + * Heals the tissues of the targeted body part with speed depending on the SEV value. + */ +"HealTissues" | +/** + * Heals the nerves of the targeted body part with speed depending on the SEV value. + */ +"HealNerves" | +/** + * Causes missing body parts to regrow. SEV controls how quickly body parts are regrown. + */ +"RegrowParts" | +/** + * Add a tag + */ +"AddTag" | +/** + * Remove a tag + */ +"RemoveTag" | +/** + * Display name of the effect + */ +"DisplayName" | +/** + * Display tile of the effect + */ +"DisplayTile" | +/** + * Whether the tile flashes + */ +"FlashTile" | +/** + * Physical attribute change + */ +"PhysAttChange" | +/** + * Mental attribute change + */ +"MentAttChange" | +/** + * Speed change + */ +"SpeedChange" | +/** + * Skill roll adjustment + */ +"SkillRollAdjust" | +/** + * Body appearance modifier + */ +"BodyAppearanceModifier" | +/** + * Body part appearance modifier + */ +"BodyPartAppearanceModifier" | +/** + * Body transformation + */ +"BodyTransformation" | +/** + * Material force multiplier + */ +"MaterialForceMultiplier" | +/** + * Can do an interaction + */ +"CanDoInteraction" | +/** + * Can do a special attack interaction + */ +"SpecialAttackInteraction" | +/** + * Can do a body mat interaction + */ +"BodyMatInteraction" | +/** + * Can sense creatures of a class + */ +"SenseCreatureClass" | +/** + * Feel emotion + */ +"FeelEmotion" | +/** + * Changes the personality of the creature + */ +"ChangePersonality" | +/** + * Erratic behavior + */ +"ErraticBehavior" | +/** + * Unknown + */ +"Unknown" + +/** + * An enum representing a creature tag. + */ +export type CreatureTag = +/** + * If set, the creature will blink between its `[Tile]` and its `[AltTile]`. + * + * Arguments: + * + * - the 'character' or tile number + * + * Appears as `ALTTILE:123` + */ +{ AltTile: { +/** + * The character or tile number + */ +character: TileCharacter } } | +/** + * Applies the specified creature variation with the given arguments to the creature. See `[ApplyCreatureVariation]` for more information. + * + * Appears as `APPLY_CREATURE_VARIATION:SOME_VARIATION` or `APPLY_CREATURE_VARIATION:SOME_VARIATION:ARG1:ARG2:ARG3` + */ +{ ApplyCreatureVariation: { +/** + * Creature variation ID to apply + */ +id: string; +/** + * (Optional) any number of arguments to pass to the creature variation + */ +args: string[] } } | +/** + * Applies the effects of all pending `[CV_ADD_TAG]` and `[CV_REMOVE_TAG]` tokens that have been defined in the current creature (so far). + * + * Appears as `APPLY_CURRENT_CREATURE_VARIATION` + */ +"ApplyCurrentCreatureVariation" | +/** + * Enables the creature to be kept in artificial hives by beekeepers. + * + * Appears as `ARTIFICIAL_HIVEABLE` + */ +"ArtificialHiveable" | +/** + * Select a biome the creature may appear in. + * + * Appears as `BIOME:SomeBiomeId` + */ +{ Biome: { +/** + * Biome identifier + */ +id: string } } | +/** + * Defines a caste + */ +{ Caste: { +/** + * The name of the caste + */ +name: string } } | +/** + * Multiplies frequency by a factor of (integer)%. + * + * Appears as `CHANGE_FREQUENCY_PERC:100` + */ +{ ChangeFrequencyPercent: { +/** + * The percentage to change the frequency by + */ +percent: number } } | +/** + * The minimum/maximum numbers of how many creatures per spawned cluster. Vermin fish with this token in + * combination with temperate ocean and river biome tokens will perform seasonal migrations. + * + * Defaults to 1:1 if not specified. + * + * Appears as `CLUSTER_NUMBER:1:1` + */ +{ ClusterNumber: { +/** + * The minimum number of creatures per spawned cluster + */ +min: number; +/** + * The maximum number of creatures per spawned cluster + */ +max: number } } | +/** + * Copies another specified creature. This will override any definitions made before it; essentially, it makes this creature identical to the other one, which can then + * be modified. Often used in combination with `[APPLY_CREATURE_VARIATION]` to import standard variations from a file. The vanilla giant animals and animal peoples are + * examples of this token combination. + * + * Arguments: + * + * * `creature`: The identifier of the creature to copy + * + * Appears as `COPY_TAGS_FROM:SomeCreature` + */ +{ CopyTagsFrom: { +/** + * The identifier of the creature to copy + */ +creature: string } } | +/** + * Creatures active in their civilization's military will use this tile instead. + * + * Appears as `CREATURE_SOLDIER_TILE:123` + */ +{ CreatureSoldierTile: { +/** + * The character or tile number + */ +character: TileCharacter } } | +/** + * The symbol of the creature in ASCII mode. + * + * Appears as `CREATURE_TILE:123` + */ +{ CreatureTile: { +/** + * The character or tile number + */ +character: TileCharacter } } | +/** + * The color of the creature's tile. + * + * Arguments: + * + * * `foreground`: The foreground color + * * `background`: The background color + * * `brightness`: The brightness of the color + * + * Appears as `COLOR:0:0:0` + */ +{ Color: { +/** + * The foreground color + */ +foreground: number; +/** + * The background color + */ +background: number; +/** + * The brightness of the color + */ +brightness: number } } | +/** + * Adding this token to a creature prevents it from appearing in generated worlds (unless it's marked as always present for a particular + * civilization). For example, adding it to dogs will lead to worlds being generated without dogs in them. Also removes the creature from the + * object testing arena's spawn list. If combined with [Fanciful], artistic depictions of the creature will occur regardless. Used by centaurs, + * chimeras and griffons in the vanilla game. + * + * Appears as `DOES_NOT_EXIST` + */ +"DoesNotExist" | +/** + * Makes the creature appear as a large 3x3 wagon responsible for carrying trade goods, pulled by two `[WAGON_PULLER]` creatures and driven by a merchant. + * + * Appears as `EQUIPMENT_WAGON` + */ +"EquipmentWagon" | +/** + * The creature is considered evil and will only show up in evil biomes. Civilizations with `[EntityToken::UseEvilAnimals]` can domesticate them + * regardless of exotic status. Has no effect on cavern creatures except to restrict taming. A civilization with evil creatures can colonize evil areas. + * + * Appears as `EVIL` + */ +"Evil" | +/** + * The creature is a thing of legend and known to all civilizations. Its materials cannot be requested or preferred. The tag also adds some art value modifiers. + * Used by a number of creatures. Conflicts with `[CasteToken::CommonDomestic]`. + */ +"Fanciful" | +/** + * Determines the chances of a creature appearing within its environment, with higher values resulting in more frequent appearance. Also affects the chance of a + * creature being brought in a caravan for trading. The game effectively considers all creatures that can possibly appear and uses the FREQUENCY value as a weight + * + * For example, if there are three creatures with frequencies 10/25/50, the creature with [FREQUENCY:50] will appear approximately 58.8% of the time. + * + * Defaults to 50 if not specified. Not to be confused with `[PopulationRatio]`. + * + * Appears as `FREQUENCY:50` + */ +{ Frequency: { +/** + * The frequency of the creature, a number between 0 and 100 (inclusive) + */ +frequency: number } } | +/** + * Name of the creatures baby form. Applies to all castes but can be overridden by `[CasteToken::BabyName]`. + * + * Appears as `GENERAL_BABY_NAME:BabyName:BabyNames` + */ +{ GeneralBabyName: { +/** + * The name of the baby + */ +singular: string; +/** + * The plural name of the baby + */ +plural: string } } | +/** + * Name of the creatures child form. Applies to all castes but can be overridden by `[CasteToken::ChildName]`. + * + * Appears as `GENERAL_CHILD_NAME:ChildName:ChildNames` + */ +{ GeneralChildName: { +/** + * The name of the child + */ +singular: string; +/** + * The plural name of the child + */ +plural: string } } | +/** + * Found on procedurally generated creatures like forgotten beasts, titans, demons, angels, and night creatures. Cannot be specified in user-defined raws. + * + * Appears as `GENERATED` + */ +"Generated" | +/** + * The color of the creature's glow tile. + * + * Arguments: + * + * * `foreground`: The foreground color + * * `background`: The background color + * * `brightness`: The brightness of the color + * + * Appears as `GLOWCOLOR:0:0:0` + */ +{ GlowColor: { +/** + * The foreground color + */ +foreground: number; +/** + * The background color + */ +background: number; +/** + * The brightness of the color + */ +brightness: number } } | +/** + * The creature's tile when it is glowing. + * + * Arguments: + * + * * `character`: The character or tile number + * + * Appears as `GLOWTILE:123` + */ +{ GlowTile: { +/** + * The character or tile number + */ +character: TileCharacter } } | +/** + * Creature is considered good and will only show up in good biomes - unicorns, for example. Civilizations with `[EntityToken::UseGoodAnimals]` can + * domesticate them regardless of exotic status. Has no effect on cavern creatures except to restrict taming. A civilization that has good + * creatures can colonize good areas in world-gen. + * + * Appears as `GOOD` + */ +"Good" | +/** + * When using tags from an existing creature, inserts new tags at the end of the creature. + * + * Appears as `GO_TO_END` + */ +"GoToEnd" | +/** + * When using tags from an existing creature, inserts new tags at the beginning of the creature. + * + * Appears as `GO_TO_START` + */ +"GoToStart" | +/** + * When using tags from an existing creature, inserts new tags after the specified tag. + * + * Arguments: + * + * * `tag`: The tag to insert after + * + * Appears as `GO_TO_TAG:TAG` + */ +{ GoToTag: { +/** + * The tag to insert after + */ +tag: string } } | +/** + * What product is harvested from beekeeping. + * + * Arguments: + * + * * `number`: The number of products harvested + * * `time`: The time it takes before the next harvest + * * `item tokens`: The item tokens that are harvested (some arbitrary list of items) + * + * Appears as `HARVEST_PRODUCT:1:1:ITEM_TOKENS` + */ +{ HarvestProduct: { +/** + * The number of products harvested + */ +number: number; +/** + * The time it takes before the next harvest + */ +time: number; +/** + * The item tokens that are harvested (some arbitrary list of items) + */ +item_tokens: string[] } } | +/** + * This is the core requisite tag allowing the creature to spawn as a wild animal in the appropriate biomes. Requires specifying a [Biome] in which the creature will spawn. + * Does not require specifying a frequency, population number, or cluster number. + * + * This tag stacks with `[CasteToken::Megabeast]`, `[CasteToken::SemiMegabeast]`, or `[CasteToken::NightCreatureHunter]`; if used with one of these tags, the creature will spawn + * as both a boss and as a wild animal. This tag does not stack with `[CasteToken::FeatureBeast]` and if both are used the creature will not spawn. This tag is unaffected by + * `[CasteToken::Demon]`. + * + * Appears as `LARGE_ROAMING` + */ +"LargeRoaming" | +/** + * Allows you to play as a wild animal of this species in adventurer mode. Prevents trading of (tame) instances of this creature in caravans. + * + * Appears as `LOCAL_POPS_CONTROLLABLE` + */ +"LocalPopsControllable" | +/** + * Wild animals of this species may occasionally join a civilization. Prevents trading of (tame) instances of this creature in caravans. + * + * Appears as `LOCAL_POPS_PRODUCE_HEROES` + */ +"LocalPopsProduceHeroes" | +/** + * The creatures will scatter if they have this tag, or form tight packs if they don't. + * + * Appears as `LOOSE_CLUSTERS` + */ +"LooseClusters" | +/** + * Marks if the creature is an actual real-life creature. Only used for age-names at present. + */ +"Mundane" | +/** + * The generic name for any creature of this type - will be used when distinctions between caste are unimportant. For names for specific castes, use `[CASTE_NAME]` instead. + * If left undefined, the creature will be labeled as "nothing" by the game. + * + * Appears as `NAME:Name:Names:NameAdj` + */ +{ Name: { +/** + * The name of the creature + */ +name: string; +/** + * The plural name of the creature + */ +plural_name: string; +/** + * The adjective form of the creature's name + */ +adjective: string } } | +/** + * Adds a material to selected materials. Used immediately after `[SELECT_MATERIAL]`. + * + * Appears as `PLUS_MATERIAL:Material` + */ +{ PlusMaterial: { +/** + * The material to add + */ +material: string } } | +/** + * The minimum/maximum numbers of how many of these creatures are present in each world map tile of the appropriate region. Defaults to 1:1 if not specified. + * + * Appears as `POPULATION_NUMBER:1:1` + */ +{ PopulationNumber: { +/** + * The minimum number of creatures per spawned cluster + */ +min: number; +/** + * The maximum number of creatures per spawned cluster + */ +max: number } } | +/** + * Sets what other creatures prefer about this creature. + * + * "Urist likes dwarves for their beards." + * + * Multiple entries will be chosen from at random. Creatures lacking a PREFSTRING token will never appear under another's preferences. + * + * Appears as `PREFSTRING:PrefString` + */ +{ PrefString: { +/** + * The preference string + */ +pref_string: string } } | +/** + * The generic name for members of this profession, at the creature level. In order to give members of specific castes different names for professions, + * use `[CASTE_PROFESSION_NAME]` instead. + * + * Appears as `PROFESSION_NAME:ProfessionId:ProfessionName:ProfessionNames` + */ +{ ProfessionName: { +/** + * The profession id + */ +id: string; +/** + * The name of the profession + */ +name: string; +/** + * The plural name of the profession + */ +plural_name: string } } | +/** + * Removes a material from the creature. + * + * Appears as `REMOVE_MATERIAL:Material` + */ +{ RemoveMaterial: { +/** + * The material to remove + */ +material: string } } | +/** + * Removes a tissue from the creature. + * + * Appears as `REMOVE_TISSUE:Tissue` + */ +{ RemoveTissue: { +/** + * The tissue to remove + */ +tissue: string } } | +/** + * The creature will only show up in "savage" biomes. Has no effect on cavern creatures. Cannot be combined with [GOOD] or [EVIL]. + * + * Appears as `SAVAGE` + */ +"Savage" | +/** + * Adds an additional previously defined caste to the selection. Used after `[SELECT_CASTE]`. + * + * Appears as `SELECT_ADDITIONAL_CASTE:Caste` + */ +{ SelectAdditionalCaste: { +/** + * The caste to add + */ +caste: string } } | +/** + * Selects a previously defined caste + * + * Appears as `SELECT_CASTE:Caste` + */ +{ SelectCaste: { +/** + * The caste to select + */ +caste: string } } | +/** + * Selects a locally defined material. Can be ALL. + * + * Appears as `SELECT_MATERIAL:Material` + */ +{ SelectMaterial: { +/** + * The material to select + */ +material: string } } | +/** + * Selects a tissue for editing. + * + * Appears as `SELECT_TISSUE:Tissue` + */ +{ SelectTissue: { +/** + * The tissue to select + */ +tissue: string } } | +/** + * Boasting speeches relating to killing this creature. Examples include `text_dwarf.txt` and `text_elf.txt` in `data\vanilla\vanilla_creatures\objects`. + * + * Appears as `SLAIN_CASTE:SomeSpeechSet` + */ +{ SlainSpeech: { +/** + * The speech set to use + */ +slain_speech: string } } | +/** + * Determines how keen a creature's sense of smell is - lower is better. At 10000, a creature cannot smell at all. + * + * Appears as `SMELL_TRIGGER:10000` + */ +{ SmellTrigger: { +/** + * The smell trigger + */ +smell_trigger: number } } | +/** + * If this creature is active in its civilization's military, it will blink between its default tile and this one. + * + * Appears as `SOLDIER_ALTTILE:SomeTile` + */ +{ SoldierAltTile: { +/** + * The tile to use + */ +tile: string } } | +/** + * Found on generated angels. This is the historical figure ID of the deity with which the angel is associated. Since HFIDs are not predictable before worldgen, + * this isn't terribly usable in mods. + * + * Appears as `SOURCE_HFID:123` + */ +{ SourceHfid: { +/** + * The historical figure ID + */ +hfid: number } } | +/** + * Sets what religious spheres the creature is aligned to, for purposes of being worshipped via the [POWER] token. Also affects the layout of hidden fun stuff, + * and the creature's name. + * + * Appears as `SPHERE:SomeSphere` + */ +{ Sphere: { +/** + * The sphere to use + */ +sphere: string } } | +/** + * Begins defining a tissue in the creature file. Follow this with standard tissue definition tokens to define the tissue properties. + * + * Arguments: + * + * * `name`: The name of the tissue + * + * Appears as `TISSUE:SomeTissue` + */ +{ Tissue: { +/** + * The name of the tissue + */ +name: string } } | +/** + * A large swarm of vermin can be disturbed, usually in adventurer mode. + * + * Appears as `TRIGGERABLE_GROUP:5:10` + */ +{ TriggerableGroup: { +/** + * The minimum number of vermin in the swarm + */ +min: number; +/** + * The maximum number of vermin in the swarm + */ +max: number } } | +/** + * Creature will occur in every region with the correct biome. Does not apply to [EVIL]/[GOOD] tags. + * + * Appears as `UBIQUITOUS` + */ +"Ubiquitous" | +/** + * Depth that the creature appears underground. Numbers can be from 0 to 5. 0 is actually 'above ground' and can be used if the creature is to appear both above and below ground. + * Values from 1-3 are the respective cavern levels, 4 is the magma sea and 5 is the HFS. A single argument may be used instead of min and max. Demons use only 5:5; + * user-defined creatures with both this depth and [FLIER] will take part in the initial wave from the HFS alongside generated demons, but without [FLIER] they will only spawn from + * the map edges. Civilizations that can use underground plants or animals will only export (via the embark screen or caravans) things that are available at depth 1. + * + * Arguments: + * + * * `min`: The minimum depth + * * `max`: The maximum depth + * + * Appears as `UNDERGROUND_DEPTH:1:3` + */ +{ UndergroundDepth: { +/** + * The minimum depth + */ +min: number; +/** + * The maximum depth + */ +max: number } } | +/** + * Defines a new caste derived directly from a previous caste. The new caste inherits all properties of the old one. The effect of this tag is automatic if one has not yet defined any castes: + * "Any caste-level tag that occurs before castes are explicitly declared is saved up and placed on any caste that is declared later, unless the caste is explicitly derived from another caste." + * + * "When DF detects duplicate tokens in the raws of the same object, a failsafe seems to kick in; it takes the bottom-most of the duplicates, and disregards the others. In the case of tokens + * added by a mod, it prioritizes the duplicate in the mod." This means that if a tag is defined in the base-caste and redefined in the derived caste, the derived tag overwrites the base tag. + * + * Arguments: + * + * * `caste`: The name of the new caste + * * `original_caste`: The name of the original caste to copy + * + * Appears as `USE_CASTE:SomeCaste:SomeOriginalCaste` + */ +{ UseCaste: { +/** + * The name of the new caste + */ +caste: string; +/** + * The name of the original caste to copy + */ +original_caste: string } } | +/** + * Defines a new local creature material and populates it with all properties defined in the specified local creature material. + * + * Arguments: + * + * * `material`: The name of the new material + * * `original_material`: The name of the original material to copy + * + * Appears as `USE_MATERIAL:SomeMaterial:SomeOriginalMaterial` + */ +{ UseMaterial: { +/** + * The name of the new material + */ +material: string; +/** + * The name of the original material to copy + */ +original_material: string } } | +/** + * Defines a new local creature material and populates it with all properties defined in the specified template. There seems to be a limit of 200 materials per creature. + * + * Arguments: + * + * * `material`: The name of the new material + * * `template`: The name of the template to copy + * + * Appears as `USE_MATERIAL_TEMPLATE:SomeMaterial:SomeTemplate` + */ +{ UseMaterialTemplate: { +/** + * The name of the new material + */ +material: string; +/** + * The name of the template to copy + */ +template: string } } | +/** + * Defines a new local creature tissue and populates it with all properties defined in the local tissue specified in the second argument. + * + * Arguments: + * + * * `tissue`: The name of the new tissue + * * `original_tissue`: The name of the original tissue to copy + * + * Appears as `USE_TISSUE:SomeTissue:SomeOriginalTissue` + */ +{ UseTissue: { +/** + * The name of the new tissue + */ +tissue: string; +/** + * The name of the original tissue to copy + */ +original_tissue: string } } | +/** + * Loads a tissue template listed in `OBJECT:TISSUE_TEMPLATE` files, such as `tissue_template_default.txt`. + * + * Arguments: + * + * * `tissue`: The name of the new tissue + * * `template`: The name of the template to copy + * + * Appears as `USE_TISSUE_TEMPLATE:SomeTissue:SomeTemplate` + */ +{ UseTissueTemplate: { +/** + * The name of the new tissue + */ +tissue: string; +/** + * The name of the template to copy + */ +template: string } } | +/** + * Changes the language of the creature into unintelligible 'kobold-speak', which creatures of other species will be unable to understand. If a civilized creature has this and is not + * part of a [SKULKING] civ, it will tend to start wars with all nearby civilizations and will be unable to make peace treaties due to 'inability to communicate'. + * + * Appears as `UTTERNANCES` + */ +"Utterances" | +/** + * The vermin creature will attempt to eat exposed food. See `[PENETRATEPOWER]`. Distinct from `[VERMIN_ROTTER]`. + * + * Appears as `VERMIN_EATER` + */ +"VerminEater" | +/** + * The vermin appears in water and will attempt to swim around. + * + * Appears as `VERMIN_FISH` + */ +"VerminFish" | +/** + * The creature appears in "general" surface ground locations. Note that this doesn't stop the creature from flying if it can (most vermin birds have this tag). + * + * Appears as `VERMIN_GROUNDER` + */ +"VerminGrounder" | +/** + * The vermin are attracted to rotting stuff and loose food left in the open and cause unhappy thoughts to dwarves who encounter them. Present on flies, knuckle worms, + * acorn flies, and blood gnats. Speeds up decay? + * + * Appears as `VERMIN_ROTTER` + */ +"VerminRotter" | +/** + * The creature randomly appears near dirt or mud, and may be uncovered by creatures that have the `[ROOT_AROUND]` interaction such as geese and chickens. + * Dwarves will ignore the creature when given the "Capture live land animal" task. + * + * Appears as `VERMIN_SOIL` + */ +"VerminSoil" | +/** + * The vermin will appear in a single tile cluster of many vermin, such as a colony of ants. + * + * Appears as `VERMIN_SOIL_COLONY` + */ +"VerminSoilColony" | +/** + * An unknown tag. + */ +"Unknown" | +/** + * Mates to breed + */ +"MatesToBreed" | +/** + * Has two genders + */ +"TwoGenders" | +/** + * All castes are alive + */ +"AllCastesAlive" | +/** + * Is a small race + */ +"SmallRace" | +/** + * Occurs as an entity + */ +"OccursAsEntityRace" | +/** + * Equipment used + */ +"Equipment" + +/** + * A creature variation. + */ +export type CreatureVariation = { +/** + * Common Raw file Things + */ +metadata: Metadata | null; identifier: string; objectId: string; +/** + * Creature variations are basically just a set of simple tag actions which are applied to + * the creature which is being modified. The tags are applied in order EXCEPT for the convert + * tags which are applied in a reverse order. + */ +rules: CreatureVariationRuleTag[]; +/** + * A creature variation can define any number of arguments which can be used in the rules. + * These arguments replace instances of `!ARGn` in the rules. Use `apply_arguments` to apply + * a set of arguments to a creature variation (and get a very specific variation back). Use + * `apply_to_creature` to apply the variation to a creature (it also takes arguments and will + * apply them to the variation before applying the variation to the creature). + */ +argumentCount: string } + +/** + * A variation rule for a creature. + */ +export type CreatureVariationRuleTag = +/** + * An unknown rule. + */ +"Unknown" | +/** + * Removes a tag from a creature. + */ +{ RemoveTag: { +/** + * The tag to remove. + */ +tag: string; +/** + * The value to remove. + */ +value: string | null } } | +/** + * Adds a new tag to a creature. + */ +{ NewTag: { +/** + * The tag to add. + */ +tag: string; +/** + * The value to add. + */ +value: string | null } } | +/** + * Adds a new tag to a creature. + */ +{ AddTag: { +/** + * The tag to add. + */ +tag: string; +/** + * The value to add. + */ +value: string | null } } | +/** + * Converts a tag on a creature. + */ +{ ConvertTag: { +/** + * The tag to convert. + */ +tag: string; +/** + * The target value to convert. + */ +target: string | null; +/** + * The replacement value to convert to. + */ +replacement: string | null } } | +/** + * Adds a new tag to a creature if a condition is met. + */ +{ ConditionalNewTag: { +/** + * The tag to add. + */ +tag: string; +/** + * The value to add. + */ +value: string | null; +/** + * The index of the argument to check. + */ +argument_index: string; +/** + * The requirement for the argument. + */ +argument_requirement: string } } | +/** + * Adds a new tag to a creature if a condition is met. + */ +{ ConditionalAddTag: { +/** + * The tag to add. + */ +tag: string; +/** + * The value to add. + */ +value: string | null; +/** + * The index of the argument to check. + */ +argument_index: string; +/** + * The requirement for the argument. + */ +argument_requirement: string } } | +/** + * Removes a tag from a creature if a condition is met. + */ +{ ConditionalRemoveTag: { +/** + * The tag to remove. + */ +tag: string; +/** + * The value to remove. + */ +value: string | null; +/** + * The index of the argument to check. + */ +argument_index: string; +/** + * The requirement for the argument. + */ +argument_requirement: string } } | +/** + * Converts a tag on a creature if a condition is met. + */ +{ ConditionalConvertTag: { +/** + * The tag to convert. + */ +tag: string; +/** + * The target value to convert. + */ +target: string | null; +/** + * The replacement value to convert to. + */ +replacement: string | null; +/** + * The index of the argument to check. + */ +argument_index: string; +/** + * The requirement for the argument. + */ +argument_requirement: string } } + +/** + * An enum representing a creature variation tag. + */ +export type CreatureVariationTag = +/** + * A tag to add a new tag to the creature. + */ +"NewTag" | +/** + * A tag to add a tag to the creature. + */ +"AddTag" | +/** + * A tag to remove a tag from the creature. + */ +"RemoveTag" | +/** + * A tag to convert a tag to a new tag. + */ +"ConvertTag" | +/** + * A tag to convert a tag to a new tag with specific token + */ +"ConvertTagMaster" | +/** + * A tag to convert a tag to a new tag with specific target + */ +"ConvertTagTarget" | +/** + * A tag to convert a tag to a new tag with specific replacement + */ +"ConvertTagReplacement" | +/** + * Conditionally add a new tag to the creature. + */ +"ConditionalNewTag" | +/** + * Conditionally add a tag to the creature. + */ +"ConditionalAddTag" | +/** + * Conditionally remove a tag from the creature. + */ +"ConditionalRemoveTag" | +/** + * Conditionally convert a tag to a new tag. + */ +"ConditionalConvertTag" | +/** + * An unknown tag. + */ +"Unknown" + +/** + * A custom graphic extension. + */ +export type CustomGraphicExtension = { extensionType: GraphicTypeTag; tilePageId: string | null; value1: number | null; value2: number | null } + +/** + * A struct representing a Dimensions object. + */ +export type Dimensions = { +/** + * The x coordinate + */ +x: number; +/** + * The y coordinate + */ +y: number } + +/** + * A struct representing an Entity object. + */ +export type Entity = { metadata: Metadata | null; identifier: string; objectId: string; tags: EntityTag[]; creature: string | null; translation: string | null; exclusiveStartBiome: string | null; biomeSupport: ([string, number])[] | null; settlementBiome: string[] | null; startBiome: string[] | null; likesSites: string[] | null; toleratesSites: string[] | null; worldConstructions: string[] | null; maxPopNumber: number | null; maxSitePopNumber: number | null; maxStartingCivNumber: number | null; permittedBuildings: string[] | null; permittedJobs: string[] | null; permittedReactions: string[] | null; currency: ([string, number])[] | null; artFacetModifier: ([string, number])[] | null; artImageElementModifier: ([string, number])[] | null; itemImprovementModifier: ([string, number])[] | null; selectSymbols: ([string, string])[] | null; subselectSymbols: ([string, string])[] | null; cullSymbols: ([string, string])[] | null; friendlyColor: Color | null; religion: string | null; religionSpheres: string[] | null; sphereAlignments: string[] | null; positions: Position[] | null; landHolderTrigger: string | null; siteVariablePositions: string[] | null; variablePositions: string[] | null; ethics: ([string, string])[] | null; values: ([string, number])[] | null; variableValues: ([string, number, number])[] | null; activeSeason: string | null; banditry: number | null; progressTriggerPopulation: number | null; progressTriggerProduction: number | null; progressTriggerTrade: number | null; progressTriggerPopulationSiege: number | null; progressTriggerProductionSiege: number | null; progressTriggerTradeSiege: number | null; scholars: string[] | null; ammo: string[] | null; armors: ([string, number])[] | null; diggers: string[] | null; gloves: ([string, number])[] | null; helms: ([string, number])[] | null; instrument: string[] | null; pants: ([string, number])[] | null; shields: string[] | null; shoes: ([string, number])[] | null; siegeAmmo: string[] | null; tool: string[] | null; toys: string[] | null; trapComponents: string[] | null; weapons: string[] | null; gemShape: string[] | null; stoneShape: string[] | null; sourceHfid: number | null } + +/** + * Tokens that can be found in an entity raw file. + */ +export type EntityTag = +/** + * Allows adventure mode for entities with sites. + */ +"AllMainPopsControllable" | +/** + * Allows fortress mode. If multiple entities have the `SITE_CONTROLLABLE` token, then at embark the specific civs can be chosen on + * the civ list screen. At least one civilization must have this token. + */ +"SiteControllable" | +/** + * Arguments: creature + * + * The type of creature that will inhabit the civilization. If multiple creature types are specified, each civilization will randomly + * choose one of the creatures. In entities with multiple possible creatures, you can manipulate the chance of one creature being + * chosen by adding multiple identical creature tags. For instance adding `[CREATURE:DWARF][CREATURE:DWARF][CREATURE:DWARF][CREATURE:ELF]` + * to the same entity will make the civs created about 75% dwarven, 25% elven. It should be noted that civilizations are in general + * weighted by this token. + * + * For example, if you have one entity with three `[CREATURE:DWARF]` entries and another separate entity with a single `[CREATURE:ELF]` entry, + * then you can expect to see three times as many of the former placed as the latter. + */ +"Creature" | +/** + * Arguments: number (integer) + * + * Found on generated angel entities. Appears to draw from creatures with this HFID, which associates the entity with a historical + * figure of the same ID corresponding to a deity. + */ +"SourceHfid" | +/** + * Arguments: biome, frequency + * + * Controls the expansion of the civilization's territory. The higher the number is relative to other `BIOME_SUPPORT` tokens in the entity, + * the faster it can spread through the biome. These numbers are evaluated relative to each other, i.e. if one biome is 1 and the other is 2, + * the spread will be the same as if one was 100 and the other was 200. Civs can spread out over biomes they cannot actually build in; + * + * For example, humans spread quickly over oceans but cannot actually build in them. + * + * e.g. `[BIOME_SUPPORT:ANY_GRASSLAND:4]` + */ +"BiomeSupport" | +/** + * Arguments: biome + * + * If the civ's territory crosses over this biome, it can build settlements here. + * + * e.g. `[SETTLEMENT_BIOME:ANY_GRASSLAND]` + */ +"SettlementBiome" | +/** + * Arguments: biome + * + * Combination of `EXCLUSIVE_START_BIOME` and `SETTLEMENT_BIOME`; allows the civ to start in and create settlements in the biome. + * + * e.g. `[START_BIOME:ANY_FOREST]` + */ +"StartBiome" | +/** + * Arguments: biome + * + * The birth of the civilization can occur in this biome, but cannot (necessarily) build in it. + * If the civ does not have `SETTLEMENT_BIOME` or `START_BIOME` for the biome in question, it will only construct a single settlement there. + * + * e.g. `[EXCLUSIVE_START_BIOME:MOUNTAIN]` + */ +"ExclusiveStartBiome" | +/** + * Arguments: site type + * + * Valid site types are `DARK_FORTRESS` (π), `CAVE` (•), `CAVE_DETAILED` (Ω), `TREE_CITY` (î), and `CITY` (#). + * Also recognizes `PLAYER_FORTRESS` (creates a civ of hillocks only), and `MONUMENT` (creates a civ without visible sites + * (except tombs and castles), but may cause worldgen crashes). `FORTRESS` is no longer a valid entry, castles are + * currently controlled by `BUILDS_OUTDOOR_FORTIFICATIONS`. Defaults to `CITY`. Selecting `CAVE` causes the classic kobold behavior + * of not showing up on the "neighbors" section of the site selection screen. Selecting `DARK_FORTRESS` also allows generation + * of certain other structures. It also gives the civ a special overlord. + * + * `CAVE_DETAILED` civilizations will create fortresses in mountainous regions and hillocks in non-mountainous regions. + * + * e.g. `[DEFAULT_SITE_TYPE:CAVE_DETAILED]` + */ +"DefaultSiteType" | +/** + * Arguments: site type + * + * Most residents will try to move to this site type, unless already at one. + * + * e.g. `[LIKES_SITE:CAVE_DETAILED]` + */ +"LikesSite" | +/** + * Arguments: site type + * + * Some residents will try to move to this site type, unless already at one. + * + * e.g. `[TOLERATES_SITE:CITY]` + */ +"ToleratesSite" | +/** + * Arguments: construction + * + * Controls which constructions the civ will build on the world map. Valid constructions are ROAD, TUNNEL, BRIDGE, and WALL. + * + * e.g. `[WORLD_CONSTRUCTION:BRIDGE] [WORLD_CONSTRUCTION:ROAD] [WORLD_CONSTRUCTION:TUNNEL] [WORLD_CONSTRUCTION:WALL]` + */ +"WorldConstruction" | +/** + * Arguments: number + * + * Max historical population per entity. Multiply this by max starting civ to get the total maximum historical population of the species. + * + * Defaults to 500. + * + * e.g. `[MAX_POP_NUMBER:500]` + */ +"MaxPopNumber" | +/** + * Arguments: number + * + * Max historical population per individual site. + * + * Defaults to 50. + * + * e.g. `[MAX_SITE_POP_NUMBER:200]` + */ +"MaxSitePopNumber" | +/** + * Arguments: number + * + * Max number of civ to spawn at world generation. Worldgen picks entities in some sequential order from the raws, + * and once it reaches the end of the list, it will begin again at the top. Setting this number lower than 100, + * like say, 7, will cause worldgen to skip over the civ for placement if there are already 7 civs of this type. + * + * Note that if all civs are set to lower numbers, and the number of starting civs is set higher than the + * maximum possible amount of civs in total, it will gracefully stop placing civs and get down to the history + * aspect of worldgen. + * + * Defaults to 3. + * + * e.g `[MAX_STARTING_CIV_NUMBER:3]` + */ +"MaxStartingCivNumber" | +/** + * Arguments: building name + * + * The named, custom building can be built by a civilization in Fortress Mode. + * + * e.g. `[PERMITTED_BUILDING:SOAP_MAKER]` + */ +"PermittedBuilding" | +/** + * Arguments: profession + * + * Allows this job type to be selected. This applies to worldgen creatures, in the embark screen, and in play. + * Certain professions also influence the availability of materials for trade. + * + * e.g. `[PERMITTED_JOB:MINER]` + */ +"PermittedJob" | +/** + * Arguments: reaction name + * + * Allows this reaction to be used by a civilization. It is used primarily in Fortress Mode, + * but also allows certain resources, such as steel, to be available to a race. When creating custom reactions, + * this token must be present or the player will not be able to use the reaction in Fortress Mode. + * + * e.g. `[PERMITTED_REACTION:TAN_A_HIDE]` + */ +"PermittedReaction" | +/** + * Causes the civ's currency to be numbered with the year it was minted. + */ +"CurrencyByYear" | +/** + * Arguments: inorganic material, value + * + * What kind of metals the civ uses for coin minting, as well as the value of the coin. + * Due to the Dwarven economy having been disabled since version 0.31, the value doesn't actually do anything. + * + * e.g `[CURRENCY:SILVER:5]` + */ +"Currency" | +/** + * Arguments: type, number + * + * `OWN_RACE`, `FANCIFUL`, `EVIL`, `GOOD` + * + * Number goes from `0` to `25_600` where `256` is the default. + * + * e.g. `[ART_FACET_MODIFIER:OWN_RACE:512]` + */ +"ArtFacetModifier" | +/** + * Arguments: item, number + * + * Allowed item: CREATURE, PLANT, TREE, SHAPE, ITEM + * + * Allowed number: 0-25600 + * + * Determines the chance of each image occurring in that entity's artwork, such as engravings and on artifacts, + * for default (non-historical) artwork. + * + * e.g. `[ART_IMAGE_ELEMENT_MODIFIER:TREE:512]` + */ +"ArtImageElementModifier" | +/** + * Arguments: item, number + * + * Allowed item: `ART_IMAGE`, `COVERED` or `GLAZED`, `RINGS_HANGING`, `BANDS`, `SPIKES`, `ITEMSPECIFIC`, `THREAD`, `CLOTH`, `SEWN_IMAGE` + * + * Allowed number: 0-25600 + * + * Determines the chance of the entity using that particular artwork method, such as "encircled with bands" or "menaces with spikes". + * + * `[ITEM_IMPROVEMENT_MODIFIER:SPIKES:0]` + * + * This also seems to change the amount that the entity will pay for items that are improved in these ways in their tokens. + */ +"ItemImprovementModifier" | +/** + * Arguments: language + * + * What language raw the entity uses. + * + * - If an entity lacks this tag, translations are drawn randomly from all translation files. Multiple translation tags will only + * result in the last one being used. Migrants will sometimes arrive with no name. + * - If `GEN_DIVINE` is entered, the entity will use a generated divine language, that is, the same language that is used for the names of angels. + * + * e.g. `[TRANSLATION:DWARF]` + */ +"Translation" | +/** + * Arguments: noun, symbol + * + * Allowed Values: + * `ALL`, `REMAINING`, `BATTLE`, `BRIDGE`, `CIV`, `CRAFT_GUILD`, `FESTIVAL`, `LIBRARY`, `MERCHANT_COMPANY`, `MILITARY_UNIT`, + * `OTHER`, `RELIGION`, `ROAD`, `SIEGE`, `SITE`, `TEMPLE`, `TUNNEL`, `VESSEL`, `WALL`, `WAR` + * + * Causes the entity to more often use these symbols in the particular SYM set. + * + * REMAINING will select all symbols that have not already been declared above it. + * + * e.g. `[SELECT_SYMBOL:ALL:PEACE]` + */ +"SelectSymbol" | +/** + * Arguments: noun, symbol + * + * Causes the symbol set to be preferred as adjectives by the civilization. Used in vanilla to put violent names in sieges and battles. + * + * e.g. `[SELECT_SYMBOL:SIEGE:NAME_SIEGE] [SUBSELECT_SYMBOL:SIEGE:VIOLENT]` + */ +"SubselectSymbol" | +/** + * Arguments: noun, symbol + * + * Causes the entity to not use the words in these SYM sets. + * + * e.g. `[CULL_SYMBOL:ALL:UGLY]` + */ +"CullSymbol" | +/** + * Arguments: color + * + * The color of this entity's civilization settlements in the world gen and embark screens, also used when announcing arrival of their caravan. + * + * Defaults to 7:0:1. + * + * e.g. `[FRIENDLY_COLOR:1:0:1]` + */ +"FriendlyColor" | +/** + * Arguments: type + * + * - `REGIONAL_FORCE`: The creatures will worship a single force associated with the terrain of their initial biome. + * - `PANTHEON`: The creatures will worship a group of gods, each aligned with their spheres and other appropriate ones as well. + * + * e.g. `[RELIGION:PANTHEON]` + */ +"Religion" | +/** + * Arguments: sphere + * + * Can be any available sphere - multiple entries are possible. Choosing a religious sphere will automatically make + * its opposing sphere not possible for the species to have: adding WATER, for example, means civilizations of this entity will + * never get FIRE as a religious sphere. Note that the DEATH sphere favours the appearance of necromancers + * (and therefore, towers) "in" the entity. + * + * e.g. `[RELIGION_SPHERE:FORTRESSES]` + */ +"ReligionSphere" | +/** + * Arguments: sphere, number + * + * This token forces an entity to favor or disfavor particular religious spheres, causing them to acquire those spheres more + * often when generating a pantheon. + * + * Default is 256, minimum is 0, maximum is 25600. + * + * e.g. `[SPHERE_ALIGNMENT:TREES:512]` + */ +"SphereAlignment" | +/** + * Defines a leader/noble position for a civilization. These replace previous tags such as `[MAYOR]` and `[CAN_HAVE_SITE_LEADER]` and so on. + * + * To define a position further, see Position token. + */ +"Position" | +/** + * Arguments: land holder ID, population, wealth exported, wealth created + * + * Defines when a particular land-holding noble (baron, count, duke in vanilla) will arrive at a fortress. + * + * As of version 0.44.11, however, this is obsolete due to the changes in how sites are elevated in status. + */ +"LandHolderTrigger" | +/** + * Arguments: position responsibility or 'ALL' + * + * Allows a site responsibility to be taken up by a dynamically generated position (lords, hearth-persons, etc.). + * Any defined positions holding a given responsibility will take precedence over generated positions for that responsibility. + * Also appears to cause site disputes. + */ +"SiteVariablePositions" | +/** + * Arguments: position responsibility or 'ALL' + * + * Allows a responsibility to be taken up by a dynamically generated position (such as Law-maker). + * Any defined positions holding a given responsibility will take precedence over generated positions for that responsibility. + */ +"VariablePositions" | +/** + * Arguments: behavior, action + * + * Sets the civ's view of ethics (certain behaviors), from capital punishment to completely acceptable. + * This also causes the civ to look upon opposing ethics with disfavor if their reaction to it is opposing, + * and when at extremes (one ACCEPTABLE, another civ UNTHINKABLE; for example) they will often go to war over it. + * + * e.g. `[ETHIC:EAT_SAPIENT_KILL:ACCEPTABLE]` + */ +"Ethic" | +/** + * Arguments: value, number + * + * Sets the civ's cultural values. Numbers range from -50 (complete anathema) to 0 (neutral) to 50 (highly valued). + * + * e.g. `[VALUE:CRAFTSMANSHIP:50]` + * + * Certain values must be set to 15 or more for civilizations to create structures and form entities during history gen: + * + * - 15+ KNOWLEDGE for libraries + * - 15+ COOPERATION and 15+ CRAFTSMANSHIP for craft guilds + * + * Guilds also need guild-valid professions (see `PERMITTED_JOB`) + */ +"Value" | +/** + * Arguments: value or `ALL`, min, max + * + * Makes values randomized rather than specified. + * + * This tag overrides the VALUE tag. Using `[VARIABLE_VALUE:ALL:x:y]` and then overwriting single values with further + * + * e.g. `[VARIABLE_VALUE:value:x:y]` tags works + */ +"VariableValue" | +/** + * Makes the civ's traders accept offered goods. + */ +"WillAcceptTribute" | +/** + * The civ will send out Wanderer adventurers in worldgen, which seems to increase Tracker skill. + * + * These types of adventurers will sometimes be seen leading a battle (instead of war leaders or generals) in remote locations during world-gen, in charge of the defenders. + * + * Mercenaries and monster hunters from the civ may visit player's fortress and petition for residency there to enlist in the military or hunt monsters in caverns, respectively. + */ +"Wanderer" | +/** + * The civ will send out `BeastHunter` adventurers in worldgen, which seems to increase Tracker skill. + * + * These types of adventurers will sometimes be seen leading a battle (instead of war leaders or generals) in remote locations during world-gen, in charge of the defenders. + * + * Mercenaries and monster hunters from the civ may visit player's fortress and petition for residency there to enlist in the military or hunt monsters in caverns, respectively. + */ +"BeastHunter" | +/** + * The civ will send out Scout adventurers in worldgen, which seems to increase Tracker skill. + * + * These types of adventurers will sometimes be seen leading a battle (instead of war leaders or generals) in remote locations during world-gen, in charge of the defenders. + * + * Mercenaries and monster hunters from the civ may visit player's fortress and petition for residency there to enlist in the military or hunt monsters in caverns, respectively. + */ +"Scout" | +/** + * The civ will send out Mercenary adventurers in worldgen, which seems to increase Tracker skill. + * + * These types of adventurers will sometimes be seen leading a battle (instead of war leaders or generals) in remote locations during world-gen, in charge of the defenders. + * + * Mercenaries and monster hunters from the civ may visit player's fortress and petition for residency there to enlist in the military or hunt monsters in caverns, respectively. + */ +"Mercenary" | +/** + * The civilization will mutilate bodies when they are the victors in history-gen warfare, such as hanging bodies from trees, putting them on spikes, and so forth. + * Adventurers killed in Adventurer mode will sometimes be impaled on spikes wherever they died, with or without this token, + * and regardless of whether they actually antagonized the townspeople. + */ +"AbuseBodies" | +/** + * Arguments: season + * + * The season when the civ is most active: when they will trade, interact with you via diplomats, and/or invade you. + * Civilizations can have multiple season entries. Note: If multiple caravans arrive at the same time, you are able to select + * which civ to trade with at the depot menu. `ACTIVE_SEASON` tags may be changed for a currently active fort. + * + * e.g. `[ACTIVE_SEASON:SUMMER]` + */ +"ActiveSeason" | +/** + * When invading, sneaks around and shoots at straggling members of your society. They will spawn on the edge of the map and will only be visible when + * one of their party are spotted; this can be quite dangerous to undefended trade depots. If the civilization also has the SIEGER token, + * they will eventually ramp it up to less subtle means of warfare. + */ +"Ambusher" | +/** + * Will not attack wildlife, and will not be attacked by them, even if you have them in your party. This can be somewhat disconcerting when attacked + * by bears in the forest, and your elven ally sits back and does nothing. Additionally, this token determines if the entity can settle in savage biomes. + */ +"AtPeaceWithWildlife" | +/** + * Sends thieves to steal babies. Without this tag (or `AMBUSHER`, or `ITEM_THIEF`), enemy civilizations will only siege (if capable), + * and will siege as early as they would otherwise babysnatch. This can happen as early as the first year of the fort! + * In addition, babysnatcher civilizations will snatch children during worldgen, allowing them to become part of the civ if they do not escape. + * + * Note: If the playable civ in fortress mode has this tag (e.g. you add BABYSNATCHER to the dwarf entity) then the roles will be reversed ==> + * elves and humans will siege and ambush and goblins will be friendly to you. + * However, animals traded away to one's own caravan will count as snatched, reported upon the animal leaving the map, + * and the animal will not count as having been exported. + */ +"BabySnatcher" | +/** + * Makes the civilization build castles from mead halls. Only functions when the type of site built is a hamlet/town. + * This, combined with the correct type of position associated with a site, is why adventurers can only lay claim to human sites. + */ +"BuildsOutdoorFortifications" | +/** + * Makes the civilization build tombs. + */ +"BuildsOutdoorTombs" | +/** + * Arguments: percentage + * + * Sets a percentage of the entity population to be used as bandits. + */ +"Banditry" | +/** + * Visiting diplomats are accompanied by a pair of soldiers. + */ +"DiplomatBodyguards" | +/** + * Found on generated divine "HF Guardian Entities". Cannot be used in user-defined raws. + */ +"Generated" | +/** + * Causes invaders to ignore visiting caravans and other neutral creatures + */ +"InvadersIgnoreNeutrals" | +/** + * Sends thieves to steal items. This will also occur in history generation, and thieves will have the "thief" profession. + * Items stolen in history gen will be scattered around that creature's home. + * + * Also causes that civ to be hostile to any entity without this token. Without this tag (or AMBUSHER, or BABYSNATCHER), enemy civs will only siege + * (if capable), and will siege as early as they would otherwise steal. + * + * Note: If the playable civ in Fortress Mode has this tag (e.g. you add `ITEM_THIEF` to the Dwarf entity) then the roles will be reversed ==> + * elves and humans will siege and ambush and kobolds will be friendly to you. However, ALL items traded away to one's own caravan will count as stolen, + * reported when the items leave the map, and the stolen items will not count as exported + */ +"ItemThief" | +/** + * Causes the entity to send out patrols that can ambush adventurers. Said patrols will be hostile to any adventurers they encounter, + * regardless of race or nationality. + */ +"LocalBanditry" | +/** + * Caravan merchants are accompanied by soldiers. + */ +"MerchantBodyguards" | +/** + * Merchants will engage in cross-civ trading and form companies. + * + * In previous versions, this resulted in the civ having a Guild Representative / Merchant Baron / Merchant Prince, + * but now this is controlled solely by positions + */ +"MerchantNobility" | +/** + * Arguments: level + * + * 0 to 5, civ will come to site once population at site has reached that level. If multiple progress triggers exist for a civ, + * it will come when any one of them is fulfilled instead of waiting for all of them to be reached. + * + * - A value of 0 disables the trigger. + * - 1 corresponds to 20 dwarves, + * - 2 to 50 dwarves, + * - 3 to 80, + * - 4 to 110, and + * - 5 to 140. + * + * Progress triggers may be changed, added, or deleted for a currently active fort. + * + * Note: hostile civs require that this be fulfilled as well as at least one other non-siege trigger before visiting for non-siege activities. + */ +"ProgressTriggerPopulation" | +/** + * Arguments: level + * + * 0 to 5, civ will come to site once created wealth has reached that level. If multiple progress triggers exist for a civ, + * it will come when any one of them is fulfilled instead of waiting for all of them to be reached. + * + * - A value of 0 disables the trigger. + * - 1 corresponds to 5000☼ created wealth, + * - 2 to 25000☼, + * - 3 to 100000☼, + * - 4 to 200000☼, and + * - 5 to 300000☼. + * + * Progress triggers may be changed, added, or deleted for a currently active fort. + */ +"ProgressTriggerProduction" | +/** + * Arguments: level + * + * 0 to 5, civ will come to site once exported goods has reached that level. If multiple progress triggers exist for a civ, + * it will come when any one of them is fulfilled instead of waiting for all of them to be reached. + * + * - A value of 0 disables the trigger. + * - 1 corresponds to 500☼ exported wealth, + * - 2 to 2500☼, + * - 3 to 10000☼, + * - 4 to 20000☼, and + * - 5 to 30000☼. + * + * Progress triggers may be changed, added, or deleted for a currently active fort. + */ +"ProgressTriggerTrade" | +/** + * Arguments: level + * + * 0 to 5, civ will begin to send sieges against the player civ when this level is reached if it is hostile. + * + * If multiple progress triggers exist for a civ, it will come when any one of them is fulfilled instead of + * waiting for all of them to be reached. A value of 0 disables the trigger + */ +"ProgressTriggerPopulationSiege" | +/** + * Arguments: level + * + * 0 to 5, civ will begin to send sieges against the player civ when this level is reached if it is hostile. + * + * If multiple progress triggers exist for a civ, it will come when any one of them is fulfilled instead of + * waiting for all of them to be reached. A value of 0 disables the trigger + */ +"ProgressTriggerProductionSiege" | +/** + * Arguments: level + * + * 0 to 5, civ will begin to send sieges against the player civ when this level is reached if it is hostile. + * + * If multiple progress triggers exist for a civ, it will come when any one of them is fulfilled instead of + * waiting for all of them to be reached. A value of 0 disables the trigger + */ +"ProgressTriggerTradeSiege" | +/** + * Will start campfires and wait around at the edge of your map for a month or two before rushing in to attack. + * This will occur when the progress triggers for sieging are reached. If the civ lacks smaller methods of conflict + * (`AMBUSHER`, `BABYSNATCHER`, `ITEM_THIEF`), they will instead send smaller-scale sieges when their triggers for + * "first contact" are reached. + */ +"Sieger" | +/** + * Guards certain special sites, such as a vault belonging to a demon allied with a deity. Used in generated divine entities. + */ +"SiteGuardian" | +/** + * This makes the severity of attacks depend on the extent of item/baby thievery rather than the passage of time. + * Designed to go with `ITEM_THIEF`, may or may not work with BABYSNATCHER. Prevents the civ from engaging in diplomacy + * or ending up at war. + */ +"Skulking" | +/** + * Visiting diplomats impose tree cutting quotas; without this, they will simply compliment your fortress and leave. + * Also causes the diplomat to make unannounced first contact at the very beginning of the first spring after your + * fortress becomes a land holder. + */ +"TreeCapDiplomacy" | +/** + * Defines if a civilization is a hidden subterranean entity, such as bat man civilizations. + * May spawn in any of the three caverns; cavern dweller raids due to agitation will pull from these. + * If you embark as this civ, you have access to pets and trees from all three layers, not only the first. + */ +"LayerLinked" | +/** + * Makes civilizations generate keyboard instruments + */ +"GenerateKeyboardInstruments" | +/** + * Makes civilizations generate percussion instruments + */ +"GeneratePercussionInstruments" | +/** + * Makes civilizations generate stringed instruments + */ +"GenerateStringedInstruments" | +/** + * Makes civilizations generate wind instruments + */ +"GenerateWindInstruments" | +/** + * Makes civilizations generate dance forms. + */ +"GenerateDanceForms" | +/** + * Makes civilizations generate musical forms. + */ +"GenerateMusicalForms" | +/** + * Makes civilizations generate poetic forms. + */ +"GeneratePoeticForms" | +/** + * Arguments: scholar type + * + * `ALL`, `ASTRONOMER`, `CHEMIST`, `DOCTOR`, `ENGINEER`, `GEOGRAPHER`, `HISTORIAN`, `MATHEMATICIAN`, `NATURALIST`, `PHILOSOPHER` + */ +"Scholar" | +/** + * Generates scholars based on the values generated with the `VARIABLE_VALUE` tag. + */ +"SetScholarsOnValuesAndJobs" | +/** + * Used for kobolds. + */ +"NoArtifactClaims" | +/** + * The civilization can breach the Underworld during world generation. + */ +"MiningUnderworldDisasters" | +/** + * Arguments: `item_token` + * + * Used before a ranged weapon type. + * + * e.g. `[AMMO:ITEM_AMMO_BOLTS]` + */ +"Ammo" | +/** + * Arguments: `item_token`, rarity + * + * Rarity is optional, and valid values are FORCED, COMMON, UNCOMMON, and RARE (anything else is treated as COMMON). + * FORCED items will be available 100% of the time, COMMON items 50%, UNCOMMON items 10%, and RARE items 1%. + * If certain armor types are lacking after performing one pass of randomized checks, the game will repeat random checks + * until an option is successfully chosen. + * + * e.g. `[ARMOR:ITEM_ARMOR_PLATEMAIL:COMMON]` + */ +"Armor" | +/** + * Arguments: `item_token` + * + * Causes the selected weapon to fall under the "digging tools" section of the embark screen. + * Also forces the weapon to be made out of metal, which can cause issues if a modded entity has access to picks without + * access to metal - for those cases, listing the pick under the `[WEAPON]` token works just as well. Note that this tag is + * neither necessary nor sufficient to allow use of that item as a mining tool -– + * for that, the item itself needs to be a weapon with `[SKILL:MINING]`. + * + * e.g. `[DIGGER:ITEM_WEAPON_PICK]` + */ +"Digger" | +/** + * Arguments: `item_token`, `rarity` + * + * Rarity is optional, and valid values are FORCED, COMMON, UNCOMMON, and RARE (anything else is treated as COMMON). + * Uses the same rarity values and methods as outlined in ARMOR. + * + * e.g. `[GLOVES:ITEM_GLOVES_GAUNTLETS:COMMON]` + */ +"Gloves" | +/** + * Arguments: `item_token`, `rarity` + * + * Rarity is optional, and valid values are FORCED, COMMON, UNCOMMON, and RARE (anything else is treated as COMMON). + * Uses the same rarity values and methods as outlined in ARMOR. + * + * e.g. `[HELM:ITEM_HELM_HELM:COMMON]` + */ +"Helm" | +/** + * Arguments: `item_token` + * + * No longer used as of Version 0.42.01 due to the ability to generate instruments in world generation. + * + * It is still usable if pre-defined instruments are modded in, and generated musical forms are capable + * of selecting pre-defined instruments to use. However, reactions for making instruments, instrument parts, + * and/or assembling such instruments need to be added as well, as this token no longer adds such instruments + * to the craftsdwarf workshop menu. + * + * e.g. `[INSTRUMENT:ITEM_INSTRUMENT_FLUTE]` + */ +"Instrument" | +/** + * Arguments: `item_token`, `rarity` + * + * Rarity is optional, and valid values are FORCED, COMMON, UNCOMMON, and RARE (anything else is treated as COMMON). + * Uses the same rarity values and methods as outlined in ARMOR. + * + * e.g. `[PANTS:ITEM_PANTS_LEGGINGS:COMMON]` + */ +"Pants" | +/** + * Arguments: `item_token` + * + * e.g. `[SHIELD:ITEM_SHIELD_BUCKLER]` + */ +"Shield" | +/** + * Arguments: `item_token`, `rarity` + * + * Rarity is optional, and valid values are FORCED, COMMON, UNCOMMON, and RARE (anything else is treated as COMMON). + * Uses the same rarity values and methods as outlined in ARMOR. + * + * e.g. `[SHOES:ITEM_SHOES_BOOTS:COMMON]` + */ +"Shoes" | +/** + * Arguments: `item_token` + * + * e.g. `[SIEGEAMMO:ITEM_SIEGEAMMO_BALLISTA]` + */ +"SiegeAmmo" | +/** + * Arguments: `item_token` + * + * e.g. `[TOOL:ITEM_TOOL_NEST_BOX]` + */ +"Tool" | +/** + * Arguments: `item_token` + * + * e.g. `[TOY:ITEM_TOY_PUZZLEBOX]` + */ +"Toy" | +/** + * Arguments: `item_token` + * + * e.g. `[TRAPCOMP:ITEM_TRAPCOMP_GIANTAXEBLADE]` + */ +"TrapComponent" | +/** + * Arguments: `item_token` + * + * While this does not accept a rarity value, something similar can be achieved by having multiple variations of a weapon type + * with small differences and specifying each of them. + * + * e.g. `[WEAPON:ITEM_WEAPON_AXE_BATTLE]` + */ +"Weapon" | +/** + * Allows use of products made from animals. All relevant creatures will be able to provide wool, silk, and extracts (including milk and venom) + * for trade, and non-sentient creatures (unless ethics state otherwise) will be able to provide eggs, caught fish, meat, leather, bone, + * shell, pearl, horn, and ivory. + */ +"UseAnimalProducts" | +/** + * Any creature in the civilization's list of usable's (from the surrounding 7x7 or so of squares and map features in those squares) + * which has `PET` or `PET_EXOTIC` will be available as a pet, pack animal (with `PACK_ANIMAL`), + * wagon puller (with `WAGON_PULLER`), + * mount (with `MOUNT` or `MOUNT_EXOTIC`), or + * siege minion (with `TRAINABLE_WAR ` and without `CAN_LEARN`). + * + * This notion of the initial usable creature list, which then gets pared down or otherwise considered, applies below as well. + * + * All common domestic and equipment creatures are also added to the initial list. + */ +"UseAnyPetRace" | +/** + * Without this, creatures with exclusively subterranean biomes are skipped. + * + * If they have it, cave creatures with PET will also be available as pets, pack animals (with `PACK_ANIMAL`), wagon pullers (with `WAGON_PULLER`), + * mounts (with `MOUNT` or `MOUNT_EXOTIC`), and siege minions (with `TRAINABLE_WAR` and without `CAN_LEARN`). + */ +"UseCaveAnimals" | +/** + * Without this, `EVIL` creatures are skipped. + * + * Otherwise, evil creatures with `SLOW_LEARNER` or without `CAN_LEARN` will be also available as pets (with `PET`), + * pack animals (with `PACK_ANIMAL`), wagon pullers (with `WAGON_PULLER`), mounts (with `MOUNT` or `MOUNT_EXOTIC`), + * and siege minions (with `TRAINABLE_WAR` or `SLOW_LEARNER`), even the normally untameable species. + */ +"UseEvilAnimals" | +/** + * Same as `USE_EVIL_ANIMALS` for all uses of plants. + */ +"UseEvilPlants" | +/** + * Same as `USE_EVIL_ANIMALS` for all uses of wood. + */ +"UseEvilWood" | +/** + * Without this `GOOD` creatures are skipped, otherwise, good creatures without `CAN_LEARN` will also be available as pets (with `PET`), + * pack animals (with `PACK_ANIMAL`), wagon pullers (with `WAGON_PULLER`), mounts (with `MOUNT` or `MOUNT_EXOTIC`), and siege minions + * (with `TRAINABLE_WAR`), even the normally untameable species. + */ +"UseGoodAnimals" | +/** + * Same as `USE_GOOD_ANIMALS` for all uses of plants. + */ +"UseGoodPlants" | +/** + * Same as `USE_GOOD_ANIMALS` for all uses of wood. + */ +"UseGoodWood" | +/** + * If the relevant professions are permitted, controls availability of lye (`LYE_MAKING`), charcoal (`BURN_WOOD`), and potash (`POTASH_MAKING`). + */ +"UseMiscProcessedWoodProducts" | +/** + * Makes the civilization use all locally available non-exotic pets. + */ +"UseNoneExoticPetRace" | +/** + * Gives the civilization access to creatures with `COMMON_DOMESTIC` and `MOUNT`. Additionally, all available + * (based on `USE_ANY_PET_RACE`, `USE_CAVE_ANIMALS`, `USE_GOOD_ANIMALS`, and `USE_EVIL_ANIMALS`) creature with `MOUNT` and `PET` + * will be allowed for use as mounts during combat. + */ +"CommonDomesticMount" | +/** + * Gives the civilization access to creatures with `COMMON_DOMESTIC` and `PACK_ANIMAL`. + * Additionally, all available (see above) creatures with `PACK_ANIMAL` and `PET` will be allowed for use during trade as pack animals. + */ +"CommonDomesticPackAnimal" | +/** + * Gives the civilization access to creatures with `COMMON_DOMESTIC` and `PET`. + * Additionally, all available (see above) creatures with PET will be allowed for use as pets. + */ +"CommonDomesticPet" | +/** + * Gives the civilization access to creatures with `COMMON_DOMESTIC` and `WAGON_PULLER`. Additionally, all + * available (see above) creatures with `WAGON_PULLER` and PET will be allowed for use during trade to pull wagons. + */ +"CommonDomesticPullAnimal" | +/** + * Allow use of river products in the goods available for trade. + */ +"RiverProducts" | +/** + * Allow use of ocean products (including amber and coral) in the goods available for trade. + * Without `OCEAN_PRODUCTS`, civilizations will not be able to trade ocean fish even if they are also + * available from other sources (e.g. sturgeons and stingrays). + */ +"OceanProducts" | +/** + * Allow use of underground plant products in the goods available for trade. + * Lack of suitable vegetation in the caverns will cause worldgen rejections. + */ +"IndoorFarming" | +/** + * Allow use of outdoor plant products in the goods available for trade. + * Lack of suitable vegetation in this civilization's starting area will cause worldgen rejections. + */ +"OutdoorFarming" | +/** + * Allow use of underground plant growths (quarry bush leaves, in unmodded games) in the goods available for trade. + */ +"IndoorGardens" | +/** + * Allow use of outdoor plant growths in the goods available for trade. + */ +"OutdoorGardens" | +/** + * Allows use of indoor tree growths in the goods available for trade. + * Not used in vanilla entities, as vanilla underground trees do not grow fruit. + * + * Needs `INDOOR_WOOD` to function. + * + * Will cause rejections, if growths are unavailable. + */ +"IndoorOrchards" | +/** + * Allows use of outdoor tree growths in the goods available for trade. + * + * Needs `OUTDOOR_WOOD` to function. + */ +"OutdoorOrchards" | +/** + * Civilization members will attempt to wear clothing. + */ +"Clothing" | +/** + * Will wear things made of spider silk and other subterranean materials. + */ +"SubterraneanClothing" | +/** + * Adds decorations to equipment based on the level of the generated unit. Also improves item quality. + */ +"EquipmentImprovements" | +/** + * Adds decorations to weapons generated for bowman and master bowman. + */ +"ImprovedBows" | +/** + * Allows metal materials to be used to make cages (inexpensive metals only) and crafts. + */ +"MetalPref" | +/** + * Allows the civilization to make use of nearby stone types. If the `FURNACE_OPERATOR` job is permitted, + * also allows ore-bearing stones to be smelted into metals. + */ +"StonePref" | +/** + * The civilization can make traditionally metallic weapons such as swords and spears from wood. + */ +"WoodWeapons" | +/** + * The civilization can make traditionally metallic armor such as mail shirts and helmets from wood. + */ +"WoodArmor" | +/** + * Enables creatures of this entity to bring gems in trade. + */ +"GemPref" | +/** + * Allow use of subterranean wood types, such as tower-cap and fungiwood logs. + */ +"IndoorWood" | +/** + * Allow use of outdoor wood types, such as mangrove and oak. + */ +"OutdoorWood" | +/** + * Arguments: shape + * + * Precious gems cut by this civilization's jewelers can be of this shape. + */ +"GemShape" | +/** + * Arguments: shape + * + * Ordinary non-gem stones cut by this civilization's jewelers can be of this shape. + */ +"StoneShape" | +/** + * Allows use of materials with `[DIVINE]` for clothing. Used for generated divine entities. + */ +"DivineMatClothing" | +/** + * Allows use of materials with `[DIVINE]` for crafts. Used for generated divine entities. + */ +"DivineMatCrafts" | +/** + * Allows use of metals with `[DIVINE]` for weapons. Used for generated divine entities. + */ +"DivineMatWeapons" | +/** + * Allows use of metals with `[DIVINE]` for armor. Used for generated divine entities. + */ +"DivineMatArmor" | +/** + * Start an animal definition. + */ +"Animal" | +/** + * Arguments: creature token + * + * Select specific creature. + */ +"AnimalToken" | +/** + * Arguments: creature caste token + * + * Select specific creature caste (requires `ANIMAL_TOKEN`). Sites with animal populations will still include all castes, + * but only the selected ones will be used for specific roles. + */ +"AnimalCasteToken" | +/** + * Arguments: creature class + * + * Select creature castes with this creature class (multiple uses allowed). + */ +"AnimalClass" | +/** + * Arguments: creature class + * + * Forbid creature castes with this creature class (multiple uses allowed). + */ +"AnimalForbiddenClass" | +/** + * Animal will be present even if it does not naturally occur in the entity's terrain. + * All creatures, including demons, night trolls and other generated ones will be used if no specific creature or class is selected. + */ +"AnimalAlwaysPresent" | +/** + * Override creature usage tokens, ALWAYS overrides NEVER if a caste is matched by more than one animal definition + */ +"AnimalNeverMount" | +/** + * Override creature usage tokens, ALWAYS overrides NEVER if a caste is matched by more than one animal definition + */ +"AnimalAlwaysMount" | +/** + * Override creature usage tokens, ALWAYS overrides NEVER if a caste is matched by more than one animal definition + */ +"AnimalNeverWagonPuller" | +/** + * Override creature usage tokens, ALWAYS overrides NEVER if a caste is matched by more than one animal definition + */ +"AnimalAlwaysWagonPuller" | +/** + * Override creature usage tokens, ALWAYS overrides NEVER if a caste is matched by more than one animal definition + */ +"AnimalNeverSiege" | +/** + * Override creature usage tokens, ALWAYS overrides NEVER if a caste is matched by more than one animal definition + */ +"AnimalAlwaysSiege" | +/** + * Override creature usage tokens, ALWAYS overrides NEVER if a caste is matched by more than one animal definition + */ +"AnimalNeverPet" | +/** + * Override creature usage tokens, ALWAYS overrides NEVER if a caste is matched by more than one animal definition + */ +"AnimalAlwaysPet" | +/** + * Override creature usage tokens, ALWAYS overrides NEVER if a caste is matched by more than one animal definition + */ +"AnimalNeverPackAnimal" | +/** + * Override creature usage tokens, ALWAYS overrides NEVER if a caste is matched by more than one animal definition + */ +"AnimalAlwaysPackAnimal" | +/** + * Arguments: tissue style unit ID + * + * Select a tissue layer which has the ID attached using `TISSUE_STYLE_UNIT` token in unit raws. + * This allows setting further cultural style parameters for the selected tissue layer. + */ +"TissueStyle" | +/** + * Arguments: min : max + * + * Presumably sets culturally preferred tissue length for selected tissue. Needs testing. + * Dwarves have their beards set to 100:NONE by default. + */ +"TissueStyleMaintainLength" | +/** + * Arguments: styling token + * + * Valid tokens are `NEATLY_COMBED`, `BRAIDED`, `DOUBLE_BRAIDS`, `PONY_TAILS`, `CLEAN_SHAVEN` and `STANDARD_HAIR/BEARD/MOUSTACHE/SIDEBURNS_SHAPINGS`. + * Presumably sets culturally preferred tissue shapings for selected tissue. Needs testing. + */ +"TissueStylePreferredShaping" | +/** + * An unknown token + */ +"Unknown" | +/** + * Found in raws as `SIEGE_SKILLED_MINERS` + * Presumed to mean that the entity can bring skilled miners to a siege + */ +"SiegeSkilledMiners" | +/** + * Prefers wood + */ +"WoodPref" | +/** + * An undead candidate + */ +"UndeadCandidate" | +/** + * Cut an existing entity + */ +"CutEntity" | +/** + * Select an entity to modify + */ +"SelectEntity" + +/** + * The class of environment that the stone appears in. + */ +export type EnvironmentClassTag = +/** + * Will appear in every stone. + */ +"AllStone" | +/** + * Will appear in all igneous layers + */ +"IgneousAll" | +/** + * Will appear in igneous extrusive layers + */ +"IgneousExtrusive" | +/** + * Will appear in igneous intrusive layers + */ +"IgneousIntrusive" | +/** + * Will appear in soil. + */ +"Soil" | +/** + * Will appear in sand. + */ +"SoilSand" | +/** + * Will appear in soil in the oceans. + */ +"SoilOcean" | +/** + * Will appear in sedimentary layers. + */ +"Sedimentary" | +/** + * Will appear in metamorphic layers. + */ +"Metamorphic" | +/** + * Will appear in alluvial layers. + */ +"Alluvial" | +/** + * Default value means parsing error. + */ +"None" + +/** + * A material fuel type that can be set in a material definition. + */ +export type FuelTypeTag = +/** + * Charcoal or coal + */ +"Charcoal" | +/** + * Coal coke + */ +"Coke" | +/** + * No glass furnace fuel + */ +"NoMaterialGloss" | +/** + * None is an invalid option, so its a hint that this is not set. + */ +"None" + +/** + * A struct describing how a creature moves. + * + * Gaits define the mechanics of movement modes like walking, swimming, or flying, + * including speed, acceleration, and energy costs. They are defined in raw files + * using the `[GAIT:type:name:full_speed:build_up:turning:start_speed:energy_use]` tag. + * + * These optional flags go at the end: + * + * * `LAYERS_SLOW` - fat/muscle layers slow the movement (muscle-slowing counter-acted by strength bonus) + * * `STRENGTH` - strength attribute can speed/slow movement + * * `AGILITY` - agility attribute can speed/slow movement + * * `STEALTH_SLOWS:` - n is percentage slowed + * + * Instead of specifying a `build_up` you can use `NO_BUILD_UP` to instantly get to speed. + * + * + * Examples: + * + * `[CV_NEW_TAG:GAIT:WALK:Sprint:!ARG4:10:3:!ARG2:50:LAYERS_SLOW:STRENGTH:AGILITY:STEALTH_SLOWS:50]` + * `[CV_NEW_TAG:GAIT:WALK:Run:!ARG3:5:3:!ARG2:10:LAYERS_SLOW:STRENGTH:AGILITY:STEALTH_SLOWS:20]` + * `[CV_NEW_TAG:GAIT:WALK:Jog:!ARG2:NO_BUILD_UP:5:LAYERS_SLOW:STRENGTH:AGILITY:STEALTH_SLOWS:10]` + * `[CV_NEW_TAG:GAIT:WALK:Walk:!ARG1:NO_BUILD_UP:0]` + * `[CV_NEW_TAG:GAIT:WALK:Stroll:!ARG5:NO_BUILD_UP:0]` + * `[CV_NEW_TAG:GAIT:WALK:Creep:!ARG6:NO_BUILD_UP:0]` + */ +export type Gait = { +/** + * The movement medium (e.g., [`GaitTypeTag::Walk`], [`GaitTypeTag::Swim`]). + */ +gaitType: GaitTypeTag; +/** + * The descriptive name of the movement (e.g., "Sprint", "Jog"). + */ +name: string; +/** + * The maximum speed achievable, where lower values are faster. + */ +maxSpeed: number; +/** + * The time in game ticks required to reach full speed. + */ +buildUpTime: number; +/** + * The maximum speed at which the creature can turn effectively. + */ +turningMax: number; +/** + * The speed at which the creature begins moving from a standstill. + */ +startSpeed: number; +/** + * The fatigue or energy cost associated with this movement. + */ +energyUse: number; +/** + * Optional modifiers affecting speed based on attributes or stealth. + */ +modifiers: GaitModifierTag[] } + +/** + * An enum representing a gait modifier. + */ +export type GaitModifierTag = +/** + * Fat/muscle layers slow the movement (muscle-slowing counter-acted by strength bonus) + * Makes `THICKENS_ON_ENERGY_STORAGE` and `THICKENS_ON_STRENGTH` tissue layers slow movement depending on how thick they are. + * Adding the `STRENGTH` gait flag counteracts the impact of the latter layer. + */ +"LayersSlow" | +/** + * Speeds/slows movement depending on the creature's Strength stat. + */ +"Strength" | +/** + * Speeds/slows movement depending on the creature's Agility stat. + */ +"Agility" | +/** + * Stealth slows movement by the specified percentage when the creature is sneaking. + */ +{ StealthSlows: { +/** + * The percentage slowed + */ +percentage: number } } | +/** + * No build up time + */ +"NoBuildUp" | +/** + * Build up time. Only used if the gait has a build up time. + */ +{ BuildUp: { +/** + * The build up time indicates how long it will take for a creature using this gait to go from `` to ``. + * For example, a value of 10 means that it should be able to reach the maximum speed by moving 10 tiles in a straight line over even terrain. + */ +time: number; +/** + * The turning max indicates the maximum speed permissible when the creature suddenly changes its direction of motion. + * The creature's speed will be reduced to `` if traveling at a higher speed than this before turning. + */ +turning_max: number; +/** + * The creature's speed when it starts moving using this gait + */ +start_speed: number } } + +/** + * An enum representing a gait type. + */ +export type GaitTypeTag = +/** + * Travel on foot/the ground + * Used for moving normally over ground tiles. + */ +"Walk" | +/** + * Travel on foot/the ground + * Used for moving over ground tiles whilst prone. + */ +"Crawl" | +/** + * Climbing on walls, etc. + * Used for moving whilst climbing. + */ +"Climb" | +/** + * Swimming in water/liquid + * Used for moving through tiles containing water or magma at a depth of at least 4/7. + */ +"Swim" | +/** + * Flying through the air + * Used for moving through open space. + */ +"Fly" | +/** + * Other gait type which is unexpected, but we can still handle it + */ +{ Other: string } | +/** + * Unknown gait type (unset) + */ +"Unknown" + +/** + * A struct representing a Graphic object. + */ +export type Graphic = { metadata: Metadata | null; identifier: string; objectId: string; casteIdentifier: string | null; kind: GraphicTypeTag; sprites: SpriteGraphic[] | null; layers: ([string, SpriteLayer[]])[] | null; growths: ([string, SpriteGraphic[]])[] | null; customExtensions: CustomGraphicExtension[] | null; tags: string[] | null; palletes: GraphicPalette[] } + +/** + * A struct representing a Graphic object. + */ +export type GraphicPalette = { +/** + * Name of the palette + */ +name: string; +/** + * Relative file path to the palette file + */ +file: string; +/** + * Default row of the palette + */ +defaultRow: number } + +/** + * The graphic type of the tile + */ +export type GraphicTypeTag = +/** + * The tile is a creature + */ +"creature" | +/** + * The tile is a creature caste + */ +"creatureCaste" | +/** + * The tile is a statue of a creature + */ +"statueCreature" | +/** + * The tile is a statue of a creature caste + */ +"statueCreatureCaste" | +/** + * The tile is a statue of a creature caste with a giant size + */ +"statuesSurfaceGiant" | +/** + * A tile + */ +"tile" | +/** + * An empty tile + */ +"empty" | +/** + * A plant + */ +"plant" | +/** + * An unknown type + */ +"unknown" | +/** + * A template + */ +"template" | +/** + * The tile is soil background + */ +"soilBackground" | +/** + * The tile is grass-1 + */ +"grass1" | +/** + * The tile is grass-2 + */ +"grass2" | +/** + * The tile is grass-3 + */ +"grass3" | +/** + * The tile is grass-4 + */ +"grass4" | +/** + * The tile is custom edging + */ +"customEdging" | +/** + * The tile is custom ramp + */ +"customRamp" | +/** + * The tile is custom corner (W) + */ +"customEdgeW" | +/** + * The tile is custom corner (E) + */ +"customEdgeE" | +/** + * The tile is custom corner (N) + */ +"customEdgeN" | +/** + * The tile is custom corner (S) + */ +"customEdgeS" | +/** + * The tile is custom corner (NW) + */ +"customEdgeNW" | +/** + * The tile is custom corner (NE) + */ +"customEdgeNE" | +/** + * The tile is custom corner (SW) + */ +"customEdgeSW" | +/** + * The tile is custom corner (SE) + */ +"customEdgeSE" | +/** + * The tile is a custom workshop + */ +"customWorkshop" | +/** + * The tile is a list icon + */ +"listIcon" | +/** + * The tile is an add tool + */ +"addTool" | +/** + * The tile is ammo + */ +"ammo" | +/** + * The tile is ammo straight default + */ +"ammoStraightDefault" | +/** + * The tile is ammo straight wood + */ +"ammoStraightWood" | +/** + * The tile is ammo diagonal default + */ +"ammoDiagonalDefault" | +/** + * The tile is ammo diagonal wood + */ +"ammoDiagonalWood" | +/** + * The tile is an armor + */ +"armor" | +/** + * The tile is food + */ +"food" | +/** + * The graphic is of gloves + */ +"gloves" | +/** + * The graphic is of a helm + */ +"helm" | +/** + * The graphic is of pants + */ +"pants" | +/** + * The graphic is of a rough gem + */ +"roughGem" | +/** + * The graphic is of a large gem + */ +"shapeLargeGem" | +/** + * The graphic is of a small gem + */ +"shapeSmallGem" | +/** + * The graphic is of a shield + */ +"shield" | +/** + * The graphic is of a wooden shield + */ +"shieldWooden" | +/** + * The graphic is of shoes + */ +"shoes" | +/** + * The graphic is of metal shoes + */ +"shoesMetal" | +/** + * The graphic is of siege ammo + */ +"siegeAmmo" | +/** + * The graphic is of siege ammo straight default + */ +"siegeAmmoStraightDefault" | +/** + * The graphic is of siege ammo straight wood + */ +"siegeAmmoStraightWood" | +/** + * The graphic is of siege ammo diagonal default + */ +"siegeAmmoDiagonalDefault" | +/** + * The graphic is of siege ammo diagonal wood + */ +"siegeAmmoDiagonalWood" | +/** + * The graphic is of a tool + */ +"tool" | +/** + * The graphic is of a tool wood + */ +"toolWood" | +/** + * The graphic is of a tool stone + */ +"toolStone" | +/** + * The graphic is of a tool metal + */ +"toolMetal" | +/** + * The graphic is of a beehive + */ +"toolHiveBuilding" | +/** + * The graphic is of a glass tool + */ +"toolGlass" | +/** + * The graphic is of a tool shape + */ +"toolShape" | +/** + * The graphic is a glass tool variant + */ +"toolGlassVariant" | +/** + * The graphic is a metal tool variant + */ +"toolMetalVariant" | +/** + * The graphic is a stone tool variant + */ +"toolStoneVariant" | +/** + * The graphic is a wood tool variant + */ +"toolWoodVariant" | +/** + * The graphic is a mud tool + */ +"toolMud" | +/** + * The graphic is a water tool + */ +"toolWater" | +/** + * The graphic is a vomit tool + */ +"toolVomit" | +/** + * The graphic is a blood tool + */ +"toolBlood" | +/** + * The graphic is a plant tool + */ +"toolDamage" | +/** + * The graphic is a tool with binds on it + */ +"toolBands" | +/** + * The graphic is a tool with engravings + */ +"toolEngraving" | +/** + * The graphic is a tool with studs + */ +"toolStuds" | +/** + * The graphic is a tool with rings + */ +"toolRings" | +/** + * The graphic is a tool with spikes + */ +"toolSpikes" | +/** + * The graphic is a toy + */ +"toy" | +/** + * The graphic is a trap component + */ +"trapComponent" | +/** + * The graphic is a weapon trap + */ +"trapComponentWeaponTrap" | +/** + * The graphic is a weapon trap upright 1-T + */ +"trapComponentUpright1T" | +/** + * The graphic is a weapon trap upright 2-T + */ +"trapComponentUpright2T" | +/** + * The graphic is a weapon trap upright 3-T + */ +"trapComponentUpright3T" | +/** + * The graphic is a weapon trap upright 4-T + */ +"trapComponentUpright4T" | +/** + * The graphic is a weapon trap upright 5-T + */ +"trapComponentUpright5T" | +/** + * The graphic is a weapon trap upright 6-T + */ +"trapComponentUpright6T" | +/** + * The graphic is a weapon trap upright 7-T + */ +"trapComponentUpright7T" | +/** + * The graphic is a weapon trap upright 8-T + */ +"trapComponentUpright8T" | +/** + * The graphic is a weapon trap upright 9-T + */ +"trapComponentUpright9T" | +/** + * The graphic is a weapon trap upright 10-T + */ +"trapComponentUpright10T" | +/** + * The graphic is a weapon trap upright 1-B + */ +"trapComponentUpright1B" | +/** + * The graphic is a weapon trap upright 2-B + */ +"trapComponentUpright2B" | +/** + * The graphic is a weapon trap upright 3-B + */ +"trapComponentUpright3B" | +/** + * The graphic is a weapon trap upright 4-B + */ +"trapComponentUpright4B" | +/** + * The graphic is a weapon trap upright 5-B + */ +"trapComponentUpright5B" | +/** + * The graphic is a weapon trap upright 6-B + */ +"trapComponentUpright6B" | +/** + * The graphic is a weapon trap upright 7-B + */ +"trapComponentUpright7B" | +/** + * The graphic is a weapon trap upright 8-B + */ +"trapComponentUpright8B" | +/** + * The graphic is a weapon trap upright 9-B + */ +"trapComponentUpright9B" | +/** + * The graphic is a weapon trap upright 10-B + */ +"trapComponentUpright10B" | +/** + * The graphic is a weapon + */ +"weapon" | +/** + * The graphic is a weapon default + */ +"weaponDefault" | +/** + * The graphic is a weapon made of wood + */ +"weaponWood" | +/** + * The graphic is a weapon made of grown wood + */ +"weaponWoodGrown" | +/** + * The graphic is a weapon made of material + */ +"weaponMaterial" | +/** + * The graphic is of a weapon used in traps + */ +"weaponTrap" | +/** + * The graphic is of a weapon upright 1-T + */ +"weaponUpright1T" | +/** + * The graphic is of a weapon upright 2-T + */ +"weaponUpright2T" | +/** + * The graphic is of a weapon upright 3-T + */ +"weaponUpright3T" | +/** + * The graphic is of a weapon upright 4-T + */ +"weaponUpright4T" | +/** + * The graphic is of a weapon upright 5-T + */ +"weaponUpright5T" | +/** + * The graphic is of a weapon upright 6-T + */ +"weaponUpright6T" | +/** + * The graphic is of a weapon upright 7-T + */ +"weaponUpright7T" | +/** + * The graphic is of a weapon upright 8-T + */ +"weaponUpright8T" | +/** + * The graphic is of a weapon upright 9-T + */ +"weaponUpright9T" | +/** + * The graphic is of a weapon upright 10-T + */ +"weaponUpright10T" | +/** + * The graphic is of a weapon upright 1-B + */ +"weaponUpright1B" | +/** + * The graphic is of a weapon upright 2-B + */ +"weaponUpright2B" | +/** + * The graphic is of a weapon upright 3-B + */ +"weaponUpright3B" | +/** + * The graphic is of a weapon upright 4-B + */ +"weaponUpright4B" | +/** + * The graphic is of a weapon upright 5-B + */ +"weaponUpright5B" | +/** + * The graphic is of a weapon upright 6-B + */ +"weaponUpright6B" | +/** + * The graphic is of a weapon upright 7-B + */ +"weaponUpright7B" | +/** + * The graphic is of a weapon upright 8-B + */ +"weaponUpright8B" | +/** + * The graphic is of a weapon upright 9-B + */ +"weaponUpright9B" | +/** + * The graphic is of a weapon upright 10-B + */ +"weaponUpright10B" + +/** + * The growth tag of the tile + */ +export type GrowthTag = +/** + * The tile is a fruit + */ +"fruit" | +/** + * The tile is growth-1 + */ +"growth1" | +/** + * The tile is growth-2 + */ +"growth2" | +/** + * The tile is growth-3 + */ +"growth3" | +/** + * The tile is growth-4 + */ +"growth4" | +/** + * The tile is "as-is" + */ +"asIs" + +/** + * The 'HABIT_NUM' value which can be a number or "TEST_ALL" + */ +export type HabitCount = +/** + * Test all possible habit values + */ +"TestAll" | +/** + * Test a specific number of habits + */ +{ Specific: number } + +/** + * The type of inclusion that the stone has. + */ +export type InclusionTypeTag = +/** + * Large ovoids that occupy their entire 48x48 embark tile. Microcline is an example. When mined, stone has a 25% yield (as with layer stones). + */ +"Cluster" | +/** + * Blobs of 3-9 tiles. Will always be successfully mined. Red pyropes are an example. When mined, stone has a 100% yield. + */ +"ClusterSmall" | +/** + * Single tiles. Will always be successfully mined. Clear diamonds are an example. When mined, stone has a 100% yield. + */ +"ClusterOne" | +/** + * Large streaks of stone. Native gold is an example. When mined, stone has a 33% yield instead of the usual 25%. + */ +"Vein" | +/** + * Default value means parsing error. + */ +"None" + +/** + * Represents the `info.txt` file for a raw module + */ +export type InfoFile = { identifier: string; objectId: string; location: RawModuleLocation; parentDirectory: string; numericVersion: number; displayedVersion: string; earliestCompatibleNumericVersion: number; earliestCompatibleDisplayedVersion: string; author: string; name: string; description: string; requiresIds: string[] | null; conflictsWithIds: string[] | null; requiresIdsBefore: string[] | null; requiresIdsAfter: string[] | null; steamData: SteamData | null } + +/** + * The raw representation of an inorganic object. + */ +export type Inorganic = { identifier: string; metadata: Metadata | null; objectId: string; material: Material; metalOreChance: ([string, number])[] | null; threadMetalChance: ([string, number])[] | null; environmentClass: EnvironmentClassTag | null; environmentInclusionType: InclusionTypeTag | null; environmentInclusionFrequency: number | null; environmentClassSpecific: string[] | null; tags: InorganicTag[] | null } + +/** + * Tags that can be used in inorganic raws. + */ +export type InorganicTag = +/** + * Used on metals, causes the metal to be made into wafers instead of bars. + */ +"Wafers" | +/** + * Causes the stone to form hollow tubes leading to the Underworld. Used for raw adamantine. When mined, stone has a 100% yield. + * If no material with this token exists, hollow veins will instead be made of the first available inorganic, usually iron. Implies \[SPECIAL\]. + */ +"DeepSpecial" | +/** + * Allows the ore to be smelted into metal in the smelter. Each token with a non-zero chance causes the game to roll d100 four times, + * each time creating one bar of the type requested on success. + */ +"MetalOre" | +/** + * Allows strands to be extracted from the metal at a craftsdwarf's workshop. + */ +"ThreadMetal" | +/** + * Causes the stone to line the landscape of the Underworld. Used for slade. When mined (if it's mineable), stone has a 100% yield. If no material with this token exists, + * other materials will be used in place of slade. Underworld spires will still be referred to as a "spire of slade" in the world's history. + */ +"DeepSurface" | +/** + * Allows the stone to support an aquifer. + */ +"Aquifer" | +/** + * Causes the material to form metamorphic layers. + */ +"Metamorphic" | +/** + * Causes the material to form sedimentary layers. + */ +"Sedimentary" | +/** + * Causes the material to form soil layers, allowing it to appear in (almost) any biome. Mining is faster and produces no stones. + */ +"Soil" | +/** + * Causes the material to form pelagic sediment layers beneath deep oceans. Mining is faster and produces no stones. + */ +"SoilOcean" | +/** + * Causes the material to form sand layers, allowing it to appear in sand deserts and shallow oceans. Mining is faster and produces no stones. + * Sand layers can also be used for making glass. Can be combined with \[SOIL\]. + */ +"SoilSand" | +/** + * Permits an already \[SEDIMENTARY\] stone layer to appear underneath shallow ocean regions. + */ +"SedimentaryOceanShallow" | +/** + * Permits an already \[SEDIMENTARY\] stone layer to appear underneath deep ocean regions. + */ +"SedimentaryOceanDeep" | +/** + * Causes the material to form igneous intrusive layers. + */ +"IgneousExtrusive" | +/** + * Causes the material to form igneous extrusive layers. + */ +"IgneousIntrusive" | +/** + * Specifies what types of layers will contain this mineral. Multiple instances of the same token segment will cause the rock type to occur more frequently, + * but won't increase its abundance in the specified environment. See below. + */ +"Environment" | +/** + * Specifies which specific minerals will contain this mineral. See below. + */ +"EnvironmentSpecific" | +/** + * Specifies that the stone is created when combining water and magma, also causing it to line the edges of magma pools and volcanoes. + * If multiple minerals are marked as lava stones, a different one will be used in each biome or geological region. + */ +"Lava" | +/** + * Prevents the material from showing up in certain places. AI-controlled entities won't use the material to make items and don't bring it in caravans, + * though the player can use it as normal. Also, inorganic generated creatures (forgotten beasts, titans, demons) will never be composed of this material. + * Explicitly set by all evil weather materials and implied by `[DEEP_SURFACE]` and `[DEEP_SPECIAL]`. + */ +"Special" | +/** + * Indicates that this is a generated material. Cannot be specified in user-defined raws. + */ +"Generated" | +/** + * Found on random-generated metals and cloth. Marks this material as usable by Deity-created generated entities. + */ +"Divine" | +/** + * Found on divine materials. Presumably links the material to a god of the same sphere. + */ +"Sphere" | +/** + * Default value means parsing error. + */ +"Unknown" + +/** + * Helper struct for managing locations related to the game directory and user directory. + */ +export type LocationHelper = { df_directory: string | null; user_data_directory: string | null } + +/** + * A struct representing a material + */ +export type Material = { +/** + * The type of the material is also the trigger to start tracking a material + */ +materialType: MaterialTypeTag | null; +/** + * The material might have a name, but its more likely that there is only an identifier to + * refer to another creature/plant/reaction, which are listed elsewhere. + * If there is no name provided, then it is a special hardcoded case, e.g. magma or green glass. + */ +name: string | null; +/** + * For the coal tag, it specifies the type of fuel that can be used. It will never be None. + */ +fuelType: FuelTypeTag | null; +/** + * Linked creature identifier (and then `material_name` might be "skin", like for "`CREATURE_MAT:DWARF:SKIN`") + */ +creatureIdentifier: string | null; +/** + * Linked plant identifier (and then `material_name` might be "leaf", like for "`PLANT_MAT:BUSH_QUARRY:LEAF`") + */ +plantIdentifier: string | null; +/** + * If a material is defined within a creature itself, it will use `LOCAL_CREATURE_MAT` tag, which implies + * that the material is only used by that creature. This is also true for plants and `LOCAL_PLANT_MAT`. + */ +isLocalMaterial: boolean | null; +/** + * Within a reaction, there can be special material definitions. Todo: Figure this out. + */ +reagentIdentifier: string | null; reactionProductIdentifier: string | null; +/** + * If material is defined from a template, we need a way to refer to that + */ +templateIdentifier: string | null; +/** + * Usage tags + */ +usage: MaterialUsageTag[] | null; value: number | null; color: Color | null; stateNames: StateName | null; stateAdjectives: StateName | null; stateColors: StateName | null; temperatures: Temperatures | null; +/** + * Catch-all for remaining tags we identify but don't do anything with... yet. + */ +properties: string[] | null; syndromes: Syndrome[] | null; mechanicalProperties: MaterialMechanics | null; liquidDensity: number | null; molarMass: number | null; buildColor: Color | null; displayColor: Color | null; tile: Tile | null; itemSymbol: string | null } + +/** + * Represents the specific yield, fracture, and elasticity of a material for the various + * types of mechanical stress. + */ +export type MaterialMechanics = { impact: MechanicalProperties | null; compressive: MechanicalProperties | null; tensile: MechanicalProperties | null; torsion: MechanicalProperties | null; shear: MechanicalProperties | null; bending: MechanicalProperties | null; maxEdge: number | null; solidDensity: number | null } + +/** + * A material property that can be set in a material definition. + */ +export type MaterialPropertyTag = +/** + * Imports the properties of the specified preexisting material template. + */ +"UseMaterialTemplate" | +/** + * Applies a prefix to all items made from the material. For `PLANT` and `CREATURE` materials, this defaults to the plant/creature name. + * Not permitted in material template definitions. + */ +"Prefix" | +/** + * Overrides the name of `BOULDER` items (i.e. mined-out stones) made of the material (used for native copper/silver/gold/platinum + * to make them be called "nuggets" instead of "boulders"). + */ +"StoneName" | +/** + * Used to indicate that said material is a gemstone - when tiles are mined out, rough gems will be yielded instead of boulders. + * Plural can be "STP" to automatically append an "s" to the singular form, and `OVERWRITE_SOLID` will override the relevant `STATE_NAME` and `STATE_ADJ` values. + */ +"IsGem" | +/** + * Specifies what the material should be treated as when drinking water contaminated by it, for generating unhappy thoughts. + * Valid values are `BLOOD`, `SLIME`, `VOMIT`, `ICHOR`, `PUS`, `GOO`, `GRIME`, and `FILTH`. + */ +"TempDietInfo" | +/** + * Allows the material to be used as dye, and defines color of dyed items. + */ +"PowderDye" | +/** + * Specifies the tile that will be used to represent unmined tiles made of this material. Generally only used with stones. Defaults to 219 ('█'). + */ +"Tile" | +/** + * Specifies the tile that will be used to represent BOULDER items made of this material. Generally only used with stones. Defaults to 7 ('•'). + */ +"ItemSymbol" | +/** + * The on-screen color of the material. Uses a standard 3-digit color token. Equivalent to `TILE_COLOR:a:b:c`, + * `BUILD_COLOR:b:a:X` (X = 1 if 'a' equals 'b', 0 otherwise), and `BASIC_COLOR:a:c` + */ +"DisplayColor" | +/** + * The color of objects made of this material which use both the foreground and background color: doors, floodgates, hatch covers, bins, barrels, and cages. + * Defaults to `7:7:1` (white). + */ +"BuildColor" | +/** + * The color of unmined tiles containing this material (for stone and soil), as well as engravings in this material. Defaults to `7:7:1` (white). + */ +"TileColor" | +/** + * The color of objects made of this material which use only the foreground color, including workshops, floors and boulders, and smoothed walls. Defaults to `7:1` (white). + */ +"BasicColor" | +/** + * Determines the color of the material at the specified state. See below for a list of valid material states. Color comes from `descriptor_color_standard.txt`. + * The nearest color value is used to display contaminants and body parts made of this material in ASCII and to color items and constructions made from this + * material with graphics. + * Example:`STATE_COLOR:ALL_SOLID:GRAY` + */ +"StateColor" | +/** + * Determines the name of the material at the specified state, as displayed in-game. `STATE_NAME:ALL_SOLID:stone` + */ +"StateName" | +/** + * Like `STATE_NAME`, but used in different situations. Equipment made from the material uses the state adjective and not the state name. + */ +"StateAdjective" | +/** + * Sets both `STATE_NAME` and `STATE_ADJ` at the same time. + */ +"StateNameAdjective" | +/** + * The material's tendency to absorb liquids. Containers made of materials with nonzero absorption cannot hold liquids unless they have been glazed. + * Defaults to 0. + */ +"Absorption" | +/** + * Specifies how hard of an impact (in kilopascals) the material can withstand before it will start deforming permanently. + * Used for blunt-force combat. Defaults to `10_000`. + */ +"ImpactYield" | +/** + * Specifies how hard of an impact the material can withstand before it will fail entirely. Used for blunt-force combat. Defaults to `10_000`. + */ +"ImpactFracture" | +/** + * Specifies how much the material will have given (in parts-per-`100_000`) when the yield point is reached. Used for blunt-force combat. Defaults to 0. + * Apparently affects in combat whether the corresponding tissue is bruised (`value >= 50_000`), torn (value between `25_000` and `49_999`), or fractured (`value <= 24_999`) + */ +"ImpactElasticity" | +/** + * Specifies how hard the material can be compressed before it will start deforming permanently. Determines a tissue's resistance to pinching and response to strangulation. + * Defaults to `10_000`. + */ +"CompressiveYield" | +/** + * Specifies how hard the material can be compressed before it will fail entirely. Determines a tissue's resistance to pinching and response to strangulation. + * Defaults to `10_000`. + */ +"CompressiveFracture" | +/** + * Specifies how much the material will have given when it has been compressed to its yield point. Determines a tissue's resistance to pinching and + * response to strangulation. Defaults to 0. + */ +"CompressiveElasticity" | +/** + * Specifies how hard the material can be stretched before it will start deforming permanently. Determines a tissue's resistance to a latching and tearing bite. + * Defaults to `10_000`. + */ +"TensileYield" | +/** + * Specifies how hard the material can be stretched before it will fail entirely. Determines a tissue's resistance to a latching and tearing bite. Defaults to `10_000`. + */ +"TensileFracture" | +/** + * Specifies how much the material will have given when it is stretched to its yield point. Determines a tissue's resistance to a latching and tearing bite. + * Defaults to 0. + */ +"TensileElasticity" | +/** + * Specifies how hard the material can be twisted before it will start deforming permanently. Used for latching and shaking with a blunt attack + * (no default creature has such an attack, but they can be modded in). Defaults to `10_000`. + */ +"TorsionYield" | +/** + * Specifies how hard the material can be twisted before it will fail entirely. Used for latching and shaking with a blunt attack + * (no default creature has such an attack, but they can be modded in). Defaults to `10_000`. + */ +"TorsionFracture" | +/** + * Specifies how much the material will have given when it is twisted to its yield point. Used for latching and shaking with a blunt attack + * (no default creature has such an attack, but they can be modded in). Defaults to 0. + */ +"TorsionElasticity" | +/** + * Specifies how hard the material can be sheared before it will start deforming permanently. Used for cutting calculations. Defaults to `10_000`. + */ +"ShearYield" | +/** + * Specifies how hard the material can be sheared before it will fail entirely. Used for cutting calculations. Defaults to `10_000`. + */ +"ShearFracture" | +/** + * Specifies how much the material will have given when sheared to its yield point. Used for cutting calculations. Defaults to 0. + */ +"ShearElasticity" | +/** + * Specifies how hard the material can be bent before it will start deforming permanently. Determines a tissue's resistance to being mangled with a joint lock. + * Defaults to `10_000`. + */ +"BendingYield" | +/** + * Specifies how hard the material can be bent before it will fail entirely. Determines a tissue's resistance to being mangled with a joint lock. Defaults to `10_000`. + */ +"BendingFracture" | +/** + * Specifies how much the material will have given when bent to its yield point. Determines a tissue's resistance to being mangled with a joint lock. Defaults to 0. + */ +"BendingElasticity" | +/** + * How sharp the material is. Used in cutting calculations. Applying a value of at least `10_000` to a stone will allow weapons to be made from that stone. Defaults to `10_000`. + */ +"MaxEdge" | +/** + * Value modifier for the material. Defaults to 1. This number can be made negative by placing a "-" in front, resulting in things that you are paid to buy and + * must pay to sell. + */ +"MaterialValue" | +/** + * Multiplies the value of the material. Not permitted in material template definitions. + */ +"MultiplyValue" | +/** + * Rate at which the material heats up or cools down (in joules/kilogram-kelvin). If set to `NONE`, the temperature will be fixed at its initial value. + * Defaults to `NONE`. + */ +"SpecificHeat" | +/** + * Temperature above which the material takes damage from heat. Defaults to `NONE`. + * If the material has an ignite point but no heatdam point, it will burn for a very long time (9 months and 16.8 days). + */ +"HeatDamagePoint" | +/** + * Temperature below which the material takes damage from cold. Defaults to `NONE`. + */ +"ColdDamagePoint" | +/** + * Temperature at which the material will catch fire. Defaults to `NONE`. + */ +"IgnitionPoint" | +/** + * Temperature at which the material melts. Defaults to `NONE`. + */ +"MeltingPoint" | +/** + * Temperature at which the material boils. Defaults to `NONE`. + */ +"BoilingPoint" | +/** + * Items composed of this material will initially have this temperature. + * Used in conjunction with `SPEC_HEAT:NONE` to make material's temperature fixed at the specified value. + * Defaults to `NONE`. + */ +"MaterialFixedTemperature" | +/** + * Changes a material's `HEATDAM_POINT`, but only if it was not set to `NONE`. Not permitted in material template definitions. + */ +"IfExistsSetHeatDamagePoint" | +/** + * Changes a material's `COLDDAM_POINT`, but only if it was not set to `NONE`. Not permitted in material template definitions. + */ +"IfExistsSetColdDamagePoint" | +/** + * Changes a material's `IGNITE_POINT`, but only if it was not set to `NONE`. Not permitted in material template definitions. + */ +"IfExistsSetIgnitePoint" | +/** + * Changes a material's `MELTING_POINT`, but only if it was not set to `NONE`. Not permitted in material template definitions. + */ +"IfExistsSetMeltingPoint" | +/** + * Changes a material's `BOILING_POINT`, but only if it was not set to `NONE`. Not permitted in material template definitions. + */ +"IfExistsSetBoilingPoint" | +/** + * Changes a material's `MAT_FIXED_TEMP`, but only if it was not set to `NONE`. Not permitted in material template definitions. + */ +"IfExistsSetMatFixedTemp" | +/** + * Specifies the density (in kilograms per cubic meter) of the material when in solid form. Also affects combat calculations; + * affects blunt-force damage and ability of weak-in-impact-yield blunt attacks to pierce armor. Defaults to `NONE`. + */ +"SolidDensity" | +/** + * Specifies the density of the material when in liquid form. Defaults to `NONE`. Also affects combat calculations; + * affects blunt force damage like `SOLID_DENSITY`, but only for attacks made by liquids (e.g. forgotten beasts made of water). + */ +"LiquidDensity" | +/** + * Specifies (in kg/mol) the molar mass of the material in gaseous form. Also affects combat calculations like the densities, + * but only for attacks made by gases (e.g. forgotten beasts made of steam). + */ +"MolarMass" | +/** + * Specifies the type of container used to store the material. Used in conjunction with the `EXTRACT_BARREL`, `EXTRACT_VIAL`, + * or `EXTRACT_STILL_VIAL` plant tokens. + * Defaults to `BARREL`. + */ +"ExtractStorage" | +/** + * Specifies the item type used for butchering results made of this material. Stock raws use `GLOB:NONE` for fat and `MEAT:NONE` for other meat materials. + */ +"ButcherSpecial" | +/** + * When a creature is butchered, meat yielded from organs made from this material will be named via this token. + */ +"MeatName" | +/** + * Specifies the name of blocks made from this material. + */ +"BlockName" | +/** + * The material forms "wafers" instead of "bars". + */ +"Wafers" | +/** + * Used with reaction raws to associate a reagent material with a product material. The first argument is used by `HAS_MATERIAL_REACTION_PRODUCT` and `GET_MATERIAL_FROM_REAGENT` in reaction raws. + * The remainder is a material reference, generally `LOCAL_CREATURE_MAT:SUBTYPE` or `LOCAL_PLANT_MAT:SUBTYPE` or `INORGANIC:STONETYPE`. + * `MATERIAL_REACTION_PRODUCT:TAN_MAT:LOCAL_CREATURE_MAT:LEATHER` + */ +"MaterialReactionProduct" | +/** + * Used with reaction raws to associate a reagent material with a complete item. The first argument is used by `HAS_ITEM_REACTION_PRODUCT` and `GET_ITEM_DATA_FROM_REAGENT` in reaction raws. + * The rest refers to the type of item, then its material. + * `ITEM_REACTION_PRODUCT:BAG_ITEM:PLANT_GROWTH:LEAVES:LOCAL_PLANT_MAT:LEAF` + */ +"ItemReactionProduct" | +/** + * "Used to classify all items made of the material, so that reactions can use them as generic reagents.In default raws, the following are used: + * `FAT`, `TALLOW`, `SOAP`, `PARCHMENT`, `PAPER_PLANT`, `PAPER_SLURRY`, `MILK`, `CHEESE`, `WAX`. + * `CAN_GLAZE` - items made from this material can be glazed. + * `FLUX` - can be used as flux in pig iron and steel making. + * `GYPSUM` - can be processed into gypsum plaster. + * `CALCIUM_CARBONATE` - can be used in production of quicklime." + */ +"ReactionClass" | +/** + * Makes `BOULDER` acceptable as a reagent in reactions that require `METAL_ORE:MATERIAL_NAME`, as well as smelting directly into metal bars. + * Places the material under Metal Ores in Stone stockpiles. The specified value determines the probability for this product (see Tetrahedrite or Galena for details). + */ +"MetalOre" | +/** + * Makes `BOULDER` items made of the material acceptable for strand extraction into threads; see also `STOCKPILE_THREAD_METAL`. + * Value presumably determines the probability of this product extracted. + */ +"ThreadMetal" | +/** + * Allows the material to be used to make casts. + */ +"HardensWithWater" | +/** + * Determines effectiveness of soap - if the amount of grime on a body part is more than 3-SOAP_LEVEL, it sets it to 3-SOAP_LEVEL; as such setting it above 3 is bad. + * Soap has `[SOAP_LEVEL:2]`. Defaults to 0. + */ +"SoapLevel" | +/** + * Begins defining a syndrome applied by the material. Multiple syndromes can be specified. See Syndrome token. + */ +"Syndrome" | +/** + * This is since .50 in the raws of several antler-wielding animals. It is used to show an antler as bodypart. + */ +"Antler" | +/** + * Hair material + */ +"Hair" | +/** + * Feather material + */ +"Feather" | +/** + * Scale material + */ +"Scale" | +/** + * Hoof material + */ +"Hoof" | +/** + * Chitin material + */ +"Chitin" | +/** + * Cartilage material + */ +"Cartilage" | +/** + * Nervous tissue + */ +"NervousTissue" | +/** + * Category of meat + */ +"MeatCategory" | +/** + * For default value, use unknown. + */ +"Unknown" + +/** + * A material state that can be set in a material definition. + */ +export type MaterialStateTag = +/** + * Solid state of the material + */ +"Solid" | +/** + * Liquid state of the material + */ +"Liquid" | +/** + * Gas state of the material + */ +"Gas" | +/** + * `POWDER` or `SOLID_POWDER` + */ +"Powder" | +/** + * `PASTE` or `SOLID_PASTE` + */ +"Paste" | +/** + * `PRESSED` or `SOLID_PRESSED` + */ +"Pressed" | +/** + * Default value is invalid, so its a hint that this is not set. + */ +"Unknown" | +/** + * Denotes all possible material states + */ +"All" | +/** + * Denotes '`Solid`', '`Powder`', '`Paste`', and '`Pressed`' + */ +"AllSolid" + +/** + * A struct representing a material template + */ +export type MaterialTemplate = { identifier: string; metadata: Metadata | null; objectId: string; material: Material } + +/** + * A material template + */ +export type MaterialTypeTag = +/** + * An inorganic material + */ +"Inorganic" | +/** + * A stone material + */ +"Stone" | +/** + * A metal material + */ +"Metal" | +/** + * A coal material + */ +"Coal" | +/** + * A creature material + */ +"CreatureMaterial" | +/** + * A creature material for the current creature token only + */ +"LocalCreatureMaterial" | +/** + * A plant material + */ +"PlantMaterial" | +/** + * A plant material for the current plant token only + */ +"LocalPlantMaterial" | +/** + * A material from a reaction + */ +"GetMaterialFromReagent" | +/** + * Amber + */ +"Amber" | +/** + * Coral + */ +"Coral" | +/** + * Green Glass + */ +"GlassGreen" | +/** + * Clear Glass + */ +"GlassClear" | +/** + * Crystal Glass + */ +"GlassCrystal" | +/** + * Water + */ +"Water" | +/** + * Potash + */ +"Potash" | +/** + * Ash + */ +"Ash" | +/** + * Pearl Ash + */ +"PearlAsh" | +/** + * Lye + */ +"Lye" | +/** + * Mud + */ +"Mud" | +/** + * Vomit + */ +"Vomit" | +/** + * Salt + */ +"Salt" | +/** + * Brown Filth + */ +"FilthB" | +/** + * Yellow Filth + */ +"FilthY" | +/** + * Unnknown Substance + */ +"UnknownSubstance" | +/** + * Grime + */ +"Grime" | +/** + * An unknown token + */ +"Unknown" + +/** + * A material usage that can be set in a material definition. + */ +export type MaterialUsageTag = +/** + * Lets the game know that an animal was likely killed in the production of this item. + * Entities opposed to killing animals (Elves in vanilla) will refuse to accept these items in trade. + */ +"ImpliesAnimalKill" | +/** + * Classifies the material as plant-based alcohol, allowing its storage in food stockpiles under "Drink (Plant)". + */ +"AlcoholPlant" | +/** + * Classifies the material as animal-based alcohol, allowing its storage in food stockpiles under "Drink (Animal)". + */ +"AlcoholCreature" | +/** + * Classifies the material as generic alcohol. Implied by both `ALCOHOL_PLANT` and `ALCOHOL_CREATURE`. Exact behavior unknown, possibly vestigial. + */ +"Alcohol" | +/** + * Classifies the material as plant-based cheese, allowing its storage in food stockpiles under "Cheese (Plant)". + */ +"CheesePlant" | +/** + * Classifies the material as animal-based cheese, allowing its storage in food stockpiles under "Cheese (Animal)". + */ +"CheeseCreature" | +/** + * Classifies the material as generic cheese. Implied by both `CHEESE_PLANT` and `CHEESE_CREATURE`. Exact behavior unknown, possibly vestigial. + */ +"Cheese" | +/** + * Classifies the material as plant powder, allowing its storage in food stockpiles under "Milled Plant". + */ +"PowderMiscPlant" | +/** + * Classifies the material as creature powder, allowing its storage in food stockpiles under "Bone Meal". + * Unlike milled plants, such as sugar and flour, "Bone Meal" barrels or pots may not contain bags. + * Custom reactions using this product better use buckets or jugs instead. + */ +"PowderMiscCreature" | +/** + * Classifies the material as generic powder. Implied by both `POWDER_MISC_PLANT` and `POWDER_MISC_CREATURE`. + * Exact behavior unknown, possibly vestigial. + */ +"PowderMisc" | +/** + * Permits globs of the material in solid form to be stored in food stockpiles under "Fat" - without it, + * dwarves will come by and "clean" the items, destroying them (unless `DO_NOT_CLEAN_GLOB` is also included). + */ +"StockpileGlobOrStockpileGlobSolid" | +/** + * Classifies the material as milled paste, allowing its storage in food stockpiles under "Paste". + */ +"StockpileGlobPaste" | +/** + * Classifies the material as pressed goods, allowing its storage in food stockpiles under "Pressed Material". + */ +"StockpileGlobPressed" | +/** + * Classifies the material as a plant growth (e.g. fruits, leaves), allowing its storage in food stockpiles under Plant Growth/Fruit. + */ +"StockpilePlantGrowth" | +/** + * Classifies the material as a plant extract, allowing its storage in food stockpiles under "Extract (Plant)". + */ +"LiquidMiscPlant" | +/** + * Classifies the material as a creature extract, allowing its storage in food stockpiles under "Extract (Animal)". + */ +"LiquidMiscCreature" | +/** + * Classifies the material as a miscellaneous liquid, allowing its storage in food stockpiles under "Misc. Liquid" along with lye. + */ +"LiquidMiscOther" | +/** + * Classifies the material as a generic liquid. Implied by `LIQUID_MISC_PLANT`, `LIQUID_MISC_CREATURE`, and `LIQUID_MISC_OTHER`. Exact behavior unknown, possibly vestigial. + */ +"LiquidMisc" | +/** + * Classifies the material as a plant, allowing its storage in food stockpiles under "Plants". + */ +"StructuralPlantMat" | +/** + * Classifies the material as a plant seed, allowing its storage in food stockpiles under "Seeds". + */ +"SeedMat" | +/** + * Classifies the material as bone, allowing its use for bone carvers and restriction from stockpiles by material. + */ +"Bone" | +/** + * Classifies the material as wood, allowing its use for carpenters and storage in wood stockpiles. + * Entities opposed to killing plants (i.e. Elves) will refuse to accept these items in trade. + */ +"Wood" | +/** + * Classifies the material as plant fiber, allowing its use for clothiers and storage in cloth stockpiles under "Thread (Plant)" and "Cloth (Plant)". + */ +"ThreadPlant" | +/** + * Classifies the material as tooth, allowing its use for bone carvers and restriction from stockpiles by material. + */ +"Tooth" | +/** + * Classifies the material as horn, allowing its use for bone carvers and restriction from stockpiles by material. + */ +"Horn" | +/** + * Classifies the material as hair, allowing for its use for spinners and restriction from refuse stockpiles by material. + */ +"Hair" | +/** + * Classifies the material as pearl, allowing its use for bone carvers and restriction from stockpiles by material. + */ +"Pearl" | +/** + * Classifies the material as shell, allowing its use for bone carvers and restriction from stockpiles by material. + */ +"Shell" | +/** + * Classifies the material as leather, allowing its use for leatherworkers and storage in leather stockpiles. + */ +"Leather" | +/** + * Classifies the material as silk, allowing its use for clothiers and storage in cloth stockpiles under "Thread (Silk)" and "Cloth (Silk)". + */ +"Silk" | +/** + * Classifies the material as soap, allowing it to be used as a bath detergent and stored in bar/block stockpiles under "Bars: Other Materials". + */ +"Soap" | +/** + * Material generates miasma when it rots. + */ +"GeneratesMiasma" | +/** + * Classifies the material as edible meat. + */ +"Meat" | +/** + * Material will rot if not stockpiled appropriately. Currently only affects food and refuse, other items made of this material will not rot. + */ +"Rots" | +/** + * In most living creatures, it controls many bodily functions and movements by sending signals around the body. See: Nervous tissue + */ +"NervousTissue" | +/** + * Tells the game to classify contaminants of this material as being "blood" in Adventurer mode tile descriptions ("Here we have a Dwarf in a slurry of blood."). + */ +"BloodMapDescriptor" | +/** + * Tells the game to classify contaminants of this material as being "ichor". + */ +"IchorMapDescriptor" | +/** + * Tells the game to classify contaminants of this material as being "goo". + */ +"GooMapDescriptor" | +/** + * Tells the game to classify contaminants of this material as being "slime". + */ +"SlimeMapDescriptor" | +/** + * Tells the game to classify contaminants of this material as being "pus". + */ +"PusMapDescriptor" | +/** + * Tells the game to classify contaminants of this material as being "sweat". + */ +"SweatMapDescriptor" | +/** + * Tells the game to classify contaminants of this material as being "tears". + */ +"TearsMapDescriptor" | +/** + * Tells the game to classify contaminants of this material as being "spit". + */ +"SpitMapDescriptor" | +/** + * Contaminants composed of this material evaporate over time, slowly disappearing from the map. Used internally by water. + */ +"Evaporates" | +/** + * Used for materials which cause syndromes, causes it to enter the creature's blood instead of simply spattering on the surface. + */ +"EntersBlood" | +/** + * Can be eaten by vermin. + */ +"EdibleVermin" | +/** + * Can be eaten raw. + */ +"EdibleRaw" | +/** + * Can be cooked and then eaten. + */ +"EdibleCooked" | +/** + * Prevents globs made of this material from being cleaned up and destroyed. + */ +"DoNotCleanGlob" | +/** + * Prevents the material from showing up in Stone stockpile settings. + */ +"NoStoneStockpile" | +/** + * The material can be made into minecarts, wheelbarrows, and stepladders at the metalsmith's forge. + */ +"ItemsMetal" | +/** + * Equivalent to `ITEMS_HARD`. Given to bone. + */ +"ItemsBarred" | +/** + * Equivalent to `ITEMS_HARD`. Given to shell. + */ +"ItemsScaled" | +/** + * Equivalent to `ITEMS_SOFT`. Given to leather. + */ +"ItemsLeather" | +/** + * The material can be made into clothing, amulets, bracelets, earrings, backpacks, and quivers, contingent + * on which workshops accept the material. Given to plant fiber, silk and wool. + */ +"ItemsSoft" | +/** + * The material can be made into furniture, crafts, mechanisms, and blocks, contingent on which workshops accept the material. + * Random crafts made from this material include all seven items. Given to stone, wood, bone, shell, chitin, claws, teeth, + * horns, hooves and beeswax. Hair, pearls and eggshells also have the tag. + */ +"ItemsHard" | +/** + * Used to define that the material is a stone. Allows its usage in masonry and stonecrafting and storage in stone stockpiles, among other effects. + */ +"IsStone" | +/** + * Defines the material is a ceramic. + */ +"IsCeramic" | +/** + * Used for a stone that cannot be dug into. + */ +"Undiggable" | +/** + * Causes containers made of this material to be prefixed with "unglazed" if they have not yet been glazed. + */ +"DisplayUnglazed" | +/** + * Classifies the material as yarn, allowing its use for clothiers and its storage in cloth stockpiles under "Thread (Yarn)" and "Cloth (Yarn)". + */ +"Yarn" | +/** + * Classifies the material as metal thread, permitting thread and cloth to be stored in cloth stockpiles under "Thread (Metal)" and "Cloth (Metal)". + */ +"StockpileThreadMetal" | +/** + * Defines the material as being metal, allowing it to be used at forges. + */ +"IsMetal" | +/** + * Used internally by green glass, clear glass, and crystal glass. Appears to only affect the `GLASS_MATERIAL` reaction token. Does not cause the game + * to treat the material like glass, i.e being referred to as "raw" instead of "rough" in its raw form or being displayed in the "glass" trade/embark category. + */ +"IsGlass" | +/** + * Can be used in the production of crystal glass. + */ +"CrystalGlassable" | +/** + * Melee weapons can be made out of this material. + */ +"ItemsWeapon" | +/** + * Ranged weapons can be made out of this material. + */ +"ItemsWeaponRanged" | +/** + * Anvils can be made out of this material. + */ +"ItemsAnvil" | +/** + * Ammunition can be made out of this material. + */ +"ItemsAmmo" | +/** + * Picks can be made out of this material. + */ +"ItemsDigger" | +/** + * Armor can be made out of this material. + */ +"ItemsArmor" | +/** + * Used internally by amber and coral. Functionally equivalent to `ITEMS_HARD`. + */ +"ItemsDelicate" | +/** + * Siege engine parts can be made out of this material. Does not appear to work. + */ +"ItemsSiegeEngine" | +/** + * Querns and millstones can be made out of this material. Does not appear to work. + */ +"ItemsQuern" | +/** + * An unknown token + */ +"Unknown" + +/** + * Represents the mechanical properties of a material via the yield, fracture, and elasticity + */ +export type MechanicalProperties = { yield: number; fracture: number; elasticity: number } + +/** + * The `RawMetadata` struct represents metadata about a raw module in Rust, including its name, + * version, file path, identifier, object type, module location, and visibility status. + * + * Properties: + * + * * `module_name`: The name of the raw module the raw is from. + * * `module_version`: The version of the raw module the raw is from. + * * `raw_file_path`: The `raw_file_path` property is a string that represents the path to the file + * containing the raw data. It specifies the location of the file on the file system. + * * `raw_identifier`: The raw identifier is a unique identifier for the raw data. It is typically + * found at the top of the raw text file and is used to identify and reference the specific raw data. + * * `object_type`: The `object_type` property represents the type of the raw data. It could be a + * creature, plant, or any other type specified in the raw text file. + * * `raw_module_location`: The `raw_module_location` property represents the location of the owning + * raw module. It can have one of the following values: + * + * - `RawModuleLocation::InstalledMods`: The raw module is located in the `installed_mods` folder. + * - `RawModuleLocation::Mods`: The raw module is located in the `mods` folder. + * - `RawModuleLocation::Vanilla`: The raw module is located in the `vanilla` folder. + * + * * `hidden`: The `hidden` property is a boolean value that indicates whether the raw metadata should + * be hidden or not when exporting. By default, it is set to `true`, meaning that the raw metadata will + * be hidden unless specified in the `ParsingOptions` struct. + */ +export type Metadata = { moduleObjectId: string; moduleName: string; moduleVersion: string; rawFilePath: string; rawIdentifier: string; objectType: ObjectType; rawModuleLocation: RawModuleLocation } + +/** + * How often a creature can be milked and what material it produces + */ +export type Milkable = { material: string; frequency: number } + +/** + * A struct representing a modification to a creature + */ +export type ModificationTag = +/** + * `COPY_TAGS_FROM` tag + */ +{ CopyTagsFrom: { +/** + * The creature to copy tags from + */ +identifier: string } } | +/** + * `APPLY_CREATURE_VARIATION` tag + */ +{ ApplyCreatureVariation: { +/** + * The creature to apply the variation from + */ +identifier: string } } | +/** + * Follows `GO_TO_END` until `GO_TO_START` or object definition finishes + * + * When using tags from an existing creature, inserts new tags at the end of the creature. + */ +{ AddToEnding: { +/** + * The set of raws to add to the end of the object + * + * This should be the entire raw in order to apply. + */ +raws: string[] } } | +/** + * Follows `GO_TO_START` until `GO_TO_END` or object definition finishes + * + * When using tags from an existing creature, inserts new tags at the beginning of the creature. + */ +{ AddToBeginning: { +/** + * The set of raws to add to the beginning of the object + * + * This should be the entire raw in order to apply. + */ +raws: string[] } } | +/** + * `GO_TO_TAG:tag` raw instruction + * + * When using tags from an existing creature, inserts new tags before the specified tag. + */ +{ AddBeforeTag: { +/** + * The tag to insert before + * + * Since we don't actually know the tag order after parsing, this will be ignored in parsing, and + * instead will just apply the raws... + */ +tag: string; +/** + * The set of raws to add before the tag + * + * This should be the entire raw in order to apply. + */ +raws: string[] } } | +/** + * The main body of the object + */ +{ MainRawBody: { +/** + * The set of raws that make up the object. This is usually defined first unless + * its specified to be added to the end or beginning (or before a tag) + * + * This should be the entire raw in order to apply. + */ +raws: string[] } } + +/** + * A name with a singular, plural, and adjective form + */ +export type Name = { singular: string; plural: string; adjective: string | null } + +/** + * The various types of objects that are within the raw files. + */ +export type ObjectType = +/** + * A creature + */ +"Creature" | +/** + * An inorganic material + */ +"Inorganic" | +/** + * A plant + */ +"Plant" | +/** + * An item + */ +"Item" | +/** + * An item of type ammo + */ +"ItemAmmo" | +/** + * An item of type armor + */ +"ItemArmor" | +/** + * An item of type food + */ +"ItemFood" | +/** + * An item of type gloves + */ +"ItemGloves" | +/** + * An item of type helm + */ +"ItemHelm" | +/** + * An item of type instrument + */ +"ItemInstrument" | +/** + * An item of type pants + */ +"ItemPants" | +/** + * An item of type shield + */ +"ItemShield" | +/** + * An item of type shoes + */ +"ItemShoes" | +/** + * An item of type siege ammo + */ +"ItemSiegeAmmo" | +/** + * An item of type tool + */ +"ItemTool" | +/** + * An item of type toy + */ +"ItemToy" | +/** + * An item of type trap component + */ +"ItemTrapComponent" | +/** + * An item of type weapon + */ +"ItemWeapon" | +/** + * A building + */ +"Building" | +/** + * A workshop building + */ +"BuildingWorkshop" | +/** + * A furnace building + */ +"BuildingFurnace" | +/** + * A reaction + */ +"Reaction" | +/** + * Graphics + */ +"Graphics" | +/** + * A material template + */ +"MaterialTemplate" | +/** + * A body detail plan + */ +"BodyDetailPlan" | +/** + * A body + */ +"Body" | +/** + * An entity + */ +"Entity" | +/** + * A language + */ +"Language" | +/** + * A translation + */ +"Translation" | +/** + * A tissue template + */ +"TissueTemplate" | +/** + * A creature variation + */ +"CreatureVariation" | +/** + * A text set + */ +"TextSet" | +/** + * A tile page + */ +"TilePage" | +/** + * A descriptor color + */ +"DescriptorColor" | +/** + * A descriptor pattern + */ +"DescriptorPattern" | +/** + * A descriptor shape + */ +"DescriptorShape" | +/** + * A palette + */ +"Palette" | +/** + * Music + */ +"Music" | +/** + * Sound + */ +"Sound" | +/** + * An interaction + */ +"Interaction" | +/** + * An unknown object type + */ +"Unknown" | +/** + * `SelectCreature` tag + */ +"SelectCreature" | +/** + * A creature caste + */ +"CreatureCaste" + +/** + * # Parsing Options + * + * Specify what to parse and where to parse it from. + * + * ## Parsing `info.txt` vs the raw files + * + * There are two main parsing functions: `parse` and `parse_module_info_files`. + * + * Both use the same options struct, but they use it in different ways. + * + * When calling `parse`, the `ParserOptions` struct is used to specify what raws to parse and where to parse them from. + * Any specified `raw_modules_to_parse` will not be parsed in the `parse` function, and the only items parsed in the + * `parse_module_info_files` function are the `module_info_files_to_parse`. + * + * ## Example + * + * ```rust + * use std::path::PathBuf; + * use dfraw_parser::metadata::{ParserOptions, ObjectType, RawModuleLocation}; + * use dfraw_parser::traits::RawObject; + * + * let mut options = ParserOptions::new(); + * options.add_location_to_parse(RawModuleLocation::Vanilla); + * // Clear the default object types + * options.set_object_types_to_parse(vec![]); + * // Add back in the ones we want + * options.add_object_type_to_parse(ObjectType::Creature); + * options.add_object_type_to_parse(ObjectType::CreatureVariation); + * // Include the metadata with the parsed raws + * options.attach_metadata_to_raws(); + * + * // Then you could parse the raws and info.txt files + * // let parsed_raws = dfraw_parser::parse(&options); + * ``` + * + */ +export type ParserOptions = { +/** + * Whether to attach a metadata field to the raws. + * If true, all raws will have a `metadata` field which shows information about the + * raw file, its path, its module, and its parent directory. + * + * Default: false. + */ +attachMetadataToRaws: boolean; +/** + * Whether to skip the "copy tags from" resolution step. + * If true, the creature will have a populated `copy_tags_from` field instead. + * + * Default: false. + */ +skipApplyCopyTagsFrom: boolean; +/** + * Whether to skip the apply "creature variations" resolution step. + * When this is true, it will just leave the variations attached to the creature + * in a `creature_variations` field. + * If false, it will modify the creature data to include the variations. + * + * Note: This is currently not implemented. + * + * Default: false. + */ +skipApplyCreatureVariations: boolean; +/** + * What types of raws to parse. If this is left empty, all parsable raws will be parsed. + * + * Default: `[Creature, CreatureVariation, Entity, Plant, Inorganic, MaterialTemplate, Graphics, TilePage]` + */ +objectTypesToParse: ObjectType[]; +/** + * What locations to parse raws from. If this is left empty, no locations will be parsed. + * + * Setting locations to parse requires a valid `dwarf_fortress_directory` to be set. + * + * Default: None + */ +locationsToParse: RawModuleLocation[]; +/** + * The paths to the locations used for parsing: the Dwarf Fortress installation directory and the + * Dwarf Fortress user data directory. + * + * This can be automatically gathered or explicitly set. + * + * Default: Attempted to be automatically gathered. + */ +locations: LocationHelper; +/** + * Optionally specify one or more `legends_plus` exports to parse in addition to the raws. + * These exports include information about generated creatures which are not included in the + * raws. + * + * Default: None + */ +legendsExportsToParse: string[]; +/** + * Optionally specify one or more raw files to parse directly. These should be the raw files + * themselves, not the containing directory. + * + * (e.g. `creature_standard.txt` in `data/vanilla/vanilla_creatures/objects/`) + * + * Note that these will be parsed in addition to the raws in the specified locations in the other + * options. That means that if you specify a raw file that is also in the vanilla raws, it will + * be parsed twice (if vanilla is in the locations to parse). + * + * Default: None + */ +rawFilesToParse: string[]; +/** + * Optionally specify one or more raw modules to parse directly. These should be the module + * directories, not the info.txt file. + * + * (e.g. `vanilla_creatures` in `data/vanilla/`) + * + * Note that these will be parsed in addition to the raws in the specified locations in the other + * options. That means that if you specify a module that is also in the vanilla raws, it will + * be parsed twice (if vanilla is in the locations to parse). + * + * Default: None + */ +rawModulesToParse: string[]; +/** + * Optionally specify one or more module info files to parse directly. These should be the info.txt + * files themselves, not the containing directory. + * + * (e.g. `info.txt` in `data/vanilla/vanilla_creatures/`) + * + * Note that if you are calling the `parse` function, this will be ignored. This is only used + * when calling the `parse_module_info_files` function. + */ +moduleInfoFilesToParse: string[]; +/** + * Include a summary of what was parsed in the log. + * + * If running with `tauri`, this will emit a `PARSE_SUMMARY` event with the summary as well. + * + * Default: false + */ +logSummary: boolean; +/** + * Log warnings about the format of the info.txt file. + * + * Typically this includes non-integer "before version" tags or other format errors which Dwarf Fortress + * will ignore/do its best to parse. They tend to not prevent the module to work, but they are technically + * incorrectly formatted. This would mostly be useful for mod authors to check. + * + * Default: false + */ +includeWarningsForInfoFileFormat: boolean } + +/** + * A struct representing a plant + */ +export type Plant = { +/** + * Common Raw file Things + */ +metadata: Metadata | null; identifier: string; objectId: string; name: Name; prefStrings: string[] | null; tags: PlantTag[] | null; +/** + * Default [0, 0] (aboveground) + */ +undergroundDepth: [number, number] | null; +/** + * Default frequency is 50 + */ +frequency: number | null; +/** + * List of biomes this plant can grow in + */ +biomes: BiomeTag[] | null; +/** + * Growth Tokens define the growths of the plant (leaves, fruit, etc.) + */ +growths: PlantGrowth[] | null; +/** + * If plant is a tree, it will have details about the tree. + */ +treeDetails: Tree | null; +/** + * If plant is a shrub, it will have details about the shrub. + */ +shrubDetails: Shrub | null; materials: Material[] | null } + +/** + * The graphic of the tile + */ +export type PlantGraphicTemplateTag = +/** + * The standard leaves + */ +"standardLeaves" | +/** + * The standard fruit 1 + */ +"standardFruit1" | +/** + * The standard fruit 2 + */ +"standardFruit2" | +/** + * The standard fruit 3 + */ +"standardFruit3" | +/** + * The standard fruit 4 + */ +"standardFruit4" | +/** + * The standard flowers 1 + */ +"standardFlowers1" | +/** + * The standard flowers 2 + */ +"standardFlowers2" | +/** + * The standard flowers 3 + */ +"standardFlowers3" | +/** + * The standard flowers 4 + */ +"standardFlowers4" + +/** + * A struct representing a plant growth + */ +export type PlantGrowth = { +/** + * Plant growths are not given an identifier, since they are just supporting + * data for the plant definition. They are defined instead by the type of growth. + */ +growthType: PlantGrowthTypeTag; +/** + * The name of the growth. This is actually defined with `GROWTH_NAME` key in the raws. + */ +name: Name; +/** + * The item grown by this growth. This is actually defined with `GROWTH_ITEM` key in the raws. + * This is a string until we make a proper item structure. Technically there are 2 arguments: + * 1. item token, 2: material token. Generally the item type should be `PLANT_GROWTH:NONE`. + */ +item: string; +/** + * Specifies on which part of the plant this growth grows. This is defined with `GROWTH_HOST_TILE` key. + * This can be unused, like in the case of crops where the plant is the growth (I think?). + */ +hostTiles: PlantPartTag[] | null; +/** + * Controls the height on the trunk above which the growth begins to appear. + * The first value is the percent of the trunk height where the growth begins appearing: + * 0 will cause it along the entire trunk (above the first tile), 100 will cause it to appear + * at the topmost trunk tile. Can be larger than 100 to cause it to appear above the trunk. + * The second value must be -1, but might be intended to control whether it starts height counting + * from the bottom or top. + */ +trunkHeightPercentage: [number, number] | null; +/** + * Currently has no effect. + */ +density: number | null; +/** + * Specifies the appearance of the growth. This is defined with `GROWTH_PRINT` key. + * This is a string until we make a proper print structure. + */ +print: string | null; +/** + * Specifies at which part of the year the growth appears. Default is all year round. + * Minimum: 0, Maximum: `402_200`. This is defined with `GROWTH_TIMING` key. + */ +timing: [number, number] | null; +/** + * Where we gather some of the growth's tags. + */ +tags: PlantGrowthTag[] | null } + +/** + * The growth tag of a plant + */ +export type PlantGrowthTag = +/** + * The beginning of a growth tag + */ +"Growth" | +/** + * The name of the growth + */ +"GrowthName" | +/** + * The item from the growth + */ +"GrowthItem" | +/** + * The host tile the growth grows on + */ +"GrowthHostTile" | +/** + * The trunk height percent of the growth + */ +"GrowthTrunkHeightPercent" | +/** + * The growth density + */ +"GrowthDensity" | +/** + * The growth timing + */ +"GrowthTiming" | +/** + * The growth print + */ +"GrowthPrint" | +/** + * If the growth has a seed + */ +"GrowthHasSeed" | +/** + * If the growth drops off the plant + */ +"GrowthDropsOff" | +/** + * If the growth drops off the plant and there is no cloud + */ +"GrowthDropsOffNoCloud" | +/** + * An unknown growth tag + */ +"Unknown" + +/** + * The types of growths + */ +export type PlantGrowthTypeTag = +/** + * The growth is a leaf + */ +"Leaves" | +/** + * The growth is a flower cluster + */ +"Spathes" | +/** + * The growth is a fruit + */ +"Fruit" | +/** + * The growth is a flower + */ +"Flowers" | +/** + * The growth is a nut + */ +"Nut" | +/** + * The growth is a seed catkin + */ +"SeedCatkins" | +/** + * The growth is a pollen catkin + */ +"PollenCatkins" | +/** + * The growth is a cone + */ +"Cone" | +/** + * The growth is a seed cone + */ +"SeedCone" | +/** + * The growth is a pollen cone + */ +"PollenCone" | +/** + * The growth is a feather + */ +"Feathers" | +/** + * The growth is an egg + */ +"Eggs" | +/** + * The growth is a pod + */ +"Pod" | +/** + * An unknown growth type + */ +"None" + +/** + * Parts of a plant + */ +export type PlantPartTag = +/** + * Twigs + */ +"Twigs" | +/** + * Branches + */ +"Branches" | +/** + * Branches and twigs + */ +"BranchesAndTwigs" | +/** + * All branches and twigs + */ +"AllBranchesAndTwigs" | +/** + * Heavy branches + */ +"HeavyBranches" | +/** + * Heavy branches and twigs + */ +"HeavyBranchesAndTrunk" | +/** + * Trunk + */ +"Trunk" | +/** + * Roots + */ +"Roots" | +/** + * Cap (canopy) + */ +"Cap" | +/** + * Sapling + */ +"Sapling" | +/** + * An unknown part of the plant + */ +"Unknown" + +/** + * The tags of a plant + */ +export type PlantTag = +/** + * The plant grows in dry conditions + */ +"Dry" | +/** + * The plant grows in evil conditions + */ +"Evil" | +/** + * The plant grows in good conditions + */ +"Good" | +/** + * The plant grows in savage conditions + */ +"Savage" | +/** + * The plant grows in wet conditions + */ +"Wet" | +/** + * The plant grows at a specific underground depth + */ +"UndergroundDepth" | +/** + * The plant grows in a specific biome + */ +"Biome" | +/** + * The frequency of the plant + */ +"Frequency" | +/** + * The material template to use for the plant + */ +"UseMaterialTemplate" | +/** + * The basic material to use for the plant + */ +"BasicMaterial" | +/** + * The material to use for the plant + */ +"UseMaterial" | +/** + * The material of the plant + */ +"Material" | +/** + * What dwarves prefer about the plant + */ +"PrefString" | +/** + * All names of the plant + */ +"AllNames" | +/** + * The adjective name of the plant + */ +"NameAdjective" | +/** + * The plural name of the plant + */ +"NamePlural" | +/** + * The singular name of the plant + */ +"NameSingular" | +/** + * An unknown plant tag + */ +"Unknown" + +/** + * Represents a position in the government of an entity + */ +export type Position = { identifier: string; allowedClasses: string[] | null; allowedCreatures: string[] | null; appointedBy: string | null; color: Color | null; commander: string | null; demandMax: number | null; executionSkill: string | null; gender: string | null; landHolder: number | null; landName: string | null; mandateMax: number | null; name: Name | null; nameMale: Name | null; nameFemale: Name | null; number: number | null; precedence: number | null; rejectedClasses: string[] | null; rejectedCreatures: string[] | null; replacedBy: string | null; requiredBedroom: number | null; requiredBoxes: number | null; requiredCabinets: number | null; requiredDining: number | null; requiredOffice: number | null; requiredRacks: number | null; requiredStands: number | null; requiredTomb: number | null; requiresPopulation: number | null; responsibilities: string[] | null; spouse: Name | null; spouseFemale: Name | null; spouseMale: Name | null; squad: string | null; succession: string | null; tags: PositionTag[] } + +/** + * Represents a position token + */ +export type PositionTag = +/** + * The position holder is not subjected to the economy. Less than relevant right now. + */ +"AccountExempt" | +/** + * Arguments: creature class token + * + * Only creatures with the specified class token can be appointed to this position. Multiple entries are allowed + */ +"AllowedClass" | +/** + * Arguments: creature:caste token + * + * Restricts the position to only the defined caste. Only works with a caste of the entity's current race. + * (If the entity had multiple CREATURE: tokens). Multiple entries are allowed + */ +"AllowedCreature" | +/** + * Arguments: position + * + * This position can only be chosen for the task from the nobles screen, and is available only if there is an *argument* present. + * For example, the `GENERAL` is `[APPOINTED_BY:MONARCH]`. Contrast `[ELECTED]`. Being appointed by a `MONARCH` seems to handle a lot of + * worldgen stuff, and interferes with fort mode titles. Multiple entries are allowed. If you have neither an `ELECTED`-token nor a + * `APPOINTED_BY`-token, the holder may always be changed (like the expedition leader) + */ +"AppointedBy" | +/** + * A creature that kills a member of this position will be sure to talk about it a lot. + */ +"BragOnKill" | +/** + * In adventure mode, when referencing locations, an NPC may mention this position holder living there or having done some + * deed there, it also means that the position exists in world-gen, rather than being created only at the end of world-gen. + * + * Before 47.05, Dark Fortress civs cannot have this tag on anybody but their Law Maker, or the game will crash without + * leaving an errorlog. + */ +"ChatWorthy" | +/** + * Arguments: color:background:foreground + * + * Creatures of this position will have this color, instead of their profession color + * + * e.g. `[COLOR:5:0:1]`. + */ +"Color" | +/** + * Arguments: position, 'ALL' + * + * This position will act as a commander of the specified position. + * + * E.g. GENERAL is `[COMMANDER:LIEUTENANT:ALL]`. Unknown if values other than ALL work. Multiple entries are allowed + */ +"Commander" | +/** + * This position is a puppet ruler left behind in a conquered site. + */ +"ConqueredSite" | +/** + * Arguments: number (0-`100`) + * + * How many demands the position can make of the population at one time. + */ +"DemandMax" | +/** + * The site's (or civ's) minted coins, if any, will have images that reflect the personality of this position holder. + */ +"DeterminesCoinDesign" | +/** + * The position won't be culled from Legends as "unimportant" during world generation. + */ +"DoNotCull" | +/** + * Members of this position will never agree to 'join' your character during adventure mode. + */ +"DutyBound" | +/** + * The population will periodically select the most skill-eligible creature to fill this position for site-level positions + * at the player's fort. For responsibilities or positions that use more than one skill, no skill takes priority in electing + * a creature: an accomplished comedian is more qualified for the TRADE responsibility than a skilled appraiser. + * A creature may be elected to multiple positions at the same time. Contrast `[APPOINTED_BY]`. More info: Elections + */ +"Elected" | +/** + * Arguments: weapon skill + * + * A mandatory sub-tag of `[RESPONSIBILITY:EXECUTIONS]`. Determines the weapon chosen by the executioner for their work. + */ +"ExecutionSkill" | +/** + * The various members who have filled this role will be listed in the civilization's history. + */ +"ExportedInLegends" | +/** + * The creature holding this position will visibly flash, like legendary citizens. Represents a properly noble station by default. + */ +"Flashes" | +/** + * Arguments: 'MALE' or 'FEMALE' + * + * The position can only be held by the specified gender. Currently bugged Bug:2714 + */ +"Gender" | +/** + * The position can assign quests to adventurers. + */ +"KillQuest" | +/** + * Arguments: importance tier (1-`10`) + * + * This is an alternative to `SITE`. What it does is allow positions to be created at civ-level 'as needed' for all sites that + * meet the requirements to have them, which are the values set in `LAND_HOLDER_TRIGGER`. The character is tied permanently to + * a particular site but also operates at the civ-level. Since 50* modded levels of higher than 3 are possible. + */ +"LandHolder" | +/** + * Arguments: name (a string) + * + * The name the area takes on when under the control of a `LAND_HOLDER`. + * + * E.g. for the DUKE, `[LAND_NAME:a duchy]`. + * + * If the position is not a `LAND_HOLDER`, the `land_name` is still displayed left of the position in the nobles menu. + */ +"LandName" | +/** + * Arguments: number (0-`100`) + * + * The maximum number of mandates the position can make at once. + */ +"MandateMax" | +/** + * The position holder cannot be assigned labors. Currently nonfunctional.Bug:3721 + */ +"MenialWorkExemption" | +/** + * The spouse of the position holder doesn't have to work, either - see above. + */ +"MenialWorkExemptionSpouse" | +/** + * This position cannot be appointed from the nobles screen. Intended for militia captains and other squad leaders to reduce clutter. Currently nonfunctionalBug:8965 + */ +"MilitaryScreenOnly" | +/** + * Arguments: `SingPlurName` + * + * The name of the position. + */ +"Name" | +/** + * Arguments: `SingPlurName` + * + * If the creature holding the position is male, this is the position's name. + * + * E.g. for MONARCH, `[NAME_MALE:king:kings]` + */ +"NameMale" | +/** + * Arguments: `SingPlurName` + * + * If the creature holding the position is female, this is the position's name. + * + * E.g. for MONARCH, `[NAME_FEMALE:queen:queens]` + */ +"NameFemale" | +/** + * arguments: description + * + * Description of this position in the nobles screen. + */ +"Description" | +/** + * Arguments: number or `AS_NEEDED` + * + * How many of the position there should be. If the `[SITE]` token exists, this is per site, otherwise this is per civilization. + * + * `AS_NEEDED` applies only to positions involved with the military command chain; this is used to allow armies to expand to + * whatever size they need to be. Non-military positions with `NUMBER:AS_NEEDED` will not be appointed. + * The problem with Lieutenants and Captains not been created, is their `AS_NEEDED` number. + * They are only then created when the're needed, and that has some pretty unusual conditions. + * When a fixed number is used, they are appointed with the creation of the civ. + */ +"Number" | +/** + * Arguments: number (0 - `30_000`) or 'NONE' + * + * How important the position is in society; a lower number is more important and displayed higher in the Nobles menu. + * For `MONARCH` it's 1, for `MILITIA_CAPTAIN` it's 200. The game just assumes that anything with `[PRECEDENCE:1]` is the ruler, + * for both embark screen and mountain home purposes. + * + * A civ-position will also be created without precedence. Positions may have the same precedence and will be appointed, + * although the effect is unknown. + */ +"Precedence" | +/** + * The position holder will not be held accountable for his or her crimes. Currently nonfunctional. + */ +"PunishmentExemption" | +/** + * The position holder can give quests in Adventure mode. Functionality in 0.31.13 and later is uncertain. + */ +"QuestGiver" | +/** + * Arguments: creature class token + * + * Creatures of the specified class cannot be appointed to this position. Multiple entries are allowed + */ +"RejectedClass" | +/** + * Arguments: `creature:caste` token + * + * Restricts position holders by `CREATURE` type. Multiple entries are allowed + */ +"RejectedCreature" | +/** + * Arguments: position + * + * This position is absorbed by another down the line. For example, expedition leader is `[REPLACED_BY:MAYOR]`. + * Only a single entry is allowed. + */ +"ReplacedBy" | +/** + * Arguments: number (0 - `10_000_000`) + * + * The position holder requires a bedroom with at least this value. + */ +"RequiredBedroom" | +/** + * Arguments: number (0 - `100`) + * + * The position holder requires at least this many boxes. + */ +"RequiredBoxes" | +/** + * Arguments: number (0 - `100`) + * + * The position holder requires at least this many cabinets. + */ +"RequiredCabinets" | +/** + * Arguments: number (0 - `10_000_000`) + * + * The position holder requires a dining room with at least this value. + */ +"RequiredDining" | +/** + * Arguments: number (0 - `10_000_000`) + * + * The position holder requires an office with at least this value. + */ +"RequiredOffice" | +/** + * Arguments: number (0 - `100`) + * + * The position holder requires at least this many weapon racks. + */ +"RequiredRacks" | +/** + * Arguments: number (0 - `100`) + * + * The position holder requires at least this many armour stands. + */ +"RequiredStands" | +/** + * Arguments: number (0 - `10_000_000`) + * + * The position holder requires a tomb with at least this value. + */ +"RequiredTomb" | +/** + * Does not have anything directly to do with markets. It means that in minor sites (such as hillocks) the position will not + * appear, while in major sites (such as dwarf fortresses) it will. + */ +"RequiresMarket" | +/** + * Arguments: number + * + * The position requires the population to be at least this number before it becomes available, or before the position holder + * will move in. + */ +"RequiresPopulation" | +/** + * Arguments: responsibility + * + * The position holder does a thing. See the table below for suitable arguments. + * + * A position does not need to have a responsibility. + */ +"Responsibility" | +/** + * If there is a special location set aside for rulers, such as a human castle/mead hall, the position holder will always be + * found at that particular location. Does nothing for dwarven nobles, because at present, dwarves have no such special locations. + */ +"RulesFromLocation" | +/** + * Every site government will have the defined number of this position instead of the whole civilization; provided that other + * criteria (if any) are met. Unless `LAND_HOLDER` is present instead, the defined number of the position will be created only + * for the civilization as a whole. + */ +"Site" | +/** + * The position holder will get upset if someone with a higher `PRECEDENCE` holds quarters with a greater value than their own. + */ +"SleepPretension" | +/** + * The civilization will inter the corpse of the position holder in a special grave, either in catacombs or in monuments. + * If that grave is disturbed, the position holder can return as a mummy. + */ +"SpecialBurial" | +/** + * Arguments: `SingPlurName` + * + * The name of the position holder's spouse. + */ +"Spouse" | +/** + * Arguments: `SingPlurName` + * + * If the spouse of the creature holding the position is female, this is the spouse's position name. + */ +"SpouseFemale" | +/** + * Arguments: `SingPlurName` + * + * If the spouse of the creature holding the position is male, this is the spouse's position name. + */ +"SpouseMale" | +/** + * Arguments: `number:SingPlurName` + * + * The position holder is authorized to form a military squad, led by themselves using the leader and military tactics skills. + * The number denotes the maximum headcount. The noun used to describe the subordinates (e.g. royal guard) is used in adventure + * mode for the adventurer. + */ +"Squad" | +/** + * Arguments: `BY_HEIR` or `BY_POSITION:position` + * + * How a new position holder is chosen. A single position can have multiple `BY_POSITION` tokens. + * See Noble for more information on how succession is handled in the game. + */ +"Succession" | +/** + * An uknow token. + */ +"Unknown" + +/** + * Raws are part of modules since 50.xx. Raw modules are loaded from 3 common locations: + * `{df_directory}/data/vanilla`, `{df_directory}/mods`, and `{df_directory/data/installed_mods}` + */ +export type RawModuleLocation = +/** + * The "installed" mods directory + */ +"InstalledMods" | +/** + * The "downloaded" mods directory + */ +"WorkshopMods" | +/** + * The vanilla data file location + */ +"Vanilla" | +/** + * An unknown location + */ +"Unknown" | +/** + * Used for handling legends exported files + */ +"LegendsExport" + +/** + * A struct purely for providing type hinting when working with parsed raws (as JSON) in typescript. + */ +export type RawObject = { +/** + * The object identifier + */ +identifier: string; +/** + * The metadata for this raw (includes the `ObjectType`, `RawModuleLocation` and other module info) + */ +metadata: Metadata } + +/** + * A carrier struct for passing the database id along with the object we retrieved. + */ +export type ResultWithId = { +/** + * id of the object in the database on its respective table + */ +id: string; +/** + * the object retrieved + */ +data: T } + +/** + * A query for searching raw objects in the database. + */ +export type SearchQuery = { +/** + * A general text search string for names and descriptions. + */ +searchString: string | null; +/** + * Search specifically for an identifier (exact or partial). + */ +identifierQuery: string | null; +/** + * Limit search to raws found within these locations + */ +locations: RawModuleLocation[]; +/** + * Limit search to only be raws of this type + */ +rawTypes: ObjectType[]; +/** + * Used to return only results with these token flags + * + * These should be the keys (from `to_keys`) on `CreatureTag`, `CasteTag`, `PlantTag`, etc. + * (e.g. `FLIER`, `EGG_LAYER`, `FIREIMMUNE`) + */ +requiredFlags: string[]; +/** + * Used to return only results with these token-value pairings + * + * These should be the keys (from `to_keys`) on `CreatureTag`, `CasteTag`, `PlantTag`, etc. + * (e.g. `LITTER_SIZE`, `POP_RATIO`, `CLUSTER_NUMBER`) + * + * The value provided will be used for (minimum/exact value, maximum value) + */ +numericFilters: ([string, number, number | null])[]; +/** + * Limit the number of raws returned to this amount per page + * + * Default: `50` + */ +limit: number; +/** + * Which page to return + * + * Default: `1` + */ +page: number } + +/** + * A structured response for search operations, containing the requested page of data + * and the total count of matches in the database. + */ +export type SearchResults = { +/** + * The page of results found. + */ +results: ResultWithId[]; +/** + * The total number of matches in the database (ignoring pagination limits). + */ +totalCount: number } + +/** + * The tokens for the seasons + */ +export type SeasonTag = +/** + * The spring season + */ +"Spring" | +/** + * The summer season + */ +"Summer" | +/** + * The autumn season + */ +"Autumn" | +/** + * The winter season + */ +"Winter" | +/** + * An unknown season + */ +"Unknown" + +/** + * A struct representing a seed material + */ +export type SeedMaterial = { name: Name; color: Color; material: string } + +/** + * A struct representing a creature selection + */ +export type SelectCreature = { metadata: Metadata | null; identifier: string; objectId: string; tags: string[] } + +/** + * The rules for selecting a creature + */ +export type SelectCreatureRuleTag = +/** + * Selects a previously defined caste + */ +{ SelectCaste: string } | +/** + * Selects a locally defined material. Can be ALL. + */ +{ SelectMaterial: string } | +/** + * Selects a tissue for editing. + */ +{ SelectTissue: string } | +/** + * Adds an additional previously defined caste to the selection. Used after `[SELECT_CASTE]`. + */ +{ SelectAdditionalCaste: string } + +/** + * A shrub in the raws. + */ +export type Shrub = { +/** + * Allows the plant to grow in farm plots during the given season. + * If the plant is a surface plant, allows it to grow in the wild during this season; wild surface plants without + * this token will disappear at the beginning of the season. Underground plants grow wild in all seasons, regardless + * of their season tokens. + * Default: empty (plant will not grow in farm plots) + */ +growingSeason: SeasonTag[] | null; +/** + * How long the plant takes to grow to harvest in a farm plot. Unit hundreds of ticks. + * There are 1008 GROWDUR units in a season. Defaults to 300. + */ +growDuration: number | null; +/** + * Has no known effect. Previously set the value of the harvested plant. + */ +value: number | null; +/** + * The tile used when the plant is harvested whole, or is ready to be picked from a farm plot. May either be a cp437 + * tile number, or a character between single quotes. See character table. Defaults to 231 (τ). + */ +pickedTile: number | null; +/** + * The tile used when a plant harvested whole has wilted. Defaults to 169 (⌐). + */ +deadPickedTile: number | null; +/** + * The tile used to represent this plant when it is wild, alive, and has no growths. Defaults to 34 ("). + */ +shrubTile: number | null; +/** + * The tile used to represent this plant when it is dead in the wild. Defaults to 34 ("). + */ +deadShrubTile: number | null; +/** + * The maximum stack size collected when gathered via herbalism (possibly also from farm plots?). Defaults to 5. + */ +clusterSize: number | null; +/** + * The color of the plant when it has been picked whole, or when it is ready for harvest in a farm plot. Defaults to 2:0:0 (dark green). + */ +pickedColor: Color | null; +/** + * The color of the plant when it has been picked whole, but has wilted. Defaults to 0:0:1 (dark gray). + */ +deadPickedColor: Color | null; +/** + * The color of the plant when it is alive, wild, and has no growths. Defaults to 2:0:0 (dark green). + */ +shrubColor: Color | null; +/** + * The color of the plant when it is dead in the wild. Defaults to 6:0:0 (brown). + */ +deadShrubColor: Color | null; +/** + * The shrub will drown once the water on its tile reaches this level. Defaults to 4. + */ +shrubDrownLevel: number | null; +/** + * Names a drink made from the plant, allowing it to be used in entity resources. + * Previously also permitted brewing the plant into alcohol made of this material. + * Now, a `MATERIAL_REACTION_PRODUCT` of type `DRINK_MAT` should be used on the proper plant material. + */ +drink: string | null; +/** + * Permits milling the plant at a quern or millstone into a powder made of this material and allows its use in entity resources. + * Said material should have `[POWDER_MISC_PLANT]` to permit proper stockpiling. This token makes the whole plant harvestable regardless + * of which material is designated for milling. + * For plants with millable growths, use only `MATERIAL_REACTION_PRODUCT` or `ITEM_REACTION_PRODUCT` tokens to define the milling products. + */ +mill: string | null; +/** + * Permits processing the plant at a farmer's workshop to yield threads made of this material and allows its use in entity resources. + * Said material should have `[THREAD_PLANT]` to permit proper stockpiling. + */ +thread: string | null; +/** + * Causes the plant to yield plantable seeds made of this material and having these properties. + * Said material should have `[SEED_MAT]` to permit proper stockpiling. + */ +seed: SeedMaterial | null; +/** + * Permits processing the plant into a vial at a still to yield extract made of this material. + * Said material should have `[EXTRACT_STORAGE:FLASK]`. + */ +extractStillVial: string | null; +/** + * Permits processing the plant into a vial at a farmer's workshop to yield extract made of this material. + * Said material should have `[EXTRACT_STORAGE:VIAL]`. + */ +extractVial: string | null; +/** + * Permits processing the plant into a barrel at a farmer's workshop to yield extract made of this material. + * Said material should have `[EXTRACT_STORAGE:BARREL]`. + */ +extractBarrel: string | null } + +/** + * The tokens for the shrubs + */ +export type ShrubTag = +/** + * The spring season + */ +"Spring" | +/** + * The summer season + */ +"Summer" | +/** + * The autumn season + */ +"Autumn" | +/** + * The winter season + */ +"Winter" | +/** + * The growth duration + */ +"GrowDuration" | +/** + * The value of the shrub + */ +"Value" | +/** + * The tile used for the shrub once it is picked + */ +"PickedTile" | +/** + * The tile used for the shrub once it is dead and picked + */ +"DeadPickedTile" | +/** + * The tile used for the shrub + */ +"ShrubTile" | +/** + * The tile used for the dead shrub + */ +"DeadShrubTile" | +/** + * The cluster size the shrubs will spawn in + */ +"ClusterSize" | +/** + * The color of the shrub once it is picked + */ +"PickedColor" | +/** + * The color of the shrub once it is dead and picked + */ +"DeadPickedColor" | +/** + * The color of the shrub + */ +"ShrubColor" | +/** + * The color of the dead shrub + */ +"DeadShrubColor" | +/** + * The depth level the shrub will drown at + */ +"ShrubDrownLevel" | +/** + * The shrub can be brewed + */ +"Drink" | +/** + * The shrub can be milled + */ +"Mill" | +/** + * The shrub can be spun + */ +"Thread" | +/** + * The shrub has seeds + */ +"Seed" | +/** + * The shrub can have a liquid extracted from it using a still and vial + */ +"ExtractStillVial" | +/** + * The shrub can have a liquid extracted from it using a vial alone + */ +"ExtractVial" | +/** + * The shrub can have a liquid extracted from it using a barrel + */ +"ExtractBarrel" | +/** + * An unknown token + */ +"Unknown" + +/** + * A struct representing a sprite graphic. + */ +export type SpriteGraphic = { primaryCondition: ConditionTag; tilePageId: string; offset: Dimensions; color?: ColorModificationTag | null; largeImage: boolean | null; offset2: Dimensions | null; secondaryCondition?: ConditionTag | null; colorPalletSwap: number | null; targetIdentifier: string | null; extraDescriptor: string | null } /** - * A struct representing a body size in the format `years:days:size_cm3` + * A simplified struct for sprite graphic data + */ +export type SpriteGraphicData = { +/** + * database id for this sprite graphic + */ +id: string; +/** + * linked raw id (of graphics raw) this belongs to + */ +rawId: string; +/** + * identifier of tile page sprite is on + */ +tilePageIdentifier: string; +/** + * sprite offset x1 + */ +offsetX: string; +/** + * sprite offset y1 + */ +offsetY: string; +/** + * for large sprites, offset x2 + */ +offsetX2: string | null; +/** + * for large sprites, offset y2 + */ +offsetY2: string | null; +/** + * primary condition for the sprite + */ +primaryCondition: string; +/** + * secondary condition for the sprite + */ +secondaryCondition: string; +/** + * the identifier of the thing this sprite displays + */ +targetIdentifier: string } + +/** + * A struct representing a `SpriteLayer` object. + */ +export type SpriteLayer = { layerName: string; tilePageId: string; offset: Dimensions; offset2: Dimensions | null; largeImage: boolean | null; conditions: ([ConditionTag, string])[] | null } + +/** + * Represents the name of a materials 3 states (solid, liquid, gas) + */ +export type StateName = { solid: string; liquid: string; gas: string } + +/** + * The additional data specific to the steam workshop + */ +export type SteamData = { title: string | null; description: string | null; tags: string[] | null; keyValueTags: string[] | null; metadata: string[] | null; changelog: string | null; fileId: string } + +/** + * A struct representing a syndrome + */ +export type Syndrome = { +/** + * Seen the `[SYN_IDENTIFIER:INEBRIATION]` tag in `material_templates.txt` + */ +identifier: string | null; name: string | null; affectedClasses: string[] | null; immuneClasses: string[] | null; affectedCreatures: ([string, string])[] | null; immuneCreatures: ([string, string])[] | null; classes: string[] | null; +/** + * Seen the `[SYN_CONCENTRATION_ADDED:100:1000]` tag in `material_templates.txt` + * default is 0:0 + */ +concentrationAdded: [number, number] | null; tags: SyndromeTag[] | null; conditions: string[] | null } + +/** + * Represents the tokens that can be used in a syndrome definition. + */ +export type SyndromeTag = +/** + * Used to specify the name of the syndrome as it appears in-game. Names don't have to be unique; + * It's perfectly acceptable to have multiple syndromes with identical names. + */ +"Name" | +/** + * Can be included to create a syndrome class and assign the syndrome to it, for use with the `IT_CANNOT_HAVE_SYNDROME_CLASS` + * interaction token. Can be specified more than once to assign the syndrome to multiple classes. + * + * Other syndromes can also be assigned to the same class. + */ +"Class" | +/** + * If the syndrome is tied to a material, the injection of this material into a creature's bloodstream will cause it to contract + * the syndrome if this token is included. Injection can be carried out as part of a creature attack via `SPECIALATTACK_INJECT_EXTRACT`, + * or by piercing the flesh of a creature with an item that has been contaminated with the material. Thus, this token can be used as a + * more specific alternative to `SYN_CONTACT` for syndromes intended to be administered by envenomed weapons. + */ +"Injected" | +/** + * If the syndrome is tied to a material, creatures who come into contact with this material will contract the syndrome + * if this token is included in the syndrome definition. Methods of getting a material contaminant onto a creature's body include: + * - secretions + * - liquid projectiles + * - vapor and dust clouds + * - puddles and dust piles + * - freakish rain + * - unprotected contact with an infected creature (punching/wrestling/colliding) + * - equipped or hauled items melting + * - being struck with a contaminated item + */ +"Contact" | +/** + * If the syndrome is tied to a material, creatures who inhale the material will contract the syndrome if this token is included. + * Materials can only be inhaled in their gaseous state, which is attainable by boiling, or in the form of a `TRAILING_GAS_FLOW`, + * `UNDIRECTED_GAS` or `WEATHER_CREEPING_GAS`. Creatures can also be made to leak gaseous tissue when damaged. + */ +"Inhaled" | +/** + * If the syndrome is tied to a material, creatures who eat or drink substances comprising, containing or contaminated with this + * material will contract the syndrome if this token is included. This includes prepared meals when any of the constituent + * ingredients contains the material in question. + * + * This also applies to grazing creatures which happen to munch on a grass that has an ingestion-triggered syndrome tied to any of + * its constituent materials. + */ +"Ingested" | +/** + * If this is included, only creatures which belong to the specified creature class (as well as creatures which pass the + * `SYN_AFFECTED_CREATURE` check if this is included) will be able to contract the syndrome. This token can be specified multiple times + * per syndrome, in which case creatures which have at least one matching class will be considered susceptible. + * + * If `SYN_IMMUNE_CLASS` and/or `SYN_IMMUNE_CREATURE` are included, creatures which fail these checks will be unable to contract the syndrome + * even if they pass this class check. + */ +"AffectedClass" | +/** + * If this is included, creatures which belong to the specified creature class will be unable to contract the syndrome. This token can be + * specified multiple times per syndrome, in which case creatures with at least one matching class will be considered immune + * (unless overridden by `SYN_AFFECTED_CREATURE`). + */ +"ImmuneClass" | +/** + * If this is included, only the specified creature (and, if `SYN_AFFECTED_CLASS` is included, also creatures which pass this check as explained above) + * will be able to contract the syndrome. This token can be used multiple times per syndrome. If used alongside `SYN_IMMUNE_CLASS`, the specified + * creature will be able to contract the syndrome regardless of this class check. + * + * `DWARF:FEMALE` is an example of a valid `creature:caste` combination. + * + * `ALL` can be used in place of a specific caste so as to indicate that this applies to all castes of the specified creature. + */ +"AffectedCreature" | +/** + * If this is included, the specified creature will be unable to contract the syndrome (even if it matches `SYN_AFFECTED_CLASS`). + * It can be specified multiple times per syndrome. As above, `ALL` can be used in place of a specific caste. + */ +"ImmuneCreature" | +/** + * Syndrome concentration is essentially a quantity which impacts the severity of the syndrome's relevant effects. The higher the syndrome's concentration, + * the greater its severity. When a syndrome is contracted, the value specified in `amount` is its initial concentration level. + * + * As described above, if a creature is exposed to a syndrome with a particular `SYN_IDENTIFIER` when already possessing an active syndrome with the same identifier, + * then this later syndrome isn't contracted, instead contributing to the original syndrome's concentration as indicated by its `SYN_CONCENTRATION_ADDED` token, if present. + * The syndrome in question will increase the original syndrome's concentration by `amount` whenever the creature is exposed to it, until its specified `max` concentration + * is reached by the original syndrome, causing subsequent exposure to this particular syndrome to do nothing (that is, until the original syndrome ends, at which point + * a new one may be contracted normally). Should the creature be exposed to a different syndrome with the same identifier and a higher `max` value, the concentration will + * of course increase further. + * + * Example: `SYN_CONCENTRATION_ADDED:amount:max` + */ +"ConcentrationAdded" | +/** + * Prevents creatures from being admitted to hospital for problems arising directly as a result of the syndrome's effects, no matter how bad they get. + */ +"NoHospital" | +/** + * This token can be included to give a syndrome an identifier which can be shared between multiple syndromes. Only one identifier may be specified per syndrome. + * + * Syndrome identifiers can be used in conjunction with the `SYNDROME_DILUTION_FACTOR` creature token to alter a creature’s innate resistance to the relevant + * effects of any syndromes that possess the specified identifier. For example, every alcoholic beverage in unmodded games comes with its own copy of an intoxicating + * syndrome, each of which has a `[SYN_IDENTIFIER:INEBRIATION]` token. All dwarves have `[SYNDROME_DILUTION_FACTOR:INEBRIATION:150]`, which decreases the severity of + * any effects derived from a syndrome with the INEBRIATION identifier, thus enabling them to better handle all forms of alcohol. + */ +"Identifier" | +/** + * Unknown as default. + */ +"Unknown" + +/** + * The `Complexity` enum is used to determine how a token is parsed. + * + * A token can have no arguments, a single argument, or multiple arguments. This corresponds to the + * `None`, `Simple`, and `Complex` variants, respectively. + */ +export type TagComplexity = +/** + * The token affects raws by itself with no arguments + */ +"None" | +/** + * The token affects raws and requires a single argument + */ +"Simple" | +/** + * The token affects raws and requires multiple arguments + */ +"Complex" + +/** + * The temperature properties of a material + */ +export type Temperatures = { +/** + * This determines how long it takes the material to heat up or cool down. + * A material with a high specific heat capacity will hold more heat and affect its surroundings more + * before cooling down or heating up to equilibrium. The input for this token is not temperature, + * but rather the specific heat capacity of the material. + */ +specificHeat: number | null; +/** + * This is the temperature at which the material will catch fire. + */ +ignitionPoint: number | null; +/** + * This is the temperature at which a liquid material will freeze, or a solid material will melt. + * In Dwarf Fortress the melting point and freezing point coincide exactly; this is contrary to many + * real-life materials, which can be supercooled. + */ +meltingPoint: number | null; +/** + * This is the temperature at which the material will boil or condense. Water boils at 10180 °U + */ +boilingPoint: number | null; +/** + * This is the temperature above which the material will begin to take heat damage. + * Burning items without a heat damage point (or with an exceptionally high one) will take damage very slowly, + * causing them to burn for a very long time (9 months and 16.8 days) before disappearing. + */ +heatDamagePoint: number | null; +/** + * This is the temperature below which the material will begin to take frost damage. + */ +coldDamagePoint: number | null; +/** + * A material's temperature can be forced to always be a certain value via the `MAT_FIXED_TEMP` + * material definition token. The only standard material which uses this is nether-cap wood, + * whose temperature is always at the melting point of water. If a material's temperature is fixed + * to between its cold damage point and its heat damage point, then items made from that material + * will never suffer cold/heat damage. This makes nether-caps fire-safe and magma-safe despite being a type of wood. + */ +materialFixedTemperature: number | null } + +/** + * Representation of a character tile (literally a single character) that is used in DF Classic + */ +export type Tile = { character: string; altCharacter: string | null; color: Color | null; glowCharacter: string | null; glowColor: Color | null } + +/** + * Custom wrapper for the Tile character used in tags + */ +export type TileCharacter = { +/** + * The character used for the tile + */ +value: string } + +/** + * A struct representing a `TilePage` object. + */ +export type TilePage = { metadata: Metadata | null; identifier: string; objectId: string; file: string; tileDim: Dimensions; pageDim: Dimensions } + +/** + * A simplified struct for tile page data + */ +export type TilePageData = { +/** + * database id for this page + */ +id: string; +/** + * linked id in database for the raw data of this tile page + */ +rawId: string; +/** + * identifier of the tile page + */ +identifier: string; +/** + * file path to tile page + */ +filePath: string; +/** + * width of tiles */ -export type BodySize = { years: number; days: number; sizeCm3: number }; - +tileWidth: number; /** - * A struct representing a creature caste. + * height of tiles */ -export type Caste = { - identifier: string; - tags?: CasteTag[] | null; - description?: string | null; - babyName?: Name | null; - casteName?: Name | null; - childName?: Name | null; - /** - * Default \[0,0\] - */ - clutchSize?: [number, number] | null; - /** - * Default \[0,0\] - */ - litterSize?: [number, number] | null; - /** - * Default \[0,0\] - */ - maxAge?: [number, number] | null; - baby?: number | null; - child?: number | null; - difficulty?: number | null; - eggSize?: number | null; - grassTrample?: number | null; - grazer?: number | null; - lowLightVision?: number | null; - petValue?: number | null; - popRatio?: number | null; - changeBodySizePercentage?: number | null; - creatureClass?: string[] | null; - bodySize?: BodySize[] | null; - milkable?: Milkable | null; - tile?: Tile | null; - /** - * The gaits by which the creature can move. - */ - gaits?: Gait[] | null; -}; - +tileHeight: number; /** - * Tokens that can be found in a creature's caste definitions. + * width of page */ -export type CasteTag = - /** - * Prevents tamed creature from being made available for adoption, instead allowing it to automatically adopt whoever it wants. - * The basic requirements for adoption are intact, and the creature will only adopt individuals who have a preference for their species. - * Used by cats in the vanilla game. When viewing a tame creature with this token, the message "This animal isn't interested in your - * wishes" will appear instead of "This adorable animal can't work" or "This animal is waiting to be trained". - * - * Appears as `ADOPTS_OWNER` - */ - | "AdoptsOwner" - /** - * Makes the creature need alcohol to get through the working day; it will choose to drink booze instead of water if possible. - * Going sober for too long reduces speed. - * - * Appears as `ALCOHOL_DEPENDENT` - */ - | "AlcoholDependent" - /** - * Sets the creature to be active during the day, night, and twilight in Adventurer Mode. Seems to be a separate value from `[DIURNAL]/[NOCTURNAL]/[CREPUSCULAR]`, - * rather than implying them. - * - * Appears as `ALL_ACTIVE` - */ - | "AllActive" - /** - * Caste-specific version of `[Creature::AltTile]`. Requires `[Tile]`. - * - * Appears as `CASTE_ALTTILE:SomeTile` - */ - | { - AltTile: { - /** - * The tile to use - */ - tile: TileCharacter; - }; - } - /** - * Found on `[LargePredator]`s who ambush their prey. Instead of charging relentlessly at prey, the predator will wait till the prey is - * within a few squares before charging. May or may not work on other creatures. - * - * Appears as `AMBUSHPREDATOR` - */ - | "AmbushPredator" - /** - * Allows a creature to breathe both in and out of water (unlike [Aquatic]) - does not prevent drowning in magma. - * - * Appears as `AMPHIBIOUS` - */ - | "Amphibious" - /** - * Applies the specified creature variation with the given arguments to the creature. See `[ApplyCreatureVariation]` for more information. - * - * Appears as `APPLY_CREATURE_VARIATION:SOME_VARIATION` or `APPLY_CREATURE_VARIATION:SOME_VARIATION:ARG1:ARG2:ARG3` - */ - | { - ApplyCreatureVariation: { - /** - * Creature variation ID to apply - */ - id: string; - /** - * (Optional) any number of arguments to pass to the creature variation - */ - args: number[]; - }; - } - /** - * Applies the effects of all pending `[CV_ADD_TAG]` and `[CV_REMOVE_TAG]` tokens that have been defined in the current creature (so far). - * - * Appears as `APPLY_CURRENT_CREATURE_VARIATION` - */ - | "ApplyCurrentCreatureVariation" - /** - * Enables the creature to breathe in water, but causes it to air-drown on dry land. - * - * Appears as `AQUATIC` - */ - | "Aquatic" - /** - * Causes the creature to be excluded from the object testing arena's creature spawning list. Typically applied to spoileriffic creatures. - * - * Appears as `ARENA_RESTRICTED` - */ - | "ArenaRestricted" - /** - * Prevents the creature from attacking or frightening creatures with the [Natural] tag. - * - * Appears as `AT_PEACE_WITH_WILDLIFE` - */ - | "AtPeaceWithWildlife" - /** - * Defines the attack name, and the body part used. - * - * Appears as `ATTACK:NAME:BODYPART:BY_CATEGORY:HORN` or similar - */ - | { - Attack: { - /** - * The verb for the attack - */ - verb: string; - /** - * The body part selector used for the attack - */ - selector: string[]; - }; - } - /** - * Specifies when a megabeast or semi-megabeast will attack the fortress. The attacks will start occurring when at least one of the requirements is met. - * Setting a value to 0 disables the trigger. - * - * Appears as `ATTACK_TRIGGER:0:1:2` - */ - | { - AttackTrigger: { - /** - * Population trigger - */ - population: number; - /** - * Exported wealth trigger - */ - exported_wealth: number; - /** - * Created wealth trigger - */ - created_wealth: number; - }; - } - /** - * Age at which creature is considered a child, the default is zero. One can think of this as the duration of the baby stage. - * - * Appears as `BABY:12` - */ - | { - Baby: { - /** - * The age at which the creature is considered a child - */ - age: number; - }; - } - /** - * Defines a new name for a creature in baby state at the caste level. For non-caste-specific baby names, see `[CreatureToken::GeneralBabyName]`. - * - * Appears as `BABYNAME:SomeName:SomeNames` - */ - | { - BabyName: { - /** - * Singular name for the baby - */ - singular: string; - /** - * Plural name for the baby - */ - plural: string; - }; - } - /** - * Creature may be subject to beaching, becoming stranded on shores, where they will eventually air-drown. The number indicates the frequency of the occurrence. - * Presumably requires the creature to be [Aquatic]. Used by orcas, sperm whales and sea nettle jellyfish in the vanilla game. - * - * Appears as `BEACH_FREQUENCY:100` - */ - | { - BeachFrequency: { - /** - * Frequency of beaching - */ - frequency: number; - }; - } - /** - * The creature is non-aggressive by default, and will never automatically be engaged by companions or soldiers, running away from any creatures - * that are not friendly to it, and will only defend itself if it becomes enraged. Can be thought of as the counterpoint of the `[LargePredator]` tag. - * When tamed, animals with this tag will be useless for fortress defense. - * - * Appears as `BENIGN` - */ - | "Benign" - /** - * Specifies what the creature's blood is made of. - * - * Appears as `BLOOD:SomeMaterial:SubMaterial?:SomeToken` - */ - | { - Blood: { - /** - * Blood material - */ - material: string[]; - /** - * Blood token - */ - state: string; - }; - } - /** - * Causes vampire-like behavior; the creature will suck the blood of unconscious victims when its thirst for blood grows sufficiently large. When controlling the - * creature in adventure mode, this can be done at will. Seems to be required to make the creature denouncable (in-world) as a creature of the night. - * - * Appears as `BLOODSUCKER` - */ - | "BloodSucker" - /** - * Draws body parts from `OBJECT:BODY` files (such as `body_default.txt`) - * - * Appears as `BODY:BODY_WITH_HEAD_FLAG:HEART:GUTS:BRAIN:MOUTH` - */ - | { - Body: { - /** - * Body parts arguments - */ - body_parts: string[]; - }; - } - /** - * These body modifiers give individual creatures different characteristics. In the case of `HEIGHT`, `BROADNESS` and `LENGTH`, the modifier is also a percentage change to - * the `BODY_SIZE` of the individual creature. The seven numbers afterward give a distribution of ranges. Each interval has an equal chance of occurring. - * - * Example: `BODY_APPEARANCE_MODIFIER:HEIGHT:90:95:98:100:102:105:110` - * - * * `HEIGHT`: marks the height to be changed - * * `90:95:98:100:102:105:110`: sets the range from the shortest (90% of the average height) to the tallest (110% of the average height) creature variation. - */ - | { - BodyAppearanceModifier: { - /** - * Body part to modify - */ - attribute: string; - /** - * Range of values, spread from lowest to median to highest - */ - values: [number, number, number, number, number, number, number]; - }; - } - /** - * Loads a plan from listed `OBJECT:BODY_DETAIL_PLAN` files, such as `b_detail_plan_default.txt`. Mass applies `USE_MATERIAL_TEMPLATE`, mass alters RELSIZE, alters - * body part positions, and will allow tissue layers to be defined. Tissue layers are defined in order of skin to bone here. - * - * Example: `BODY_DETAIL_PLAN:VERTEBRATE_TISSUE_LAYERS:SKIN:FAT:MUSCLE:BONE:CARTILAGE` - * - * This creates the detailed body of a fox, the skin, fat, muscle, bones and cartilage out of the vertebrate tissues. A maggot would only need: - * - * `BODY_DETAIL_PLAN:EXOSKELETON_TISSUE_LAYERS:SKIN:FAT:MUSCLE` - */ - | { - BodyDetailPlan: { - /** - * Body detail plan to load - */ - body_plan: string; - /** - * Body detail plan arguments - */ - arguments: string[]; - }; - } - /** - * Sets size at a given age. Size is in cubic centimeters, and for normal body materials, is roughly equal to the creature's average weight in grams. - * - * Appears as `BODY_SIZE:0:0:1000` - */ - | { - BodySize: { - /** - * Year at which the size is set - */ - year: number; - /** - * Days at which the size is set - */ - days: number; - /** - * Size in cubic centimeters - */ - size: number; - }; - } - /** - * Substitutes body part text with replacement text. Draws gloss information from `OBJECT:BODY`files (such as `body_default.txt`) - * - * Appears as `BODYGLOSS:SomeGloss` - */ - | { - BodyGloss: { - /** - * The gloss to use on the body part - */ - gloss: string; - }; - } - /** - * Creature eats bones. Implies [Carnivore]. Adventurers with this token are currently unable to eat bones. - * - * Appears as `BONECARN` - */ - | "BoneCarn" - /** - * Adds a type to a body part - used with `[SetBodyPartGroup]`. In vanilla DF, this is used for adding the type `[Geldable]` to the lower body of certain creatures. - * - * Appears as `BP_ADD_TYPE:SomeBodyPartType` - */ - | { - BodyPartAddType: { - /** - * The body part type to add - */ - body_part_type: string; - }; - } - /** - * Sets up the breadth of possibilities for appearance qualities for a selected `BP` group. e.g.: - * - * * Eyes (`CLOSE_SET`, `DEEP_SET`, `ROUND_VS_NARROW`, `LARGE_IRIS`) - * * Lips (`THICKNESS`) - * * Nose (`BROADNESS`, `LENGTH`, `UPTURNED`, `CONVEX`) - * * Ear (`SPLAYED_OUT`, `HANGING_LOBES`, `BROADNESS`, `HEIGHT`) - * * Tooth (`GAPS`) - * * Skull (`HIGH_CHEEKBONES`, `BROAD_CHIN`, `JUTTING CHIN`, `SQUARE_CHIN`) - * * Neck (`DEEP_VOICE`, `RASPY_VOICE`) - * * Head (`BROADNESS`, `HEIGHT`) - * - * Appears as `BP_APPEARANCE_MODIFIER:SomeQuality:0:0:0:0:0:0:0` - */ - | { - BodyPartAppearanceModifier: { - /** - * The quality that can appear - */ - quality: string; - /** - * The spread of the quality, from lowest to median to highest - */ - spread: [number, number, number, number, number, number, number]; - }; - } - /** - * Removes a type from a body part. Used with `[SetBodyPartGroup]`. - * - * Appears as `BP_REMOVE_TYPE:SomeBodyPartType` - */ - | { - BodyPartRemoveType: { - /** - * The body part type to remove - */ - body_part_type: string; - }; - } - /** - * Allows a creature to destroy furniture and buildings. Value `1` targets mostly doors, hatches, furniture and the like. Value `2` targets - * anything not made with the b + C commands. - * - * Appears as `BUILDINGDESTROYER:1` - */ - | { - BuildingDestroyer: { - /** - * Whether the creature focuses on doors, hatches, furniture, etc. (`1`) or anything not made with the b + C commands (`2`) - */ - door_and_furniture_focused: boolean; - }; - } - /** - * The creature can perform an interaction. - * - * Appears as `CAN_DO_INTERACTION:SomeInteraction` - */ - | { - CanDoInteraction: { - /** - * Interaction to allow - */ - interaction: string; - }; - } - /** - * The creature gains skills and can have professions. If a member of a civilization (even a pet) has this token, it'll need to eat, drink and sleep. - * Note that this token makes the creature unable to be eaten by an adventurer, so it is not recommended for uncivilized monsters. Adventurers lacking - * this token can allocate but not increase attributes and skills. Skills allocated will disappear on start. - * - * Appears as `CAN_LEARN` - */ - | "CanLearn" - /** - * Can talk. Note that this is not necessary for a creature to gain social skills. - * - * Appears as `CAN_SPEAK` - */ - | "CanSpeak" - /** - * Creature cannot climb, even if it has free grasp parts. - * - * Appears as `CANNOT_CLIMB` - */ - | "CannotClimb" - /** - * Creature cannot jump. - * - * Appears as `CANNOT_JUMP` - */ - | "CannotJump" - /** - * Acts like `[NotLiving]`, except that `[OpposedToLife]` creatures will attack them. - * - * Appears as `CANNOT_UNDEAD` - */ - | "CannotUndead" - /** - * Allows the creature to open doors that are set to be unpassable for pets. In adventure mode, creatures lacking this token will be unable to pass through door - * tiles except whilst these are occupied by other creatures. Not currently useful in Fortress mode as doors can no longer be set unpassable for pets. - * - * Appears as `CANOPENDOORS` - */ - | "CanOpenDoors" - /** - * Creature only eats meat. If the creature goes on rampages in worldgen, it will often devour the people/animals it kills. - * Does not seem to affect the diet of the adventurer in adventure mode. - * - * Appears as `CARNIVORE` - */ - | "Carnivore" - /** - * Gives the creature a bonus in caves. Also causes cave adaptation. - * - * Appears as `CAVE_ADAPT` - */ - | "CaveAdaptation" - /** - * Multiplies body size by a factor of (integer)%. 50 halves size, 200 doubles. - * - * Appears as `CHANGE_BODY_SIZE_PERC:100` - */ - | { - ChangeBodySizePercent: { - /** - * The percentage to change the body size by - */ - percent: number; - }; - } - /** - * Age at which creature is considered an adult - one can think of this as the duration of the child stage. Allows the creature's offspring to be - * rendered fully tame if trained during their childhood. - * - * Appears as `CHILD:12` - */ - | { - Child: { - /** - * The age at which the creature is considered an adult - */ - age: number; - }; - } - /** - * Defines a name for the creature in child state at the caste level. For non-caste-specific child names, see `[CreatureToken::GeneralChildName]`. - * - * Appears as `CHILDNAME:SomeName:SomeNames` - */ - | { - ChildName: { - /** - * Singular name for the child - */ - singular: string; - /** - * Plural name for the child - */ - plural: string; - }; - } - /** - * Number of eggs laid in one sitting. - * - * Appears as `CLUTCH_SIZE:1:1` - */ - | { - ClutchSize: { - /** - * Minimum number of eggs laid in one sitting - */ - min: number; - /** - * Maximum number of eggs laid in one sitting - */ - max: number; - }; - } - /** - * Caste-specific color - * - * Arguments: - * - * * `foreground`: The foreground color - * * `background`: The background color - * * `brightness`: The brightness of the color - * - * Appears as `CASTE_COLOR:0:0:0` - */ - | { - Color: { - /** - * The foreground color - */ - foreground: number; - /** - * The background color - */ - background: number; - /** - * The brightness of the color - */ - brightness: number; - }; - } - /** - * When combined with any of `[Pet]`, `[PackAnimal]`, `[WagonPuller]` and/or `[Mount]`, the creature is guaranteed to be domesticated by any civilization with - * `[EntityToken::CommonDomesticPet]`, `[EntityToken::CommonDomesticPackAnimal]`, `[EntityToken::CommonDomesticWagonPuller]` and/or `[EntityToken::CommonDomesticMount]` - * respectively. Such civilizations will always have access to the creature, even in the absence of wild populations. This token is invalid on `[CreatureToken::Fanciful]` creatures. - * - * Appears as `COMMON_DOMESTIC` - */ - | "CommonDomestic" - /** - * Creatures of this caste's species with the `[SpouseConverter]` and `[NightCreatureHunter]` tokens will kidnap `[SpouseConversionTarget]`s of an appropriate - * sex and convert them into castes with `CONVERTED_SPOUSE`. - * - * Appears as `CONVERTED_SPOUSE` - */ - | "ConvertedSpouse" - /** - * Set this to allow the creature to be cooked in meals without first being butchered/cleaned. Used by some water-dwelling vermin such as mussels, nautiluses and oysters. - * - * Appears as `COOKABLE_LIVE` - */ - | "CookableLive" - /** - * Creature is 'berserk' and will attack all other creatures, except members of its own species that also have the CRAZED tag. It will show Berserk in the unit list. - * Berserk creatures go on rampages during worldgen much more frequently than non-berserk ones. - * - * Appears as `CRAZED` - */ - | "Crazed" - /** - * An arbitrary creature classification. Can be set to anything, but the only vanilla uses are `GENERAL_POISON` (used in syndromes), `EDIBLE_GROUND_BUG` - * (used as targets for `[GobbleVerminClass]`), `MAMMAL`, and `POISONOUS` (both used for kobold pet eligibility). A single creature can have multiple classes. - * - * Appears as `CREATURE_CLASS:SomeClass` - */ - | { - CreatureClass: { - /** - * The creature class - */ - class: string; - }; - } - /** - * Sets the creature to be active at twilight in adventurer mode. - * - * Appears as `CREPUSCULAR` - */ - | "Crepuscular" - /** - * Allows a creature to steal and eat edible items from a site. It will attempt to grab a food item and immediately make its way to the map's edge, - * where it will disappear with it. If the creature goes on rampages during worldgen, it will often steal food instead of attacking. Trained and tame instances - * of the creature will no longer display this behavior. - * - * Appears as `CURIOUSBEAST_EATER` - */ - | "CuriousBeastEater" - /** - * Allows a creature to (very quickly) drink your alcohol. Or spill the barrel to the ground. Also affects undead versions of the creature. Unlike food or item thieves, - * drink thieves will consume your alcohol on the spot rather than run away with one piece of it. Trained and tame instances of the creature will no longer display this behavior. - * - * Appears as `CURIOUSBEAST_GUZZLER` - */ - | "CuriousBeastGuzzler" - /** - * Allows a creature to steal things (apparently, of the highest value it can find). It will attempt to grab an item of value and immediately make its way to the map's edge, - * where it will disappear with it. If a creature with any of the CURIOUSBEAST tokens carries anything off the map, even if it is a caravan's pack animal, it will be reported - * as stealing everything it carries. If the creature goes on rampages in worldgen, it will often steal items instead of attacking - kea birds are infamous for this. - * Trained and tame instances of the creature will no longer display this behavior. Also, makes the creature unable to drop hauled items until it enters combat. - * - * Appears as `CURIOUSBEAST_ITEM` - */ - | "CuriousBeastItem" - /** - * Adds a tag. Used in conjunction with creature variation templates. - * - * Appears as `CV_ADD_TAG:SomeTag` - */ - | { - CreatureVariationAddTag: { - /** - * The tag to add - */ - tag: string; - }; - } - /** - * Removes a tag. Used in conjunction with creature variation templates. - * - * Appears as `CV_REMOVE_TAG:SomeTag` - */ - | { - CreatureVariationRemoveTag: { - /** - * The tag to remove - */ - tag: string; - }; - } - /** - * Found on generated demons. Marks the caste to be used in the initial wave after breaking into the underworld, and by the demon civilizations created during world-gen breachings - * - * Appears as `DEMON` - */ - | "Demon" - /** - * A brief description of the creature type, as displayed when viewing the creature's description/thoughts & preferences screen. - */ - | { - Description: { - /** - * The description to use - */ - description: string; - }; - } - /** - * Causes the creature to die upon attacking. Used by honey bees to simulate them dying after using their stingers. - * - * Appears as `DIE_WHEN_VERMIN_BITE` - */ - | "DieWhenVerminBite" - /** - * Increases experience gain during adventure mode. Creatures with a difficulty of 11 or higher are not assigned for quests in adventure mode. - * - * Appears as `DIFFICULTY:10` - */ - | { - Difficulty: { - /** - * The difficulty of the creature - */ - difficulty: number; - }; - } - /** - * Sets the creature to be active during the day in Adventurer Mode. - * - * Appears as `DIURNAL` - */ - | "Diurnal" - /** - * The creature hunts vermin by diving from the air. On tame creatures, it has the same effect as `[HuntsVermin]`. Found on peregrine falcons. - * - * Appears as `DIVE_HUNTS_VERMIN` - */ - | "DiveHuntsVermin" - /** - * Defines the item that the creature drops upon being butchered. Used with `[ExtraButcherObject]`. - * - * Appears as `EBO_ITEM:SomeItem:SomeMaterial` - */ - | { - ExtraButcherObjectItem: { - /** - * The item to add - */ - item: string; - /** - * The material of the item - */ - material: string[]; - }; - } - /** - * The shape of the creature's extra butchering drop. Used with `[ExtraButcherObject]`. - * - * Appears as `EBO_SHAPE:SomeShape` - */ - | { - ExtraButcherObjectShape: { - /** - * The shape to add - */ - shape: string; - }; - } - /** - * Defines the material composition of eggs laid by the creature. Egg-laying creatures in the default game define this 3 times, using `LOCAL_CREATURE_MAT:EGGSHELL`, - * `LOCAL_CREATURE_MAT:EGG_WHITE`, and then `LOCAL_CREATURE_MAT:EGG_YOLK`. Eggs will be made out of eggshell. Edibility is determined by tags on whites or yolk, - * but they otherwise do not exist. - * - * Appears as `EGG_MATERIAL:SomeMaterial:SomeState` - */ - | { - EggMaterial: { - /** - * The material to use - */ - material: string[]; - /** - * The state of the material - */ - state: string; - }; - } - /** - * Determines the size of laid eggs. Doesn't affect hatching or cooking, but bigger eggs will be heavier, and may take longer to be hauled depending on the hauler's strength. - * - * Appears as `EGG_SIZE:100` - */ - | { - EggSize: { - /** - * The size of the egg - */ - size: number; - }; - } - /** - * Allows the creature to wear or wield items. - * - * Appears as `EQUIPS` - */ - | "Equips" - /** - * The creature drops an additional object when butchered, as defined by `[ExtraButcherObjectItem]` and `[ExtraButcherObjectShape]`. - * Used for gizzard stones in default creatures. For some materials, needs to be defined after caste definitions with `SELECT_CASTE:ALL` - * - * Appears as `EXTRA_BUTCHER_OBJECT` - */ - | { - ExtraButcherObject: { - /** - * Details about the extra butcher object - */ - object_type: string; - /** - * Arguments for the extra butcher object - */ - arguments: string[]; - }; - } - /** - * Defines a creature extract which can be obtained via small animal dissection. - * - * Appears as `EXTRACT:SomeExtract` - */ - | { - Extract: { - /** - * The extract material - */ - material: string; - }; - } - /** - * The creature can see regardless of whether it has working eyes and has full 360 degree vision, making it impossible to strike the creature from a blind spot - * in combat. Invisible creatures will also be seen, namely intelligent undead using a "vanish" power. - * - * Appears as `EXTRAVISION` - */ - | "Extravision" - /** - * Found on subterranean animal-man tribals. Currently defunct. In previous versions, it caused these creatures to crawl out of chasms and underground rivers. - * - * Appears as `FEATURE_ATTACK_GROUP` - */ - | "FeatureAttackGroup" - /** - * Found on forgotten beasts. Presumably makes it act as such, initiating underground attacks on fortresses, or leads to the pop-up message upon encountering one. - * Hides the creature from displaying in a `world_sites_and_pops.txt` file. Does not create historical figures like generated forgotten beasts do. - * - * Requires specifying a `[Biome]` in which the creature will live, and both surface and subterranean biomes are allowed. Does not stack with `[LargeRoaming]` and if - * both are used the creature will not spawn. Appears to be incompatible with [Demon] even if used in separate castes. - * - * Appears as `FEATURE_BEAST` - */ - | "FeatureBeast" - /** - * Makes the creature biologically female, enabling her to bear young. Usually specified inside a caste. - * - * Appears as `FEMALE` - */ - | "Female" - /** - * Makes the creature immune to FIREBALL and FIREJET attacks, and allows it to path through high temperature zones, like lava or fires. Does not, by itself, - * make the creature immune to the damaging effects of burning in fire, and does not prevent general heat damage or melting on its own (this would require adjustments - * to be made to the creature's body materials - see the dragon raws for an example). - * - * Appears as `FIREIMMUNE` - */ - | "FireImmune" - /** - * Like `[FireImmune]`, but also renders the creature immune to `DRAGONFIRE` attacks. - * - * Appears as `FIREIMMUNE_SUPER` - */ - | "FireImmuneSuper" - /** - * The creature's corpse is a single `FISH_RAW` food item that needs to be cleaned (into a FISH item) at a fishery to become edible. Before being cleaned the item is - * referred to as "raw". The food item is categorized under "fish" on the food and stocks screens, and when uncleaned it is sorted under "raw fish" in the stocks - * (but does not show up on the food screen). - * - * Without this or `[CookableLive]`, fished vermin will turn into food the same way as non-vermin creatures, resulting in multiple units of food (meat, brain, lungs, - * eyes, spleen etc.) from a single fished vermin. These units of food are categorized as meat by the game. - * - * Appears as `FISHITEM` - */ - | "FishItem" - /** - * The creature's body is constantly at this temperature, heating up or cooling the surrounding area. Alters the temperature of the creature's inventory and all - * adjacent tiles, with all the effects that this implies - may trigger wildfires at high enough values. Also makes the creature immune to extreme heat or cold, as - * long as the temperature set is not harmful to the materials that the creature is made from. Corpses and body parts of creatures with a fixed temperature maintain - * their temperature even after death. - * - * Note that temperatures of 12000 and higher may cause pathfinding issues in fortress mode. - * - * Appears as `FIXED_TEMP:10000` - */ - | { - FixedTemp: { - /** - * The temperature of the creature - */ - temperature: number; - }; - } - /** - * If engaged in combat, the creature will flee at the first sign of resistance. Used by kobolds in the vanilla game. - * - * Appears as `FLEEQUICK` - */ - | "FleeQuick" - /** - * Allows a creature to fly, independent of it having wings or not. Fortress Mode pathfinding only partially incorporates flying - flying creatures need a land path - * to exist between them and an area in order to access it, but as long as one such path exists, they do not need to use it, instead being able to fly over intervening - * obstacles. Winged creatures with this token can lose their ability to fly if their wings are crippled or severed. Winged creatures without this token will be unable - * to fly. (A 'wing' in this context refers to any body part with its own FLIER token). - * - * Appears as `FLIER` - */ - | "Flier" - /** - * Defines a gait by which the creature can move. Typically defined by using `APPLY_CREATURE_VARIATION:STANDARD_GAIT:xxx` in the creature's raws, instead of - * by using this token directly. See [Gait] for more detailed information. - * - * Since it's a bit complicated, we let the [Gait] `from_value()` handle parsing this token. - * - * Appears (typically) as `CV_NEW_TAG:GAIT:WALK:Sprint:!ARG4:10:3:!ARG2:50:LAYERS_SLOW:STRENGTH:AGILITY:STEALTH_SLOWS:50` - */ - | { - Gait: { - /** - * The value of the token - */ - gait_values: string[]; - }; - } - /** - * Has the same function as `[MaterialForceMultiplier]`, but applies to all attacks instead of just those involving a specific material. Appears to be overridden by - * `[MaterialForceMultiplier]` (werebeasts, for example, use both tokens to provide resistance to all materials, with one exception to which they are especially vulnerable). - * - * When struck with a weapon made of the any material, the force exerted will be multiplied by A/B. - * - * Appears as `GENERAL_MATERIAL_FORCE_MULTIPLIER:1:1` - */ - | { - GeneralMaterialForceMultiplier: { - /** - * The material to apply the multiplier to - */ - value_a: number; - /** - * The multiplier to apply - */ - value_b: number; - }; - } - /** - * Makes the creature get infections from necrotic tissue. - * - * Appears as `GETS_INFECTIONS_FROM_ROT` - */ - | "GetsInfectionsFromRot" - /** - * Makes the creature's wounds become infected if left untreated for too long. - * - * Appears as `GETS_WOUND_INFECTIONS` - */ - | "GetsWoundInfections" - /** - * Caste-specific glow color. - * - * Arguments: - * - * * `foreground`: The foreground color - * * `background`: The background color - * * `brightness`: The brightness of the color - * - * Appears as `CASTE_GLOWCOLOR:0:0:0` - */ - | { - GlowColor: { - /** - * The foreground color - */ - foreground: number; - /** - * The background color - */ - background: number; - /** - * The brightness of the color - */ - brightness: number; - }; - } - /** - * Caste-specific glow tile. - * - * Appears as `CASTE_GLOWTILE:SomeTile` - */ - | { - GlowTile: { - /** - * The tile to use - */ - tile: TileCharacter; - }; - } - /** - * The creature can and will gnaw its way out of animal traps and cages using the specified verb, depending on the material from which it is made (normally wood). - * - * Appears as `GNAWER:SomeVerb` - */ - | { - Gnawer: { - /** - * The verb to use - */ - verb: string; - }; - } - /** - * The creature eats vermin of the specified class. - * - * Appears as `GOBBLE_VERMIN_CLASS:SomeVerminClass` - */ - | { - GobbleVerminClass: { - /** - * The vermin class to eat - */ - vermin_class: string; - }; - } - /** - * The creature eats a specific vermin. - * - * Appears as `GOBBLE_VERMIN_CREATURE:SomeVerminCreature:SomeVerminCaste` - */ - | { - GobbleVerminCreature: { - /** - * The vermin creature to eat - */ - vermin_creature: string; - /** - * The vermin caste to eat - */ - vermin_caste: string; - }; - } - /** - * The value determines how rapidly grass is trampled when a creature steps on it - a value of 0 causes the creature to never damage grass, - * while a value of 100 causes grass to be trampled as rapidly as possible. - * - * Defaults to 5. - * - * Appears as `GRASS_TRAMPLE:5` - */ - | { - GrassTrample: { - /** - * The trample value - */ - trample: number; - }; - } - /** - * Used in Creature Variants. This token changes the adult body size to the average of the old adult body size and the target value and scales all intermediate - * growth stages by the same factor. - * - * Appears as `GRAVITATE_BODY_SIZE:25` - */ - | { - GravitateBodySize: { - /** - * The target body size of the creature when it is an adult (fully grown) - */ - target: number; - }; - } - /** - * The creature is a grazer - if tamed in fortress mode, it needs a pasture to survive. The higher the number, the less frequently it needs to eat in order to live. - * - * Not used since 0.40.12, replaced by `[StandardGrazer]` to fix Bug 4113. - * - * Appears as `GRAZER:100` - */ - | { - Grazer: { - /** - * The grazer value - */ - grazer: number; - }; - } - /** - * Defines certain behaviors for the creature. The habit types are: - * - * * `COLLECT_TROPHIES` - * * `COOK_PEOPLE` - * * `COOK_VERMIN` - * * `GRIND_VERMIN` - * * `COOK_BLOOD` - * * `GRIND_BONE_MEAL` - * * `EAT_BONE_PORRIDGE` - * * `USE_ANY_MELEE_WEAPON` - * * `GIANT_NEST` - * * `COLLECT_WEALTH` - * - * These require the creature to have a [Lair] to work properly, and also don't seem to work on creatures who are not a `[SemiMegabeast]`, `[Megabeast]`, or `[NightCreatureHunter]`. - * - * Appears as `HABIT:SomeHabit` - */ - | { - Habit: { - /** - * The habit to add - */ - habit: string; - }; - } - /** - * "If you set `HABIT_NUM` to a number, it should give you that exact number of habits according to the weights.". All lists of `HABIT`s are preceded by `[HABIT_NUM:TEST_ALL]` - * - * Appears as `HABIT_NUM:2` or `HABIT_NUM:TEST_ALL` - */ - | { - HabitNumber: { - /** - * The number of habits to add. A value of `TEST_ALL` will add all habits and will cause number to be 0. - */ - number: HabitCount; - }; - } - /** - * The creature has nerves in its muscles. Cutting the muscle tissue can sever motor and sensory nerves. - * - * Appears as `HAS_NERVES` - */ - | "HasNerves" - /** - * The creature has a shell. Seemingly no longer used - holdover from previous versions. - * - * Appears as `HASSHELL` - */ - | "HasShell" - /** - * Default 'NONE'. The creature's normal body temperature. Creature ceases maintaining temperature on death unlike fixed material temperatures. - * Provides minor protection from environmental temperature to the creature. - * - * Appears as `HOMEOTHERM:10000` - */ - | { - Homeotherm: { - /** - * The temperature of the creature, as number or `NONE` (zero) which is the default - */ - temperature: number; - }; - } - /** - * Creature hunts and kills nearby vermin, randomly walking between places with food laying on the ground or in stockpiles, to check for possible `[VerminEater]` vermin, - * but they'll kill any other vermin too. - */ - | "HuntsVermin" - /** - * The creature cannot move. Found on sponges. Will also stop a creature from breeding in fortress mode (MALE and FEMALE are affected, if one is IMMOBILE no breeding will happen). - * - * Appears as `IMMOBILE` - */ - | "Immobile" - /** - * The creature is immobile while on land. Only works on [Aquatic] creatures which can't breathe on land. - * - * Appears as `IMMOBILE_LAND` - */ - | "ImmobileLand" - /** - * The creature radiates fire. It will ignite, and potentially completely destroy, items the creature is standing on. Also gives the vermin a high chance of escaping from animal - * traps and cages made of any flammable materials (specifically ones that could be ignited by magma). - * - * Appears as `IMMOLATE` - */ - | "Immolate" - /** - * Alias for `[CanSpeak]` + `[CanLearn]`. - * - * Appears as `INTELLIGENT` - */ - | "Intelligent" - /** - * Specifies interaction details following a `[CanDoInteraction]` token. - * - * Appears as `[CDI:TYPE:SomeArgs..]`: - * - * * `[CDI:TOKEN:SPIT]` - * * `[CDI:ADV_NAME:Spit]` - * * `[CDI:USAGE_HINT:NEGATIVE_SOCIAL_RESPONSE]` - * * etc. - */ - | { - InteractionDetail: { - /** - * The type of detail described - */ - label: string; - /** - * Arbitrary arguments for the interaction - */ - args: string[]; - }; - } - /** - * Determines if the creature leaves behind a non-standard corpse (i.e. wood, statue, bars, stone, pool of liquid, etc.). Ethics may prevent actually using the item in jobs or reactions. - * - * Appears as `ITEMCORPSE:ItemToken:MaterialToken` - */ - | { - ItemCorpse: { - /** - * The item token to use - */ - item: string; - /** - * The material token to use - */ - material: string[]; - }; - } - /** - * The quality of an item-type corpse left behind. Valid values are: 0 for ordinary, 1 for well-crafted, 2 for finely-crafted, 3 for superior, 4 for exceptional, 5 for masterpiece. - * - * Appears as `ITEMCORPSE_QUALITY:3` - */ - | { - ItemCorpseQuality: { - /** - * The quality of the item - */ - quality: number; - }; - } - /** - * Found on megabeasts, semimegabeasts, and night creatures. The creature will seek out sites of this type and take them as lairs. The lair types are: - * - * * `SIMPLE_BURROW` - * * `SIMPLE_MOUND` - * * `WILDERNESS_LOCATION` - * * `SHRINE` - * * `LABYRINTH` - * - * Appears as `LAIR:SomeLair:Probability` - */ - | { - Lair: { - /** - * The lair type - */ - lair: string; - /** - * The probability of the lair - */ - probability: number; - }; - } - /** - * Defines certain features of the creature's lair. The only valid characteristic is `HAS_DOORS`. - * - * Appears as `LAIR_CHARACTERISTIC:SomeCharacteristic` - */ - | { - LairCharacteristic: { - /** - * The characteristic to add - */ - characteristic: string; - }; - } - /** - * This creature will actively hunt adventurers in its lair. - * - * Appears as `LAIR_HUNTER` - */ - | "LairHunter" - /** - * What this creature says while hunting adventurers in its lair. - * - * Appears as `LAIR_HUNTER_SPEECH:SomeSpeech` - */ - | { - LairHunterSpeech: { - /** - * The file containing what the creature says - */ - speech_file: string; - }; - } - /** - * Will attack things that are smaller than it (like dwarves). Only one group of "large predators" (possibly two groups on "savage" maps) will appear on any given map. - * In adventure mode, large predators will try to ambush and attack you (and your party will attack them back). When tamed, large predators tend to be much more - * aggressive to enemies than non-large predators, making them a good choice for an animal army. They may go on rampages in worldgen, and adventurers may receive quests - * to kill them. Also, they can be mentioned in the intro paragraph when starting a fortress e.g. "ere the wolves get hungry." - * - * Appears as `LARGE_PREDATOR` - */ - | "LargePredator" - /** - * Creature lays eggs instead of giving birth to live young. - * - * Appears as `LAYS_EGGS` - */ - | "LaysEggs" - /** - * Creature lays the specified item instead of regular eggs. - * - * Appears as `LAYS_UNUSUAL_EGGS:SomeItem:SomeMaterial` - */ - | { - LaysUnusualEggs: { - /** - * The item to lay - */ - item: string; - /** - * The material of the item - */ - material: string[]; - }; - } - /** - * The creature has ligaments in its `[CONNECTIVE_TISSUE_ANCHOR]` tissues (bone or chitin by default). Cutting the bone/chitin tissue severs the ligaments, - * disabling motor function if the target is a limb. - * - * Appears as `LIGAMENTS:SomeMaterial:HealingRate` - */ - | { - Ligaments: { - /** - * The material to use - */ - material: string[]; - /** - * The healing rate - */ - healing_rate: number; - }; - } - /** - * The creature will generate light, such as in adventurer mode at night. - * - * Appears as `LIGHT_GEN` - */ - | "LightGen" - /** - * The creature will attack enemies rather than flee from them. This tag has the same effect on player-controlled creatures - including modded dwarves. - * Retired as of v0.40.14 in favor of `[LargePredator]`. - * - * Appears as `LIKES_FIGHTING` - */ - | "LikesFighting" - /** - * Creature uses "sssssnake talk" (multiplies 'S' when talking - "My name isss Recisssiz."). Used by serpent men and reptile men in the vanilla game. - * C's with the same pronunciation (depending on the word) are not affected by this token. - * - * Appears as `LISP` - */ - | "Lisp" - /** - * Determines the number of offspring per one birth; default 1-3, not used in vanilla raws. - * - * Appears as `LITTERSIZE:1:3` - */ - | { - LitterSize: { - /** - * The minimum number of offspring - */ - min: number; - /** - * The maximum number of offspring - */ - max: number; - }; - } - /** - * Lets a creature open doors that are set to forbidden in fortress mode. - * - * Appears as `LOCKPICKER` - */ - | "LockPicker" - /** - * Determines how well a creature can see in the dark - higher is better. Dwarves have 10000, which amounts to perfect nightvision. - * - * Appears as `LOW_LIGHT_VISION:10000` - */ - | { - LowLightVision: { - /** - * The vision value - */ - vision: number; - }; - } - /** - * According to Toady One, this is completely interchangeable with `[AtPeaceWithWildlife]` and might have been used in very early versions of the game by - * wandering wizards or the ent-type tree creatures that used to be animated by elves. - * - * Appears as `MAGICAL` - */ - | "Magical" - /** - * The creature is able to see while submerged in magma. - * - * Appears as `MAGMA_VISION` - */ - | "MagmaVision" - /** - * Makes the creature biologically male. - * - * Appears as `MALE` - */ - | "Male" - /** - * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. - * - * Appears as `MANNERISM_LAUGH` - */ - | "MannerismLaugh" - /** - * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. - * - * Appears as `MANNERISM_SMILE` - */ - | "MannerismSmile" - /** - * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. - * - * Appears as `MANNERISM_WALK` - */ - | "MannerismWalk" - /** - * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. - * - * Appears as `MANNERISM_SIT` - */ - | "MannerismSit" - /** - * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. - * - * Appears as `MANNERISM_BREATH` - */ - | "MannerismBreath" - /** - * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. - * - * Appears as `MANNERISM_POSTURE` - */ - | "MannerismPosture" - /** - * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. - * - * Appears as `MANNERISM_STRETCH` - */ - | "MannerismStretch" - /** - * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. - * - * Appears as `MANNERISM_EYELIDS` - */ - | "MannerismEyelids" - /** - * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. - * - * Appears as `MANNERISM_FINGERS:SomeFinger:SomeFingers` - */ - | { - MannerismFingers: { - /** - * The finger mannerism to add - */ - finger: string; - /** - * The fingers mannerism to add - */ - fingers: string; - }; - } - /** - * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. - * - * Appears as `MANNERISM_NOSE:SomeNose` - */ - | { - MannerismNose: { - /** - * The nose mannerism to add - */ - nose: string; - }; - } - /** - * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. - * - * Appears as `MANNERISM_EAR:SomeEar` - */ - | { - MannerismEar: { - /** - * The ear mannerism to add - */ - ear: string; - }; - } - /** - * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. - * - * Appears as `MANNERISM_HEAD:SomeHead` - */ - | { - MannerismHead: { - /** - * The head mannerism to add - */ - head: string; - }; - } - /** - * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. - * - * Appears as `MANNERISM_EYES:SomeEyes` - */ - | { - MannerismEyes: { - /** - * The eyes mannerism to add - */ - eyes: string; - }; - } - /** - * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. - * - * Appears as `MANNERISM_MOUTH:SomeMouth` - */ - | { - MannerismMouth: { - /** - * The mouth mannerism to add - */ - mouth: string; - }; - } - /** - * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. - * - * Appears as `MANNERISM_HAIR:SomeHair` - */ - | { - MannerismHair: { - /** - * The hair mannerism to add - */ - hair: string; - }; - } - /** - * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. - * - * Appears as `MANNERISM_KNUCKLES:SomeKnuckles` - */ - | { - MannerismKnuckles: { - /** - * The knuckles mannerism to add - */ - knuckles: string; - }; - } - /** - * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. - * - * Appears as `MANNERISM_LIPS:SomeLips` - */ - | { - MannerismLips: { - /** - * The lips mannerism to add - */ - lips: string; - }; - } - /** - * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. - * - * Appears as `MANNERISM_CHEEK:SomeCheek` - */ - | { - MannerismCheek: { - /** - * The cheek mannerism to add - */ - cheek: string; - }; - } - /** - * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. - * - * Appears as `MANNERISM_NAILS:SomeNails` - */ - | { - MannerismNails: { - /** - * The nails mannerism to add - */ - nails: string; - }; - } - /** - * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. - * - * Appears as `MANNERISM_FEET:SomeFeet` - */ - | { - MannerismFeet: { - /** - * The feet mannerism to add - */ - feet: string; - }; - } - /** - * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. - * - * Appears as `MANNERISM_ARMS:SomeArms` - */ - | { - MannerismArms: { - /** - * The arms mannerism to add - */ - arms: string; - }; - } - /** - * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. - * - * Appears as `MANNERISM_HANDS:SomeHands` - */ - | { - MannerismHands: { - /** - * The hands mannerism to add - */ - hands: string; - }; - } - /** - * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. Appears to be unused. - * - * Appears as `MANNERISM_TONGUE:SomeTongue` - */ - | { - MannerismTongue: { - /** - * The tongue mannerism to add - */ - tongue: string; - }; - } - /** - * Adds a possible mannerism to the creature's profile. These are not defined in raws but hardcoded. Appears to be unused. - * - * Appears as `MANNERISM_LEG:SomeLeg` - */ - | { - MannerismLeg: { - /** - * The leg mannerism to add - */ - leg: string; - }; - } - /** - * Sets the creature to be active at dawn in adventurer mode. - * - * Appears as `MATUTINAL` - */ - | "Matutinal" - /** - * Determines the creature's natural lifespan, using the specified minimum and maximum age values (in years). Each individual creature with this token is generated with a - * predetermined date (calculated down to the exact tick!) between these values, at which it is destined to die of old age, should it live long enough. Note that the - * probability of death at any given age does not increase as the creature gets older [3]. - * - * Creatures which lack this token are naturally immortal. The `NO_AGING` syndrome tag will prevent death by old age from occurring. Also note that, among civilized creatures, - * castes which lack this token will refuse to marry others with it, and vice versa. - * - * Appears as `MAXAGE:100:150` - */ - | { - MaxAge: { - /** - * The minimum age of the creature - */ - min: number; - /** - * The maximum age of the creature - */ - max: number; - }; - } - /** - * Makes the creature slowly stroll around, unless it's in combat or performing a job. If combined with `[CanLearn]`, will severely impact their pathfinding and lead the creature - * to move extremely slowly when not performing any task. Problematically applies to animal people based on the animal and war trained animals. - * - * Appears as `MEANDERER` - */ - | "Meanderer" - /** - * A 'boss' creature. A small number of the creatures are created during worldgen, their histories and descendants (if any) will be tracked in worldgen (as opposed to simply 'spawning'), - * and they will occasionally go on rampages, potentially leading to worship if they attack the same place multiple times. Their presence and number will also influence age names. - * When appearing in fortress mode, they will have a pop-up message announcing their arrival. They will remain hostile to military even after being tamed. - * - * Requires specifying a `[Biome]` in which the creature will live. Subterranean biomes appear to not be allowed. Does stack with `[LargeRoaming]` and if both are used the creature will spawn - * as both historical bosses and as wild animals. - * - * Appears as `MEGABEAST` - */ - | "Megabeast" - /** - * Default is 200. This means you can increase your attribute to 200% of its starting value (or the average value + your starting value if that is higher). - * - * Arguments: - * - * * `attribute`: The attribute to modify - * * `percentage`: The percentage to modify the attribute by - * - * Appears as `MENT_ATT_CAP_PERC:Attribute:200` - */ - | { - MentalAttributeCapPercentage: { - /** - * The attribute to modify - */ - attribute: string; - /** - * The percentage to modify the attribute by - */ - percentage: number; - }; - } - /** - * Sets up a mental attribute's range of values (0-5000). All mental attribute ranges default to 200:800:900:1000:1100:1300:2000. - * - * Arguments: - * - * * `attribute`: The attribute to modify - * * `ranges`: The ranges from lowest to highest with 7 steps - * - * Appears as `MENT_ATT_RANGE:Attribute:200:800:900:1000:1100:1300:2000` - */ - | { - MentalAttributeRange: { - /** - * The attribute to modify - */ - attribute: string; - /** - * The ranges from lowest to highest with 7 steps - */ - ranges: [number, number, number, number, number, number, number]; - }; - } - /** - * Mental attribute gain/decay rates. Lower numbers in the last three slots make decay occur faster. Defaults are 500:4:5:4. - * - * Arguments: - * - * * `attribute`: The attribute to modify - * * `improvement_cost`: The cost to improve the attribute - * * `decay_rate_unused`: The decay rate of the attribute when it is unused - * * `decay_rate_rusty`: The decay rate of the attribute when it is rusty - * * `decay_rate_demotion`: The decay rate of the attribute when it is demoted - * - * Appears as `MENT_ATT_RATE:Attribute:500:4:5:4` - */ - | { - MentalAttributeRate: { - /** - * The attribute to modify - */ - attribute: string; - /** - * The cost to improve the attribute - */ - improvement_cost: number; - /** - * The decay rate of the attribute when it is unused - */ - decay_rate_unused: number; - /** - * The decay rate of the attribute when it is rusty - */ - decay_rate_rusty: number; - /** - * The decay rate of the attribute when it is demoted - */ - decay_rate_demotion: number; - }; - } - /** - * Allows the creature to be milked in the farmer's workshop. The frequency is the amount of ticks the creature needs to "recharge" (i.e. how much time needs to pass before - * it can be milked again). Does not work on sentient creatures, regardless of ethics. - * - * Arguments: - * - * * `material`: The material of the milk - * * `frequency`: The frequency the creature can be milked - * - * Appears as `MILKABLE:SomeMaterial:1000` - */ - | { - Milkable: { - /** - * The material of the milk - */ - material: string[]; - /** - * The frequency the creature can be milked - */ - frequency: number; - }; - } - /** - * The creature spawns stealthed and will attempt to path into the fortress, pulling any levers it comes across. It will be invisible on the map and unit list until spotted by a citizen, - * at which point the game will pause and recenter on the creature. - * - * Used by gremlins in the vanilla game. "They go on little missions to mess with various fortress buildings, not just levers." - * - * Appears as `MISCHIEVOUS` or `MISCHIEVIOUS` (sic) - */ - | "Mischievous" - /** - * Seemingly no longer used. - * - * Appears as `MODVALUE:SomeValue` - */ - | { - ModValue: { - /** - * The value to modify - */ - value: string; - }; - } - /** - * Creature may be used as a mount. No use for the player in fortress mode, but enemy sieging forces may arrive with cavalry. Mounts are usable in adventure mode. - * - * Appears as `MOUNT` - */ - | "Mount" - /** - * Creature may be used as a mount, but civilizations cannot domesticate it in worldgen without certain exceptions. - * - * Appears as `MOUNT_EXOTIC` - */ - | "MountExotic" - /** - * Allows the creature to have all-around vision as long as it has multiple heads that can see. - * - * Appears as `MULTIPART_FULL_VISION` - */ - | "MultipartFullVision" - /** - * Makes the species usually produce a single offspring per birth, with a 1/500 chance of using the [LITTERSIZE] as usual. Requires [FEMALE]. - * - * Appears as `MULTIPLE_LITTER_RARE` - */ - | "MultipleLitterRare" - /** - * Name of the caste - * - * Arguments: - * - * * `singular`: The singular name of the caste - * * `plural`: The plural name of the caste - * * `adjective`: The adjective form of the caste - * - * Appears as `CASTE_NAME:SomeName:SomeNames:SomeAdjective` - */ - | { - Name: { - /** - * The singular name of the caste - */ - singular: string; - /** - * The plural name of the caste - */ - plural: string; - /** - * The adjective form of the caste - */ - adjective: string; - }; - } - /** - * Animal is considered to be natural. NATURAL animals will not engage creatures tagged with `[AtPeaceWitHWildlife]` in combat unless they are - * members of a hostile entity and vice-versa. - * - * Appears as `NATURAL` or `NATURAL_ANIMAL` - */ - | "Natural" - /** - * The creature possesses the specified skill at this level inherently - that is, it begins with the skill at this level, and the skill may never - * rust below that. A value of 15 is legendary. - * - * Arguments: - * - * * `skill`: The skill token to add - * * `level`: The level of the skill - * - * Appears as `NATURAL_SKILL:SomeSkill:15` - */ - | { - NaturalSkill: { - /** - * The skill token to add - */ - skill: string; - /** - * The level of the skill - */ - level: number; - }; - } - /** - * Creatures with this token can appear in bogeyman ambushes in adventure mode, where they adopt classical bogeyman traits such as stalking the adventurer - * and vaporising when dawn breaks. Such traits do not manifest if the creature is encountered outside of a bogeyman ambush (for instance, as a megabeast - * or a civilised being). In addition, their corpses and severed body parts turn into smoke after a short while. Note that setting the "Number of Bogeyman Types" - * in advanced world generation to 0 will only remove randomly-generated bogeymen. - * - * Appears as `NIGHT_CREATURE_BOGEYMAN` - */ - | "NightCreatureBogeyman" - /** - * Found on some necromancers. Creatures with this tag may periodically "perform horrible experiments" offscreen, during which they can use creature-targeting - * interactions with an `[I_SOURCE:EXPERIMENT]` tag on living creatures in their area. Worlds are generated with a list of procedurally-generated experiments, - * allowing necromancers to turn living people and animals into ghouls and other experimental creatures, and these will automatically be available to all experimenters; - * it does not appear possible to prevent this. You can mod in your own custom experiment interactions, but these are used very infrequently due to the large number - * of generated experiments. - * - * Appears as `NIGHT_CREATURE_EXPERIMENTER` - */ - | "NightCreatureExperimenter" - /** - * Found on night trolls and werebeasts. Implies that the creature is a night creature, and shows its description in legends mode entry. The creature is always hostile and - * will start no quarter combat with any nearby creatures, except for members of its own race. Note that this tag does not override the creature's normal behavior in fortress - * mode except for the aforementioned aggression, and doesn't prevent the creature from fleeing the battles it started. It also removes the creature's materials from stockpile - * settings list, making them be stored there regardless of settings. - * - * Does stack with `[LARGE_ROAMING]` and if both are used the creature will spawn as both historical hunters and as wild animals; this requires specifying a [BIOME] in which the - * creature will live, and subterranean biomes are allowed. - * - * This tag causes the usual behaviour of werebeasts in worldgen, that is, fleeing towns upon being cursed and conducting raids from a lair. If this tag is absent from a deity - * curse, the accursed will simply be driven out of towns in a similar manner to vampires. When paired with `SPOUSE_CONVERTER`, a very small population of the creature will be - * created during worldgen (sometimes only a single individual will be created), and their histories will be tracked (that is, they will not spawn spontaneously later, they must - * either have children or convert other creatures to increase their numbers). The creature will settle in a lair and go on rampages during worldgen. It will actively attempt to - * seek out potential conversion targets to abduct, convert, and have children with (if possible). - * - * Appears as `NIGHT_CREATURE_HUNTER` - */ - | "NightCreatureHunter" - /** - * Found on nightmares. Corpses and severed body parts derived from creatures with this token turn into smoke after a short while. - * - * Appears as `NIGHT_CREATURE_NIGHTMARE` - */ - | "NightCreatureNightmare" - /** - * Creature doesn't require connected body parts to move; generally used on undead creatures with connections that have rotted away. - * - * Appears as `NO_CONNECTIONS_FOR_MOVEMENT` - */ - | "NoConnectionsForMovement" - /** - * Creature cannot become dizzy. - * - * Appears as `NO_DIZZINESS` - */ - | "NoDizziness" - /** - * Creature does not need to drink. - * - * Appears as `NO_DRINK` - */ - | "NoDrink" - /** - * Creature does not need to eat. - * - * Appears as `NO_EAT` - */ - | "NoEat" - /** - * The creature caste does not appear in autumn. - * - * Appears as `NO_AUTUMN` - */ - | "NoFall" - /** - * Creature cannot suffer fevers. - * - * Appears as `NO_FEVERS` - */ - | "NoFevers" - /** - * The creature is biologically sexless. Makes the creature unable to breed. - * - * Appears as `NO_GENDER` - */ - | "NoGender" - /** - * The creature cannot raise any physical attributes. - * - * Appears as `NO_PHYS_ATT_GAIN` - */ - | "NoPhysicalAttributeGain" - /** - * The creature cannot lose any physical attributes. - * - * Appears as `NO_PHYS_ATT_RUST` - */ - | "NoPhysicalAttributeRust" - /** - * Creature does not need to sleep. Can still be rendered unconscious by other means. - * - * Appears as `NO_SLEEP` - */ - | "NoSleep" - /** - * The creature caste does not appear in spring. - * - * Appears as `NO_SPRING` - */ - | "NoSpring" - /** - * The creature caste does not appear in summer. - * - * Appears as `NO_SUMMER` - */ - | "NoSummer" - /** - * Creature doesn't require an organ with the [THOUGHT] tag to survive or attack; generally used on creatures that don't have brains. - * - * Appears as `NO_THOUGHT_CENTER_FOR_MOVEMENT` - */ - | "NoThoughtCenterForMovement" - /** - * Prevents creature from selecting its color based on its profession (e.g. Miner, Hunter, Wrestler). - * - * Appears as `NO_UNIT_TYPE_COLOR` - */ - | "NoUnitTypeColor" - /** - * Likely prevents the creature from leaving broken vegetation tracks - * - * Appears as `NO_VEGETATION_PERTURB` - */ - | "NoVegetationDisturbance" - /** - * The creature caste does not appear in winter. - * - * Appears as `NO_WINTER` - */ - | "NoWinter" - /** - * Creature has no bones. - * - * Appears as `NOBONES` - */ - | "NoBones" - /** - * Creature doesn't need to breathe or have [BREATHE] parts in body, nor can it drown or be strangled. Creatures living in magma must have this tag, - * otherwise they will drown. - * - * Appears as `NOBREATHE` - */ - | "NoBreathe" - /** - * Sets the creature to be active at night in adventure mode. - * - * Appears as `NOCTURNAL` - */ - | "Nocturnal" - /** - * Creature has no emotions. It is immune to the effects of stress and unable to rage, and its needs cannot be fulfilled in any way. - * Used on undead in the vanilla game. - * - * Appears as `NOEMOTION` - */ - | "NoEmotion" - /** - * Creature can't become tired or over-exerted from taking too many combat actions or moving at full speed for extended periods of time. - * - * Appears as `NOEXERT` - */ - | "NoExert" - /** - * Creature doesn't feel fear and will never flee from battle, and will be immune to ghosts' attempts to 'scare it to death'. - * Additionally, it causes bogeymen and nightmares to become friendly towards the creature. - * - * Appears as `NOFEAR` - */ - | "NoFear" - /** - * Creature will not be hunted or fed to wild beasts. - * - * Appears as `NOMEAT` - */ - | "NoMeat" - /** - * Creature isn't nauseated by gut hits and cannot vomit. - * - * Appears as `NONAUSEA` - */ - | "NoNausea" - /** - * Creature doesn't feel pain. - * - * Appears as `NOPAIN` - */ - | "NoPain" - /** - * Creature will not drop a hide when butchered. - * - * Appears as `NOSKIN` - */ - | "NoSkin" - /** - * Creature will not drop a skull on butchering, rot, or decay of severed head. - * - * Appears as `NOSKULL` - */ - | "NoSkull" - /** - * Does not produce miasma when rotting. - * - * Appears as `NOSMELLYROT` - */ - | "NoSmellyRot" - /** - * Weapons can't get stuck in the creature. - * - * Appears as `NOSTUCKINS` - */ - | "NoStuckIns" - /** - * Creature can't be stunned and knocked unconscious by pain or head injuries. Creatures with this tag never wake up from sleep in Fortress Mode. - * If this creature needs to sleep while playing, it will die. - * - * Appears as `NOSTUN` - */ - | "NoStun" - /** - * Cannot be butchered. - * - * Appears as `NOT_BUTCHERABLE` - */ - | "NotButcherable" - /** - * Cannot be raised from the dead by necromancers or evil clouds. Implies the creature is not a normal living being. Used by vampires, mummies and - * inorganic creatures like the amethyst man and bronze colossus. Creatures who are `[OPPOSED_TO_LIFE]` (undead) will be docile towards creatures with this token. - * - * Appears as `NOT_LIVING` - */ - | "NotLiving" - /** - * Creature doesn't require a [THOUGHT] body part to survive. Has the added effect of preventing speech, though directly controlling creatures that would otherwise - * be capable of speaking allows them to engage in conversation. - * - * Appears as `NOTHOUGHT` - */ - | "NoThought" - /** - * How easy the creature is to smell. The higher the number, the easier the creature is to sniff out. Defaults to 50. - * Vanilla creatures have values from 0 (undetectable) to 90 (noticeable by humans and dwarves). - * - * Appears as `ODOR_LEVEL:50` - */ - | { - OdorLevel: { - /** - * The odor level, defaults to 50 - */ - odor_level: number; - }; - } - /** - * What the creature smells like. If no odor string is defined, the creature name (not the caste name) is used. - * - * Appears as `ODOR_STRING:SomeOdor` - */ - | { - OdorString: { - /** - * The odor string to use - */ - odor_string: string; - }; - } - /** - * Is hostile to all creatures except undead and other non-living ones and will show Opposed to life in the unit list. Used by undead in the vanilla game. - * Functions without the `[NOT_LIVING]` token, and seems to imply said token as well. Undead will not be hostile to otherwise-living creatures given this token. - * Living creatures given this token will attack living creatures that lack it, while ignoring other living creatures that also have this token. - * - * Appears as `OPPOSED_TO_LIFE` - */ - | "OpposedToLife" - /** - * Determines caste's likelihood of having sexual attraction to certain sexes. Values default to 75:20:5 for the same sex and 5:20:75 for the opposite sex. - * The first value indicates how likely to be entirely uninterested in the sex, the second decides if the creature will be able to become lovers with that sex, - * the third decides whether they will be able to marry in worldgen and post-worldgen world activities (which implies being able to become lovers). Marriage seems - * to be able to happen in fort mode play regardless, as long as they are lovers first. - * - * Arguments: - * - * * `caste`: The caste to set orientation to (MALE or FEMALE typically) - * * `disinterested_chance`: The chance of being disinterested in `caste` - * * `casual_chance`: The chance of being casually interested in `caste` - * * `strong_chance`: The chance of being strongly interested in `caste` - * - * Appears as `ORIENTATION:SomeCaste:75:20:5` - */ - | { - Orientation: { - /** - * The caste to set orientation to - */ - caste: string; - /** - * The chance of being disinterested in `caste` - */ - disinterested_chance: number; - /** - * The chance of being casually interested in `caste` - */ - casual_chance: number; - /** - * The chance of being strongly interested in `caste` - */ - strong_chance: number; - }; - } - /** - * Lets you play as an outsider of this species in adventure mode. - * - * Appears as `OUTSIDER_CONTROLLABLE` - */ - | "OutsiderControllable" - /** - * Allows the creature to be used as a pack animal. Used by merchants without wagons and adventurers. Also prevents creature from dropping hauled items on its own - * - * Note: do not use for player-controllable creatures! May lead to the creature being domesticated during worldgen, even if it doesn't have `[COMMON_DOMESTIC]`. - * - * Appears as `PACK_ANIMAL` - */ - | "PackAnimal" - /** - * The creature is immune to all paralyzing special attacks. - * - * Appears as `PARALYZEIMMUNE` - */ - | "ParalyzeImmune" - /** - * Used to control the bat riders with paralyze-dart blowguns that flew through the 2D chasm. Doesn't do anything now. - * - * Appears as `PATTERNFLIER` - */ - | "PatternFlier" - /** - * In earlier versions, creature would generate pearls. Does nothing in the current version - * - * Appears as `PEARL` - */ - | "Pearl" - /** - * Controls the ability of vermin to find a way into containers when they are eating food from your stockpiles. - * - * Objects made of most materials (e.g. metal) roll a number from 0-100, and if the resulting number is greater than the penetrate power, their contents escape for the time - * being. Objects made of wood, leather, amber, or coral roll 0-95, and items made of cloth roll 0-90. - * - * Appears as `PENETRATEPOWER:100` - */ - | { - PenetratePower: { - /** - * The penetration power - */ - penetrate_power: number; - }; - } - /** - * Determines the range and chance of personality traits. Standard is 0:50:100. - * - * Arguments: - * - * * `personality_trait`: The trait to modify - * * `low`: The lowest chance of having the trait - * * `median`: The median chance of having the trait - * * `high`: The highest chance of having the trait - * - * Appears as `PERSONALITY:SomeTrait:0:50:100` - */ - | { - Personality: { - /** - * The trait to modify - */ - personality_trait: string; - /** - * The lowest chance of having the trait - */ - low: number; - /** - * The median chance of having the trait - */ - median: number; - /** - * The highest chance of having the trait - */ - high: number; - }; - } - /** - * Allows the creature to be tamed in Fortress mode. Prerequisite for all other working animal roles. Civilizations that encounter it in worldgen - * will tame and domesticate it for their own use. Adding this to civilization members will classify them as pets instead of citizens, with all the - * problems that entails. However, you can solve these problems using the popular plugin Dwarf Therapist, which is completely unaffected by the tag. - * - * Appears as `PET` - */ - | "Pet" - /** - * Allows the creature to be tamed in Fortress mode. Prerequisite for all other working animal roles. Civilizations cannot domesticate it in worldgen, - * with certain exceptions. Adding this to civilization members will classify them as pets instead of citizens, with all the problems that entails. - * - * Appears as `PET_EXOTIC` - */ - | "PetExotic" - /** - * How valuable a tamed animal is. Actual cost in points in the embarking screen is 1+(PETVALUE/2) for an untrained animal, 1+PETVALUE for a war/hunting one. - * - * Appears as `PETVALUE:100` - */ - | { - PetValue: { - /** - * The pet value - */ - pet_value: number; - }; - } - /** - * Divides the creature's [PETVALUE] by the specified number. Used by honey bees to prevent a single hive from being worth a fortune. - * - * Appears as `PETVALUE_DIVISOR:2` - */ - | { - PetValueDivisor: { - /** - * The divisor - */ - divisor: number; - }; - } - /** - * Default is 200. This means you can increase your attribute to 200% of its starting value (or the average value + your starting value if that is higher). - * - * Appears as `PHYS_ATT_CAP_PERC:Attribute:200` - */ - | { - PhysicalAttributeCapPercentage: { - /** - * The attribute to modify - */ - attribute: string; - /** - * The percentage to modify the attribute by - */ - percentage: number; - }; - } - /** - * Sets up a physical attribute's range of values (0-5000). All physical attribute ranges default to 200:700:900:1000:1100:1300:2000. - * - * Appears as `PHYS_ATT_RANGE:Attribute:200:700:900:1000:1100:1300:2000` - */ - | { - PhysicalAttributeRange: { - /** - * The attribute to modify - */ - attribute: string; - /** - * The ranges from lowest to highest with 7 steps - */ - ranges: [number, number, number, number, number, number, number]; - }; - } - /** - * Physical attribute gain/decay rates. Lower numbers in the last three slots make decay occur faster. Defaults for `STRENGTH`, `AGILITY`, `TOUGHNESS`, and `ENDURANCE` - * are `500:3:4:3`, while `RECUPERATION` and `DISEASE_RESISTANCE` default to `500:NONE:NONE:NONE`. - * - * Arguments: - * - * * `attribute`: The attribute to modify - * * `improvement_cost`: The cost to improve the attribute - * * `decay_rate_unused`: The decay rate of the attribute when it is unused - * * `decay_rate_rusty`: The decay rate of the attribute when it is rusty - * * `decay_rate_demotion`: The decay rate of the attribute when it is demoted - * - * Appears as `PHYS_ATT_RATE:Attribute:500:3:4:3` - */ - | { - PhysicalAttributeRate: { - /** - * The attribute to modify - */ - attribute: string; - /** - * The cost to improve the attribute - */ - improvement_cost: number; - /** - * The decay rate of the attribute when it is unused - */ - decay_rate_unused: number; - /** - * The decay rate of the attribute when it is rusty - */ - decay_rate_rusty: number; - /** - * The decay rate of the attribute when it is demoted - */ - decay_rate_demotion: number; - }; - } - /** - * Adds a body part group to selected body part group. Presumably used immediately after `[SET_BP_GROUP]`. - * - * Arguments: - * - * * `selector`: the selector for the specific body part - * - * Appears as `PLUS_BP_GROUP:SomeBodyPartSelector:SomeBodyPartGroup` - */ - | { - PlusBodyPartGroup: { - /** - * The body part selector - */ - selector: string[]; - }; - } - /** - * Weighted population of caste; Lower is rarer. Not to be confused with [FREQUENCY]. - * - * Appears as `POP_RATIO:100` - */ - | { - PopulationRatio: { - /** - * The population ratio - */ - pop_ratio: number; - }; - } - /** - * Allows the being to represent itself as a deity, allowing it to become the leader of a civilized group. Used by unique demons in the vanilla game. - * Requires `[CAN_SPEAK]` to actually do anything more than settle at a location (e.g. write books, lead armies, profane temples). Doesn't appear to do anything - * for creatures that are already civilized. Once the creature ascends to a position of leadership, it will proceed to act as a standard ruler for their - * entity and fulfill the same functions (hold tournaments, tame creatures, etc.). - * - * Appears as `POWER` - */ - | "Power" - /** - * Caste-specific profession name. - * - * Arguments: - * - * * `profession`: The profession name / unit type token ID - * * `singular`: The singular name of the profession - * * `plural`: The plural name of the profession - * - * Appears as `CASTE_PROFESSION_NAME:SomeProfession:SomeName:SomeNames` - */ - | { - ProfessionName: { - /** - * The profession name / unit type token ID - */ - profession: string; - /** - * The singular name of the profession - */ - singular: string; - /** - * The plural name of the profession - */ - plural: string; - }; - } - /** - * Creature has a percentage chance to flip out at visible non-friendly creatures. Enraged creatures attack anything regardless of timidity and get a - * strength bonus to their hits. This is what makes badgers so hardcore. - * - * Appears as `PRONE_TO_RAGE:100` - */ - | { - ProneToRage: { - /** - * The rage chance - */ - rage_chance: number; - }; - } - /** - * The creature has pus. Specifies the stuff secreted by infected wounds. - * - * Arguments: - * - * * `material`: The material of the pus - * * `material_state`: The material state of the pus - * - * Appears as `PUS:SomeMaterial:SomeMaterialState` - */ - | { - Pus: { - /** - * The material of the pus - */ - material: string[]; - /** - * The material state of the pus - */ - state: string; - }; - } - /** - * Specifies a new relative size for a part than what is stated in the body plan. For example, dwarves have larger livers. - * - * Arguments: - * - * * `selector`: the selector for the specific body part - * * `relative_size`: The relative size of the body part (by percentage?) - * - * Appears as `RELATIVE_SIZE:SomeBodyPartSelector:SomeBodyPart:100` - */ - | { - RelativeSize: { - /** - * The body part selector - */ - selector: string[]; - /** - * The relative size of the body part (by percentage?) - */ - relative_size: number; - }; - } - /** - * What the creature's remains are called. - * - * Appears as `REMAINS:SomeRemain:SomeRemains` - */ - | { - Remains: { - /** - * The singular name of the remains - */ - singular: string; - /** - * The plural name of the remains - */ - plural: string; - }; - } - /** - * What color the creature's remains are. - * - * Appears as `REMAINS_COLOR:SomeColor` - */ - | { - RemainsColor: { - /** - * The color of the remains - */ - remains_color: string; - }; - } - /** - * Goes with `[VERMIN_BITE]` and `[DIE_WHEN_VERMIN_BITE]`, the vermin creature will leave remains on death when biting. - * Leaving this tag out will cause the creature to disappear entirely after it bites. - * - * Appears as `REMAINS_ON_VERMIN_BITE_DEATH` - */ - | "RemainsOnVerminBiteDeath" - /** - * Unknown remains variation. - * - * Appears as `REMAINS_UNDETERMINED` - */ - | "RemainsUndetermined" - /** - * The creature will retract into the specified body part(s) when threatened. It will be unable to move or attack, but enemies will only be able to attack the specified body part(s). When one of the specified body part is severed off, the creature automatically unretracts and cannot retract anymore. More than one body part can be selected by using `BY_TYPE` or `BY_CATEGORY`. - * - * Second-person descriptions are used for adventurer mode natural ability. `""` can be used in the descriptions, being replaced with the proper pronoun (or lack thereof) in-game. - * - * Undead curled up creatures are buggy, specifically those that retract into their upper bodies: echidnas, hedgehogs and pangolins. The upper body is prevented from collapsing by a separate body part (the middle spine), which cannot be attacked when the creature is retracted. See `[PREVENTS_PARENT_COLLAPSE]`. Living creatures eventually succumb to blood loss, but undead creatures do not. Giant creatures also take a very long time to bleed out. - * - * Arguments: - * - * * `body_part_selector`: The body part selector to use - * * `body_part`: The body part to retract - * * `second_person`: Description using "you" and "your" - * * `third_person`: Description using "it" and "its" - * * `second_person_cancel`: Description using "you" and "your" when the creature is no longer retracted - * * `third_person_cancel`: Description using "it" and "its" when the creature is no longer retracted - * - * Appears as `RETRACT_INTO_BP:SomeBodyPartSelector:SomeBodyPart:SomeSecondPerson:SomeThirdPerson:SomeSecondPersonCancel:SomeThirdPersonCancel` - */ - | { - RetractIntoBodyPart: { - /** - * The body part selector to use - */ - body_part_selector: string; - /** - * The body part to retract - */ - body_part: string; - /** - * Description using "you" and "your" - */ - second_person: string; - /** - * Description using "it" and "its" - */ - third_person: string; - /** - * Description using "you" and "your" when the creature is no longer retracted - */ - second_person_cancel: string; - /** - * Description using "it" and "its" when the creature is no longer retracted - */ - third_person_cancel: string; - }; - } - /** - * Cat behavior. If it kills a vermin creature and has an owner, it carries the remains in its mouth and drops them at their feet. Requires `[HUNTS_VERMIN]`. - * - * Appears as `RETURNS_VERMIN_KILLS_TO_OWNER` - */ - | "ReturnsVerminKillsToOwner" - /** - * Creature will occasionally root around in the grass, looking for insects. Used for flavor in Adventurer Mode, spawns vermin edible for this creature in Fortress Mode. Creatures missing the specified body part will be unable to perform this action. The action produces a message (visible in adventure mode) in the form: - * - * [creature] [verb text] the [description of creature's location] - * - * In adventure mode, the "rooting around" ability will be included in the "natural abilities" menu, represented by its second person verb text. - * - * Arguments: - * - * * `body_part_selector`: the selector for the specific body part - * * `second_person_verb`: Verb to use in second person tense ("you") - * * `third_person_verb`: Verb to use in third person tense ("it") - * - * Appears as `ROOT_AROUND:SomeBodyPartSelector:SomeBodyPart:SomeSecondPersonVerb:SomeThirdPersonVerb` - */ - | { - RootAround: { - /** - * The body part selector - */ - body_part_selector: string[]; - /** - * Verb to use in second person tense ("you") - */ - second_person_verb: string; - /** - * Verb to use in third person tense ("it") - */ - third_person_verb: string; - }; - } - /** - * Causes the specified tissue layer(s) of the indicated body part(s) to secrete the designated material. A size 100 ('covering') contaminant is created over the affected body part(s) in its specified material state (and at the temperature appropriate to this state) when the trigger condition is met, as long as one of the secretory tissue layers is still intact. Valid triggers are: - * - * * `CONTINUOUS`: Secretion occurs once every 40 ticks in fortress mode, and every tick in adventurer mode. - * * `EXERTION`: Secretion occurs continuously (at the rate described above) whilst the creature is at minimum Tired following physical exertion. Note that this cannot occur if the creature has [NOEXERT]. - * * `EXTREME_EMOTION`: Secretion occurs continuously (as above) whilst the creature is distressed. Cannot occur in creatures with [NOEMOTION]. - * - * Arguments: - * - * * `material`: The material of the secretion - * * `material_state`: The material state of the secretion - * * `body_part_selector`: the selector for the specific body part - * * `tissue_layer`: The tissue layer to use - * * `trigger`: The trigger to use (`CONTINUOUS`, `EXERTION`, `EXTREME_EMOTION`) - * - * Appears as `SECRETION:SomeMaterial:SomeMaterialState:SomeBodyPartSelector:SomeBodyPart:SomeTissueLayer:SomeTrigger` - */ - | { - Secretion: { - /** - * The material of the secretion - */ - material: string[]; - /** - * The material state of the secretion - */ - material_state: string; - /** - * The body part selector - */ - body_part_selector: string[]; - /** - * The tissue layer to use - */ - tissue_layer: string; - /** - * The trigger to use (`CONTINUOUS`, `EXERTION`, `EXTREME_EMOTION`) - */ - trigger: string; - }; - } - /** - * Essentially the same as [MEGABEAST], but more of them are created during worldgen. See the semi-megabeast page for details. - * - * Appears as `SEMIMEGABEAST` - */ - | "SemiMegabeast" - /** - * Gives the creature the ability to sense creatures belonging to the specified creature class even when they lie far beyond line of sight, including through walls and floors. - * It also appears to reduce or negate the combat penalty of blind units when fighting creatures they can sense. In adventure mode, the specified tile will be used to represent - * sensed creatures when they cannot be seen directly. - * - * Arguments: - * - * * `creature_class`: The creature class to sense - * * `tile`: The tile to use - * * `color`: via foreground, background, and brightness values - * - * Appears as `SENSE_CREATURE_CLASS:SomeCreatureClass:SomeTile:0:0:0` - */ - | { - SenseCreatureClass: { - /** - * The creature class to sense - */ - creature_class: string; - /** - * The tile to use - */ - tile: string; - /** - * The foreground color to use - */ - foreground: number; - /** - * The background color to use - */ - background: number; - /** - * The brightness to use - */ - brightness: number; - }; - } - /** - * Begins a selection of body parts. - * - * Arguments: - * - * * `body_part_selector`: the selector for the specific body part - * - * Appears as `SET_BP_GROUP:SomeBodyPartSelector:SomeBodyPart` - */ - | { - SetBodyPartGroup: { - /** - * The body part selector - */ - body_part_selector: string[]; - }; - } - /** - * The rate at which this creature learns this skill. Requires `[CAN_LEARN]` or `[INTELLIGENT]` to function. - * - * Arguments: - * - * * `skill`: The skill to modify - * * `rate`: The rate to modify the skill by (percentage) - * - * Appears as `SKILL_LEARN_RATE:SomeSkill:100` - */ - | { - SkillLearnRate: { - /** - * The skill to modify - */ - skill: string; - /** - * The rate to modify the skill by - */ - rate: number; - }; - } - /** - * The rate at which this creature learns all skills. Requires `[CAN_LEARN]` or `[INTELLIGENT]` to function. - * - * Arguments: - * - * * `rate`: The rate to modify the skill by (percentage) - * - * Appears as `SKILL_LEARN_RATES:100` - */ - | { - SkillLearnRates: { - /** - * The rate to modify the skill by - */ - rate: number; - }; - } - /** - * Like `[SKILL_RATES]`, but applies to individual skills instead. Requires `[CAN_LEARN]` or `[INTELLIGENT]` to function. - * - * Arguments: - * - * * `skill`: The skill to modify - * * `improvement_rate`: The improvement rate to modify the skill by (percentage) - * * `decay_rate_unused`: The decay rate of the skill when it is unused - * * `decay_rate_rusty`: The decay rate of the skill when it is rusty - * * `decay_rate_demotion`: The decay rate of the skill when it is demoted - * - * Appears as `SKILL_RATE:SomeSkill:100:3:4:3` - */ - | { - SkillRate: { - /** - * The skill to modify - */ - skill: string; - /** - * The improvement rate to modify the skill by - */ - improvement_rate: number; - /** - * The decay rate of the skill when it is unused - */ - decay_rate_unused: number; - /** - * The decay rate of the skill when it is rusty - */ - decay_rate_rusty: number; - /** - * The decay rate of the skill when it is demoted - */ - decay_rate_demotion: number; - }; - } - /** - * Affects skill gain and decay. Lower numbers in the last three slots make decay occur faster (`[SKILL_RATES:100:1:1:1]` would cause rapid decay). - * The counter (decay) rates may also be replaced with NONE. - * - * Default is `[SKILL_RATES:100:8:16:16]`. Requires `[CAN_LEARN]` or `[INTELLIGENT]` to function. - * - * Arguments: - * - * * `improvement_rate`: The improvement rate to modify the skill by (percentage) - * * `decay_rate_unused`: The decay rate of the skill when it is unused - * * `decay_rate_rusty`: The decay rate of the skill when it is rusty - * * `decay_rate_demotion`: The decay rate of the skill when it is demoted - * - * Appears as `SKILL_RATES:100:8:16:16` - */ - | { - SkillRates: { - /** - * The improvement rate to modify the skill by - */ - improvement_rate: number; - /** - * The decay rate of the skill when it is unused - */ - decay_rate_unused: number; - /** - * The decay rate of the skill when it is rusty - */ - decay_rate_rusty: number; - /** - * The decay rate of the skill when it is demoted - */ - decay_rate_demotion: number; - }; - } - /** - * The rate at which this skill decays. Lower values cause the skill to decay faster. Requires `[CAN_LEARN]` or `[INTELLIGENT]` to function. - * - * Arguments: - * - * * `skill`: The skill to modify - * * `decay_rate_unused`: The decay rate of the skill when it is unused - * * `decay_rate_rusty`: The decay rate of the skill when it is rusty - * * `decay_rate_demotion`: The decay rate of the skill when it is demoted - * - * Appears as `SKILL_RUST_RATE:SomeSkill:3:4:3` - */ - | { - SkillRustRate: { - /** - * The skill to modify - */ - skill: string; - /** - * The decay rate of the skill when it is unused - */ - decay_rate_unused: number; - /** - * The decay rate of the skill when it is rusty - */ - decay_rate_rusty: number; - /** - * The decay rate of the skill when it is demoted - */ - decay_rate_demotion: number; - }; - } - /** - * The rate at which all skills decay. Lower values cause the skills to decay faster. Requires `[CAN_LEARN]` or `[INTELLIGENT]` to function. - * - * Arguments: - * - * * `decay_rate_unused`: The decay rate of the skill when it is unused - * * `decay_rate_rusty`: The decay rate of the skill when it is rusty - * * `decay_rate_demotion`: The decay rate of the skill when it is demoted - * - * Appears as `SKILL_RUST_RATES:3:4:3` - */ - | { - SkillRustRates: { - /** - * The decay rate of the skill when it is unused - */ - decay_rate_unused: number; - /** - * The decay rate of the skill when it is rusty - */ - decay_rate_rusty: number; - /** - * The decay rate of the skill when it is demoted - */ - decay_rate_demotion: number; - }; - } - /** - * Caste-specific `[SLAIN_SPEECH]`. - * - * Appears as `SLAIN_CASTE_SPEECH:SomeSpeechSet` - */ - | { - SlainSpeech: { - /** - * The speech set to use - */ - speech_file: string; - }; - } - /** - * Shorthand for `[CAN_LEARN]` + `[SKILL_LEARN_RATES:50]`. Used by a number of 'primitive' creatures (like ogres, giants and troglodytes) in the vanilla game. - * Applicable to player races. Prevents a player from recruiting nobility, even basic ones. Subterranean creatures with this token combined with [EVIL] will become - * servants of goblins in their civilizations, in the style of trolls. - * - * Appears as `SLOW_LEARNER` - */ - | "SlowLearner" - /** - * Creature leaves "remains" instead of a corpse. Used by vermin. - * - * Appears as `SMALL_REMAINS` - */ - | "SmallRemains" - /** - * Caste-specific solider tile. - * - * Appears as `CASTE_SOLDIER_TILE:SomeTile` - */ - | { - SoldierTile: { - /** - * The tile to use - */ - tile: TileCharacter; - }; - } - /** - * Caste-specific solider alt tile. - * - * Appears as `CASTE_SOLDIER_ALTTILE:SomeTile` - */ - | { - SoldierAltTile: { - /** - * The tile to use - */ - tile: TileCharacter; - }; - } - /** - * Creature makes sounds periodically, which can be heard in Adventure mode. - * - * For example, with `SOUND:PEACEFUL_INTERMITTENT:100:1000:VOCALIZATION:bark:barks:a loud bark` - * - * * First-person reads "You 'bark'" - * * Third-person reads "The capybara 'barks'" - * * Out of sight reads "You hear 'a loud bark'" - * - * Arguments: - * - * * `sound_type`: The sound type to use (`ALERT` or `PEACEFUL_INTERMITTENT`) - * * `sound_range`: The range of the sound (in tiles) - * * `sound_interval`: A delay before the sound is produced again (in ticks) - * * `requires_breathing`: Whether the creature needs to breathe to make the sound - * (indicated by `VOCALIZATION` for true or `NONE` for false) - * * `first_person`: The first-person description of the sound - * * `third_person`: The third-person description of the sound - * * `out_of_sight`: The out-of-sight description of the sound - * - * Appears as `SOUND:SomeSoundType:100:1000:SomeFirstPerson:SomeThirdPerson:SomeOutOfSight` - */ - | { - Sound: { - /** - * The sound type to use (`ALERT` or `PEACEFUL_INTERMITTENT`) - */ - sound_type: string; - /** - * The range of the sound (in tiles) - */ - sound_range: number; - /** - * A delay before the sound is produced again (in ticks) - */ - sound_interval: number; - /** - * Whether the creature needs to breathe to make the sound - */ - requires_breathing: boolean; - /** - * The first-person description of the sound - */ - first_person: string; - /** - * The third-person description of the sound - */ - third_person: string; - /** - * The out-of-sight description of the sound - */ - out_of_sight: string; - }; - } - /** - * Creature will only appear in biomes with this plant or creature available. - * Grazers given a specific type of grass (such as pandas and bamboo) will only eat that grass and nothing else, risking starvation if there's none available. - * - * Arguments: - * - * * `food_type`: The type of the required food - * * `identifier`: The identifier of the required plant or creature - * - * Appears as `SPECIFIC_FOOD:PLANT:Bamboo` or `SPECIFIC_FOOD:CREATURE:Tiger` - */ - | { - SpecificFood: { - /** - * The type of the required food - */ - food_type: ObjectType; - /** - * The identifier of the required plant or creature - */ - identifier: string; - }; - } - /** - * This creature can be converted by a night creature with `[SPOUSE_CONVERTER]`. - * - * Appears as `SPOUSE_CONVERSION_TARGET` - */ - | "SpouseConversionTarget" - /** - * If the creature has the `[NIGHT_CREATURE_HUNTER]` tag, it will kidnap `[SPOUSE_CONVERSION_TARGET]`s and transform them into the caste of its species - * with the `[CONVERTED_SPOUSE]` tag during worldgen. It may also start families this way. - * - * Appears as `SPOUSE_CONVERTER` - */ - | "SpouseConverter" - /** - * If the creature rules over a site, it will cause the local landscape to be corrupted into evil surroundings associated with the creature's spheres. - * The creature must have at least one of the following spheres for this to take effect: BLIGHT, DEATH, DISEASE, DEFORMITY, NIGHTMARES. The first three kill vegetation, - * while the others sometimes do. The last two get evil plants and evil animals sometimes. NIGHTMARES gets bogeymen. [4] Used by demons in the vanilla game. - * - * Appears as `SPREAD_EVIL_SPHERES_IF_RULER` - */ - | "SpreadEvilSpheresIfRuler" - /** - * Caste does not require [GRASP] body parts to climb -- it can climb with [STANCE] parts instead. - * - * Appears as `STANCE_CLIMBER` - */ - | "StanceClimber" - /** - * Acts as [GRAZER] but set to 20000*G*(max size)^(-3/4), where G defaults to 100 but can be set in `d_init`, and the whole thing is trapped between 150 and 3 million. - * Used for all grazers in the default creature raws. - * - * Appears as `STANDARD_GRAZER` - */ - | "StandardGrazer" - /** - * The creature will get strange moods in fortress mode and can produce artifacts. - * - * Appears as `STRANGE_MOODS` - */ - | "StrangeMoods" - /** - * Gives the creature knowledge of any secrets with `[SUPERNATURAL_LEARNING_POSSIBLE]` that match its spheres and also prevents it from becoming a vampire or werebeast. - * Other effects are unknown. - * - * Appears as `SUPERNATURAL` - */ - | "Supernatural" - /** - * The creature naturally knows how to swim perfectly and does not use the swimmer skill, as opposed to `[SWIMS_LEARNED]` below. - * However, Fortress mode AI never paths into water anyway, so it's less useful there. - * - * Appears as `SWIMS_INNATE` - */ - | "SwimsInnate" - /** - * The creature swims only as well as their present swimming skill allows them to. - * - * Appears as `SWIMS_LEARNED` - */ - | "SwimsLearned" - /** - * Dilutes the effects of syndromes which have the specified identifier. A percentage of 100 is equal to the regular syndrome effect severity, higher percentages reduce severity. - * - * Arguments: - * - * * `syndrome`: The syndrome to modify - * * `percentage`: The percentage to modify the syndrome by - * - * Appears as `SYNDROME_DILUTION_FACTOR:SomeSyndrome:100` - */ - | { - SyndromeDilutionFactor: { - /** - * The syndrome to modify - */ - syndrome: string; - /** - * The percentage to modify the syndrome by - */ - percentage: number; - }; - } - /** - * The creature has tendons in its `[CONNECTIVE_TISSUE_ANCHOR]` tissues (bone or chitin by default). - * Cutting the bone/chitin tissue severs the tendons, disabling motor function if the target is a limb. - * - * Arguments: - * - * * `material`: The material of the tendons - * * `healing_rate`: The rate at which the tendons heal (lower is faster) - * - * Appears as `TENDONS:SomeMaterial:100` - */ - | { - Tendons: { - /** - * The material of the tendons - */ - material: string[]; - /** - * The rate at which the tendons heal (lower is faster) - */ - healing_rate: number; - }; - } - /** - * The creature's webs can catch larger creatures. - * - * Appears as `THICKWEB` - */ - | "ThickWeb" - /** - * Caste-specific tile. - * - * Appears as `CASTE_TILE:SomeTile` - */ - | { - Tile: { - /** - * The tile to use - */ - tile: TileCharacter; - }; - } - /** - * Adds the tissue layer to wherever it is required. - * - * Non-argument Locations can be FRONT, RIGHT, LEFT, TOP, BOTTOM. Argument locations are AROUND and CLEANS, requiring a further body part and a % of coverage/cleansing - * - * Arguments: - * - * * `body_part_selector`: the selector for the specific body part - * * `tissue`: The name of the tissue to use - * * `location`: The location to use (`FRONT`, `RIGHT`, `LEFT`, `TOP`, `BOTTOM`) or with an additional argument, (`AROUND`, `CLEANS`) with a body part and a percentage - * - * Appears as `[TISSUE_LAYER:SomeBodyPartSelector:SomeBodyPart:SomeTissue:SomeLocation]` or `[TISSUE_LAYER:SomeBodyPartSelector:SomeBodyPart:SomeTissue:SomeLocation:SomeBodyPart:100]` - * ALSO appears as `[TISSUE_LAYER_OVER:SomeBodyPartSelector:SomeBodyPart:SomeTissue:SomeLocation]` or `[TISSUE_LAYER_OVER:SomeBodyPartSelector:SomeBodyPart:SomeTissue:SomeLocation:SomeBodyPart:100]` - */ - | { - TissueLayer: { - /** - * The body part selector - */ - body_part_selector: string[]; - /** - * The tissue to apply (e.g. NAIL) - */ - tissue: string; - /** - * The remaining tokens defining location/positioning. - * e.g. ["FRONT"] or ["ABOVE", "BY_CATEGORY", "EYE"] or [] - */ - positioning: string[]; - }; - } - /** - * Adds the tissue layer under a given part. - * - * For example, an iron man has a gaseous poison within, and this tissue (GAS is its name) has the token `[TISSUE_LEAKS]` and its state is GAS, so when you puncture the iron outside and - * damage this tissue it leaks gas (can have a syndrome by using a previous one in the creature sample.) - * `[TISSUE_LAYER_UNDER:BY_CATEGORY:ALL:{tissue}]` - * - * `{tissue}` is what will be under the `TISSUE_LAYER`; here is an example Tissue from the Iron Man: - * - * `[TISSUE:GAS] [TISSUE_NAME:gas:NP] [TISSUE_MATERIAL:LOCAL_CREATURE_MAT:GAS] [TISSUE_MAT_STATE:GAS] [RELATIVE_THICKNESS:50] [TISSUE_LEAKS] [TISSUE_SHAPE:LAYER]` - * - * Arguments: - * - * * `body_part_selector`: The body part selector to use (`BY_TYPE`, `BY_CATEGORY`, `BY_TOKEN`) - * * `body_part`: The body part to use (via category, type or token) - * * `tissue`: The name of the tissue to use - * - * Appears as `TISSUE_LAYER_UNDER:SomeBodyPartSelector:SomeBodyPart:SomeTissue` - */ - | { - TissueLayerUnder: { - /** - * The body part selector to use (`BY_TYPE`, `BY_CATEGORY`, `BY_TOKEN`) - */ - body_part_selector: string; - /** - * The body part to use (via category, type or token) - */ - body_part: string; - /** - * The name of the tissue to use - */ - tissue: string; - }; - } - /** - * Found on titans. Cannot be specified in user-defined raws. - * - * Appears as `TITAN` - */ - | "Titan" - /** - * How much the creature can carry when used by merchants. 1000 by default. If a civilization uses a custom pack animal via `ALWAYS_PACK`, you must manually add a capacity to the raws of that - * creature itself. Capacity defaults to null leading to empty caravans. - * - * Arguments: - * - * * `capacity`: The capacity of the creature - * - * Appears as `TRADE_CAPACITY:1000` - */ - | { - TradeCapacity: { - /** - * The capacity of the creature - */ - capacity: number; - }; - } - /** - * Shortcut for `[TRAINABLE_HUNTING]` + `[TRAINABLE_WAR]`. - * - * Appears as `TRAINABLE` - */ - | "Trainable" - /** - * Can be trained as a hunting beast, increasing speed. - * - * Appears as `TRAINABLE_HUNTING` - */ - | "TrainableHunting" - /** - * Can be trained as a war beast, increasing strength and endurance. - * - * Appears as `TRAINABLE_WAR` - */ - | "TrainableWar" - /** - * Allows the creature to go into martial trances. Used by dwarves in the vanilla game. - * - * Appears as `TRANCES` - */ - | "Trances" - /** - * The creature will never trigger traps it steps on. Used by a number of creatures. Doesn't make the creature immune to remotely activated traps (like retractable spikes being triggered - * while the creature is standing over them). TRAPAVOID creatures lose this power if they're immobilized while standing in a trap, be it by stepping on thick web, being paralyzed or being - * knocked unconscious. - * - * Appears as `TRAPAVOID` - */ - | "TrapAvoid" - /** - * The creature is displayed as blue when in 7/7 water. Used on fish and amphibious creatures which swim under the water. - * - * Appears as `UNDERSWIM` - */ - | "UnderSwim" - /** - * Found on generated demons; causes the game to create a single named instance of the demon which will emerge from the underworld and take over civilizations during worldgen. - * - * Appears as `UNIQUE_DEMON` - */ - | "UniqueDemon" - /** - * Like `[AT_PEACE_WITH_WILDLIFE]`, but also makes the creature more valued in artwork by civilisations with the PLANT sphere. [5] Used by grimelings in the vanilla game. - * - * Appears as `VEGETATION` - */ - | "Vegetation" - /** - * Enables vermin to bite other creatures, injecting the specified material. See `[SPECIALATTACK_INJECT_EXTRACT]` for details about injection - this token presumably works in a similar manner. - * - * Arguments: - * - * * `chance`: The chance to inject the material - * * `verb`: The verb to use (e.g. "bitten, stung") - * * `material`: The material to inject - * * `material_state`: The material state to inject - * - * Appears as `VERMIN_BITE:100:bitten:SomeMaterial:SomeMaterialState` - */ - | { - VerminBite: { - /** - * The chance to inject the material - */ - chance: number; - /** - * The verb to use (e.g. "bitten, stung") - */ - verb: string; - /** - * The material to inject - */ - material: string[]; - /** - * The material state to inject - */ - material_state: string; - }; - } - /** - * Some dwarves will hate the creature and get unhappy thoughts when around it. See the list of hateable vermin for details. - * - * Appears as `VERMIN_HATEABLE` - */ - | "VerminHateable" - /** - * This makes the creature move in a swarm of creatures of the same race as it (e.g. swarm of flies, swarm of ants). - * - * Appears as `VERMIN_MICRO` - */ - | "VerminMicro" - /** - * The creature cannot be caught by fishing. - * - * Appears as `VERMIN_NOFISH` - */ - | "VerminNoFish" - /** - * The creature will not be observed randomly roaming about the map. - * - * Appears as `VERMIN_NOROAM` - */ - | "VerminNoRoam" - /** - * The creature cannot be caught in baited animal traps; however, a "catch live land animal" task may still be able to capture one if a dwarf finds one roaming around. - * - * Appears as `VERMIN_NOTRAP` - */ - | "VerminNoTrap" - /** - * Old shorthand for "does cat stuff". Contains `[AT_PEACE_WITH_WILDLIFE]` + `[RETURNS_VERMIN_KILLS_TO_OWNER]` + `[HUNTS_VERMIN]` + `[ADOPTS_OWNER]`. - * - * Appears as `VERMINHUNTER` - */ - | "VerminHunter" - /** - * Sets the creature to be active during the evening in adventurer mode. - * - * Appears as `VESPERTINE` - */ - | "Vespertine" - /** - * Value should determine how close you have to get to a critter before it attacks (or prevents adv mode travel etc.) Default is 20. - * - * Appears as `VIEWRANGE:20` - */ - | { - ViewRange: { - /** - * The view range of the creature, default is 20 - */ - view_range: number; - }; - } - /** - * The width of the creature's vision arcs, in degrees (i.e. 0 to 360). The first number is binocular vision, the second is non-binocular vision. - * Binocular vision has a minimum of about 10 degrees, monocular, a maximum of about 350 degrees. Values past these limits will be accepted, but will default to ~10 degrees - * and ~350 degrees respectively. - * - * Defaults are 60:120. - * - * Appears as `VISION_ARC:60:120` - */ - | { - VisionArc: { - /** - * The binocular vision arc of the creature, default is 60 - */ - binocular: number; - /** - * The non-binocular vision arc of the creature, default is 120 - */ - non_binocular: number; - }; - } - /** - * Allows the creature to pull caravan wagons. If a civilization doesn't have access to any, it is restricted to trading with pack animals. - * - * Appears as `WAGON_PULLER` - */ - | "WagonPuller" - /** - * Allows the creature to create webs, and defines what the webs are made of. - * - * Arguments: - * - * * `material`: The material of the webs - * - * Appears as `WEBBER:SomeMaterial` - */ - | { - Webber: { - /** - * The material of the webs - */ - material: string[]; - }; - } - /** - * The creature will not get caught in thick webs. Used by creatures who can shoot thick webs (such as giant cave spiders) in order to make them immune to their own attacks. - * - * Appears as `WEBIMMUNE` - */ - | "WebImmune" - /** - * An unknown token. - */ - | "Unknown" - /** - * A night creature - */ - | "NightCreature" - /** - * Not fire immune - */ - | "NotFireImmune" - /** - * Has blood - */ - | "HasBlood" - /** - * Can grasp - */ - | "Grasp" - /** - * The gait of the race - */ - | "RaceGait" - /** - * Cannot breathe water - */ - | "CannotBreatheWater" - /** - * Is a natural animal - */ - | "NaturalAnimal" - /** - * Is a curious beast - */ - | "CuriousBeast" - /** - * Is a flying curious beast - */ - | "CannotBreatheAir"; - +pageWidth: number; /** - * A struct representing a color in the format "foreground:background:brightness". + * height of page */ -export type Color = { - foreground: number; - background: number; - brightness: number; -}; +pageHeight: number } /** - * The color modification of the tile + * The tokens used to define the tile page */ -export type ColorModificationTag = - /** - * The color is as is - */ - "asIs"; - +export type TilePageTag = /** - * A condition that can be applied to a tile/entity + * The dimensions of the tile */ -export type ConditionTag = - /** - * No condition - */ - | "none" - /** - * The start of a condition - */ - | "condition" - /** - * Default condition - */ - | "default" - /** - * A condition of "being animated" - */ - | "animated" - /** - * Condition of being a corpse - */ - | "corpse" - /** - * Condition of being a child - */ - | "child" - /** - * Condition of being a baby - */ - | "baby" - /** - * Condition of being trained for hunting - */ - | "trainedHunter" - /** - * Condition of being trained for war - */ - | "trainedWar" - /** - * Condition of being a list icont - */ - | "listIcon" - /** - * Condition of being a skeleton - */ - | "skeleton" - /** - * Condition of being a skeleton with a skull - */ - | "skeletonWithSkull" - /** - * Condition of being a zombie - */ - | "zombie" - /** - * Condition of being a necromancer - */ - | "necromancer" - /** - * Condition of being male - */ - | "male" - /** - * Condition of being female - */ - | "female" - /** - * Condition of being a vampire - */ - | "vampireCursed" - /** - * Condition of being a ghoul - */ - | "ghoul" - /** - * Condition of being a disturbed dead - */ - | "disturbedDead" - /** - * Condition of being remains - */ - | "remains" - /** - * Condition of being a vermin - */ - | "vermin" - /** - * Condition of being a light vermin - */ - | "lightVermin" - /** - * Condition of being a hive - */ - | "hive" - /** - * Condition of being a small swarm - */ - | "swarmSmall" - /** - * Condition of being a medium swarm - */ - | "swarmMedium" - /** - * Condition of being a large swarm - */ - | "swarmLarge" - /** - * Condition of being not an artifact - */ - | "notArtifact" - /** - * Condition of being a crafted artifact - */ - | "craftedArtifact" - /** - * Condition of being dyed - */ - | "dye" - /** - * Condition of not being dyed - */ - | "notDyed" - /** - * Condition of being a crop - */ - | "crop" - /** - * Condition of being a seed - */ - | "seed" - /** - * Condition of being a plant (picked) - */ - | "picked" - /** - * Condition of being a shrub - */ - | "shrub" - /** - * Condition of being a sapling - */ - | "sapling" - /** - * Condition of being a crop sprout - */ - | "cropSprout" - /** - * Condition of being a large crop - */ - | "cropL" - /** - * Condition of being a medium crop - */ - | "cropM" - /** - * Condition of being a small crop - */ - | "cropR" - /** - * Condition of being a dead shrub - */ - | "shrubDead" - /** - * Condition of not being a child - */ - | "notChild" - /** - * Condition of being at least so many hauled - */ - | "haulCountMin" - /** - * Condition of being at most so many hauled - */ - | "haulCountMax" - /** - * Condition of being a worn item - */ - | "itemWorn" - /** - * Condition of having a profession - */ - | "professionCategory" - /** - * Condition of being a class - */ - | "class" - /** - * Condition of being a syndrome class - */ - | "syndromeClass" - /** - * Condition of being a caste - */ - | "caste" - /** - * Condition of being a tissue layer - */ - | "tissueLayer" - /** - * Condition of being a material flag - */ - | "materialFlag" - /** - * Condition of being a material type - */ - | "materialType" - /** - * Condition of being off if an item is present - */ - | "shutOffIfItemPresent" - /** - * Condition of being a random part index - */ - | "randomPartIndex" - /** - * Condition of being a ghost - */ - | "ghost" - /** - * Condition of being a tissue that may have color - */ - | "tissueMayHaveColor" - /** - * Condition of being a tissue that is at least so long - */ - | "tissueMinLength" - /** - * Condition of being a tissue that is at most so long - */ - | "tissueMaxLength" - /** - * Condition of being a tissue at least so curly - */ - | "tissueMinCurly" - /** - * Condition of being a tissue at most so curly - */ - | "tissueMaxCurly" - /** - * Condition of being a tissue that may have a shape - */ - | "tissueMayHaveShaping" - /** - * Condition of being a tissue that is not shaped - */ - | "tissueNotShaped" - /** - * Condition of being a swapped tissue - */ - | "tissueSwap" - /** - * Condition of being a specific layer (start layer definition) - */ - | "layer" - /** - * Condition of being a specific layer set of layers - */ - | "layerSet" - /** - * Condition of being a specific layer group - */ - | "layerGroup" - /** - * Condition of being a specific layer group set of layers - */ - | "endLayerGroup" - /** - * Condition of being the upper body - */ - | "bodyUpper" - /** - * Condition of being a copy of a template - */ - | "copyOfTemplate" - /** - * Hammerman profession - */ - | "hammerman" - /** - * Master Hammerman profession - */ - | "masterHammerman" - /** - * Spearman profession - */ - | "spearman" - /** - * Master Spearman profession - */ - | "masterSpearman" - /** - * Wrestler profession - */ - | "wrestler" - /** - * Master Wrestler profession - */ - | "masterWrestler" - /** - * Axeman profession - */ - | "axeman" - /** - * Master Axeman profession - */ - | "masterAxeman" - /** - * Swordsman profession - */ - | "swordsman" - /** - * Master Swordsman profession - */ - | "masterSwordsman" - /** - * Maceman profession - */ - | "maceman" - /** - * Master Maceman profession - */ - | "masterMaceman" - /** - * Pikeman profession - */ - | "pikeman" - /** - * Master Pikeman profession - */ - | "masterPikeman" - /** - * Recruit profession - */ - | "recruit" - /** - * Thief profession - */ - | "thief" - /** - * Master Thief profession - */ - | "masterThief" - /** - * Lasher profession - */ - | "lasher" - /** - * Master Lasher profession - */ - | "masterLasher" - /** - * Monster slayer profession - */ - | "monsterSlayer" - /** - * Crossbowman profession - */ - | "crossbowman" - /** - * Master Crossbowman profession - */ - | "masterCrossbowman" - /** - * Bowman profession - */ - | "bowman" - /** - * Master Bowman profession - */ - | "masterBowman" - /** - * Blowgunman profession - */ - | "blowgunman" - /** - * Master Blowgunman profession - */ - | "masterBlowgunman" - /** - * Beat hunter profession - */ - | "beastHunter" - /** - * Scout profession - */ - | "scout" - /** - * Ranger profession - */ - | "ranger" - /** - * Hunter profession - */ - | "hunter" - /** - * Sage profession - */ - | "sage" - /** - * Scholar profession - */ - | "scholar" - /** - * Philosopher profession - */ - | "philosopher" - /** - * Mathematician profession - */ - | "mathematician" - /** - * Historian profession - */ - | "historian" - /** - * Astronomer profession - */ - | "astronomer" - /** - * Naturalist profession - */ - | "naturalist" - /** - * Chemist profession - */ - | "chemist" - /** - * Geographer profession - */ - | "geographer" - /** - * Scribe profession - */ - | "scribe" - /** - * Bookbinder profession - */ - | "bookbinder" - /** - * Performer profession - */ - | "performer" - /** - * Poet profession - */ - | "poet" - /** - * Bard profession - */ - | "bard" - /** - * Dancer profession - */ - | "dancer"; - +"tileDim" | /** - * The `Creature` struct represents a creature in a Dwarf Fortress, with the properties - * that can be set in the raws. Not all the raws are represented here, only the ones that - * are currently supported by the library. - * - * Some items like `CREATURE_VARIATION` and `CREATURE_VARIATION_CASTE` are saved in their raw - * format. `SELECT_CREATURE` is saved here as a sub-creature object with all the properties - * from that raw. This is because the `SELECT_CREATURE` raws are used to create new creatures - * based on the properties of the creature they are applied to. But right now the application - * of those changes is not applied, in order to preserve the original creature. So instead, - * they are saved and can be applied later (at the consumer's discretion). + * The dimensions of the page */ -export type Creature = { - /** - * The `metadata` field is of type `RawMetadata` and is used to provide additional information - * about the raws the `Creature` is found in. - */ - metadata?: Metadata | null; - /** - * The `identifier` field is a string that represents the identifier of the creature. It is used - * to uniquely identify the creature (however it is not guaranteed to be unique across object types - * or all raws parsed, *especially* if you are parsing multiple versions of the same raws). - */ - identifier: string; - /** - * The `castes` field is a vector of `Caste` objects. Each `Caste` object represents a caste of the - * creature. For example, a creature may have a `MALE` and `FEMALE` caste. Each `Caste` object has - * its own properties, such as `name`, `description`, `body`, `flags`, etc. - * - * A lot of the properties of the `Creature` object are actually properties of a special `Caste`, `ALL`. - */ - castes: Caste[]; - /** - * Any tags that are not parsed into their own fields are stored in the `tags` field. - */ - tags?: CreatureTag[] | null; - /** - * The biomes that this creature can be found in - */ - biomes?: BiomeTag[] | null; - /** - * Pref strings are things that make dwarves (or others?) like or dislike the creature. - */ - prefStrings?: string[] | null; - /** - * The tile that represents the creature in the game (classic mode) - */ - tile?: Tile | null; - /** - * Determines the chances of a creature appearing within its environment, with higher values resulting in more frequent appearance. - * - * Also affects the chance of a creature being brought in a caravan for trading. The game effectively considers all creatures that - * can possibly appear and uses the FREQUENCY value as a weight - for example, if there are three creatures with frequencies 10/25/50, - * the creature with `[FREQUENCY:50]` will appear approximately 58.8% of the time. - * - * Defaults to 50 if not specified. - * - * Minimum value is 0, maximum value is 100. - * - * Note: not to be confused with `[POP_RATIO]`. - */ - frequency?: number | null; - /** - * The minimum/maximum numbers of how many creatures per spawned cluster. Vermin fish with this token in combination with - * temperate ocean and river biome tokens will perform seasonal migrations. - * - * Defaults to [1,1] if not specified. - */ - clusterNumber?: [number, number] | null; - /** - * The minimum/maximum numbers of how many of these creatures are present in each world map tile of the appropriate region. - * - * Defaults to [1,1] if not specified. - */ - populationNumber?: [number, number] | null; - /** - * Depth that the creature appears underground. Numbers can be from 0 to 5. 0 is actually 'above ground' and can be used if the - * creature is to appear both above and below ground. Values from 1-3 are the respective cavern levels, 4 is the magma sea and - * 5 is the HFS. - * - * A single argument may be used instead of min and max. - * - * Civilizations that can use underground plants or animals will only export (via the embark screen or caravans) things that are available at depth 1. - * - * Default [0, 0] (aboveground) - */ - undergroundDepth?: [number, number] | null; - /** - * Like `[BABYNAME]`, but applied regardless of caste. - */ - generalBabyName?: Name | null; - /** - * Like `[CHILDNAME]`, but applied regardless of caste. - */ - generalChildName?: Name | null; - /** - * The generic name for any creature of this type - will be used when distinctions between caste are unimportant. For names for specific castes, - * use `[CASTE_NAME]` instead. If left undefined, the creature will be labeled as "nothing" by the game. - */ - name: Name; - /** - * Copies another specified creature. This will override any definitions made before it; essentially, it makes this creature identical to the other one, - * which can then be modified. Often used in combination with `[APPLY_CREATURE_VARIATION]` to import standard variations from a file. - * - * The vanilla giant animals and animal peoples are examples of this token combination. - */ - copyTagsFrom?: string | null; - /** - * Applies the specified creature variation. - * - * These are stored "in the raw", i.e. how they appear in the raws. They are not handled until the end of the parsing process. - */ - applyCreatureVariation?: string[] | null; - /** - * A generated field that is used to uniquely identify this object. It is generated from the `metadata`, `identifier`, and `ObjectType`. - * - * This field is always serialized. - */ - objectId: string; - /** - * Various `SELECT_CREATUR` modifications. - */ - selectCreatureVariation?: SelectCreature[] | null; -}; - +"pageDim" | /** - * A creature effect. + * The file path */ -export type CreatureEffect = { - severity: number; - probability: number; - affectedBodyPartsByCategory?: string[] | null; - affectedBodyPartsByType?: string[] | null; - affectedBodyPartsByToken?: string[] | null; - tags?: CreatureEffectPropertyTag[] | null; - start: number; - peak: number; - end: number; - dwfStretch?: number | null; -}; - +"file" | /** - * An enum representing a creature effect property tag. + * An unknown token */ -export type CreatureEffectPropertyTag = - /** - * The severity of the effect. Higher values appear to be worse, with SEV:1000 `CE_NECROSIS` causing a part to near-instantly become rotten. - */ - | "Severity" - /** - * The probability of the effect actually manifesting in the victim, as a percentage. 100 means always, 1 means a 1 in 100 chance. - */ - | "Probability" - /** - * (Optional) Determines if the effect can be hindered by the target creature's disease resistance attribute. - * Without this token, disease resistance is ignored. (yes, it's spelled incorrectly) - */ - | "Resistible" - /** - * (Optional) This token presumably causes the severity of the effect to scale with the size of the creature compared - * to the size of the dose of contagion they received, but has yet to be extensively tested. - */ - | "SizeDilutes" - /** - * (Optional) As above, this token has yet to be tested but presumably delays the onset of an effect according to the size of the victim. - */ - | "SizeDelays" - /** - * (Optional; overrides BP tokens) This tag causes an effect to ignore all BP tokens and then forces the game to attempt to apply the effect to - * the limb that came into contact with the contagion - i.e. the part that was bitten by the creature injecting the syndrome material, - * or the one that was splattered by a contact contagion. If an effect can not be applied to the contacted limb (such as `IMPAIR_FUNCTION` on a non-organ) - * then this token makes the effect do nothing. This token also makes inhaled syndromes have no effect. - */ - | "Localized" - /** - * (Optional) This effect only affects tissue layers with the VASCULAR token. - */ - | "VascularOnly" - /** - * (Optional) This effect only affects tissue layers with the MUSCULAR token. - */ - | "MuscularOnly" - /** - * (Optional; overridden by LOCALIZED) Specifies which body parts and tissues the effect is to be applied to. Not every effect requires a target! - * For example, if you wanted to target the lungs of a creature, you would use `BP:BY_CATEGORY:LUNG:ALL`. The effect would act on all body parts - * within the creature with the CATEGORY tag LUNG and affect all tissue layers. For another example, say you wanted to cause the skin to rot off a creature - - * you could use `BP:BY_CATEGORY:ALL:SKIN`, targeting the SKIN tissue on all body parts. Multiple targets can be given in one effect by placing the BP tokens end to end. - * This is one of the most powerful and useful aspects of the syndrome system, as it allows you to selectively target body parts relevant to the contagion, - * like lungs for coal dust inhalation, or the eyes for exposure to an acid gas. - */ - | "BodyPart" - /** - * `BY_CATEGORY:X` to target body parts with a matching `[CATEGORY:X]` body token (or `ALL` to affect everything) - */ - | "ByCategory" - /** - * `BY_TYPE:X` to target body parts having a particular type (`UPPERBODY`, `LOWERBODY`, `HEAD`, `GRASP`, or `STANCE`) - */ - | "ByType" - /** - * `BY_TOKEN:X` to target individual body parts by their ID as specified by the `[BP]` token of the body plan definition. - */ - | "ByToken" - /** - * Determines the time after exposure, in ticks, when the effect starts. Required for all effects. - */ - | "Start" - /** - * (Optional) Determines the time after exposure, in ticks, when the effect reaches its peak intensity. - */ - | "Peak" - /** - * (Optional) Determines the time after exposure, in ticks, when the effect ends. - */ - | "End" - /** - * (Optional) Multiplies the duration values of the effect by the specified amount in Fortress mode. - */ - | "DwfStretch" - /** - * (Optional) Makes the effect begin immediately rather than ramping up. - */ - | "AbruptStart" - /** - * (Optional) Makes the effect end immediately rather than ramping down. - */ - | "AbruptEnd" - /** - * (Optional) Combination of `ABRUPT_START` and `ABRUPT_END`. - */ - | "Abrupt" - /** - * (Optional) Can be hidden by a unit assuming a secret identity, such as a vampire. - */ - | "CanBeHidden" - /** - * Unknown value for default. - */ - | "Unknown"; +"unknown" /** - * An enum representing a creature effect tag. + * A struct representing a tree. */ -export type CreatureEffectTag = - /** - * Afflicts the targeted body part with intense pain. If no target is specified this applies to all body parts. - */ - | "Pain" - /** - * Causes the targeted body part to swell up. Extreme swelling may lead to necrosis. - */ - | "Swelling" - /** - * Causes pus to ooze from the afflicted body part. - */ - | "Oozing" - /** - * Causes the targeted body part to undergo bruising. - */ - | "Bruising" - /** - * Covers the targeted body part with blisters. - */ - | "Blisters" - /** - * Causes numbness in the affected body part, blocking pain. Extreme numbness may lead to sensory nerve damage. - * If no target is specified this applies to all body parts. - */ - | "Numbness" - /** - * Causes complete paralysis of the affected body part. Paralysis on a limb may lead to motor nerve damage. - * If no target is specified this causes total paralysis, which can lead to suffocation of smaller creatures. - */ - | "Paralysis" - /** - * Causes the Fever condition. - */ - | "Fever" - /** - * Causes the targeted body part to start bleeding, with heavy enough bleeding resulting in the death of the sufferer. - * Some conditions seem to cause bleeding to be fatal no matter how weak. - */ - | "Bleeding" - /** - * This effect results in the sufferer periodically coughing blood, which stains the tile they're on and requires cleanup. - * It doesn't appear to be lethal, but may cause minor bleeding damage. - */ - | "CoughingBlood" - /** - * This effect results in the sufferer periodically vomiting blood, which stains the tile they're on and requires cleanup. - * It doesn't appear to be lethal, but may cause minor bleeding damage. - */ - | "VomitingBlood" - /** - * Causes the Nausea condition, and heavy vomiting. Can eventually lead to dehydration and death. - */ - | "Nausea" - /** - * Renders the creature unconscious. - */ - | "Unconsciousness" - /** - * Causes the targeted body part to rot, with associated tissue damage, miasma emission and bleeding. - * The victim slowly bleeds to death if the wound is not treated. Badly necrotic limbs will require amputation. - */ - | "Necrosis" - /** - * An organ afflicted with this effect is rendered inoperable. - * E.g., if both lungs are impaired the creature can't breathe and will suffocate. This token only affects organs, not limbs. - */ - | "ImpairFunction" - /** - * Causes the Drowsiness condition - */ - | "Drowsiness" - /** - * Inflicts the Dizziness condition, occasional fainting and a general slowdown in movement and work speed. - */ - | "Dizziness" - /** - * Decreases the severity of pain produced by wounds or syndrome effects on the targeted body part. - * The SEV value probably controls by how much the pain is decreased. - */ - | "ReducePain" - /** - * Decreases the severity of swelling on the targeted body part. - */ - | "ReduceSwelling" - /** - * Decreases the severity of any paralysis effects on the targeted body part. - */ - | "ReduceParalysis" - /** - * Decreases the severity of any dizziness the creature has. - */ - | "ReduceDizziness" - /** - * Decreases the severity of any nausea the creature has. - */ - | "ReduceNausea" - /** - * Decreases the severity of any fever the creature has. - */ - | "ReduceFever" - /** - * Decreases the severity of the bleeding of any wounds or syndrome effects on the targeted body part. - * The SEV value probably controls by how much the bleeding is decreased. - */ - | "StopBleeding" - /** - * Closes any wounds on the targeted body part with speed depending on the SEV value. - */ - | "CloseOpenWounds" - /** - * Probably decreases the severity of the infection from infected wounds over time. - */ - | "CureInfection" - /** - * Heals the tissues of the targeted body part with speed depending on the SEV value. - */ - | "HealTissues" - /** - * Heals the nerves of the targeted body part with speed depending on the SEV value. - */ - | "HealNerves" - /** - * Causes missing body parts to regrow. SEV controls how quickly body parts are regrown. - */ - | "RegrowParts" - /** - * Add a tag - */ - | "AddTag" - /** - * Remove a tag - */ - | "RemoveTag" - /** - * Display name of the effect - */ - | "DisplayName" - /** - * Display tile of the effect - */ - | "DisplayTile" - /** - * Whether the tile flashes - */ - | "FlashTile" - /** - * Physical attribute change - */ - | "PhysAttChange" - /** - * Mental attribute change - */ - | "MentAttChange" - /** - * Speed change - */ - | "SpeedChange" - /** - * Skill roll adjustment - */ - | "SkillRollAdjust" - /** - * Body appearance modifier - */ - | "BodyAppearanceModifier" - /** - * Body part appearance modifier - */ - | "BodyPartAppearanceModifier" - /** - * Body transformation - */ - | "BodyTransformation" - /** - * Material force multiplier - */ - | "MaterialForceMultiplier" - /** - * Can do an interaction - */ - | "CanDoInteraction" - /** - * Can do a special attack interaction - */ - | "SpecialAttackInteraction" - /** - * Can do a body mat interaction - */ - | "BodyMatInteraction" - /** - * Can sense creatures of a class - */ - | "SenseCreatureClass" - /** - * Feel emotion - */ - | "FeelEmotion" - /** - * Changes the personality of the creature - */ - | "ChangePersonality" - /** - * Erratic behavior - */ - | "ErraticBehavior" - /** - * Unknown - */ - | "Unknown"; - +export type Tree = { /** - * An enum representing a creature tag. + * Tree will yield logs made of that material. Instead, if it's `[TREE:NONE]`, no logs will result. + * Materials are typically found in other raws.. */ -export type CreatureTag = - /** - * If set, the creature will blink between its `[Tile]` and its `[AltTile]`. - * - * Arguments: - * - * - the 'character' or tile number - * - * Appears as `ALTTILE:123` - */ - | { - AltTile: { - /** - * The character or tile number - */ - character: TileCharacter; - }; - } - /** - * Applies the specified creature variation with the given arguments to the creature. See `[ApplyCreatureVariation]` for more information. - * - * Appears as `APPLY_CREATURE_VARIATION:SOME_VARIATION` or `APPLY_CREATURE_VARIATION:SOME_VARIATION:ARG1:ARG2:ARG3` - */ - | { - ApplyCreatureVariation: { - /** - * Creature variation ID to apply - */ - id: string; - /** - * (Optional) any number of arguments to pass to the creature variation - */ - args: string[]; - }; - } - /** - * Applies the effects of all pending `[CV_ADD_TAG]` and `[CV_REMOVE_TAG]` tokens that have been defined in the current creature (so far). - * - * Appears as `APPLY_CURRENT_CREATURE_VARIATION` - */ - | "ApplyCurrentCreatureVariation" - /** - * Enables the creature to be kept in artificial hives by beekeepers. - * - * Appears as `ARTIFICIAL_HIVEABLE` - */ - | "ArtificialHiveable" - /** - * Select a biome the creature may appear in. - * - * Appears as `BIOME:SomeBiomeId` - */ - | { - Biome: { - /** - * Biome identifier - */ - id: string; - }; - } - /** - * Defines a caste - */ - | { - Caste: { - /** - * The name of the caste - */ - name: string; - }; - } - /** - * Multiplies frequency by a factor of (integer)%. - * - * Appears as `CHANGE_FREQUENCY_PERC:100` - */ - | { - ChangeFrequencyPercent: { - /** - * The percentage to change the frequency by - */ - percent: number; - }; - } - /** - * The minimum/maximum numbers of how many creatures per spawned cluster. Vermin fish with this token in - * combination with temperate ocean and river biome tokens will perform seasonal migrations. - * - * Defaults to 1:1 if not specified. - * - * Appears as `CLUSTER_NUMBER:1:1` - */ - | { - ClusterNumber: { - /** - * The minimum number of creatures per spawned cluster - */ - min: number; - /** - * The maximum number of creatures per spawned cluster - */ - max: number; - }; - } - /** - * Copies another specified creature. This will override any definitions made before it; essentially, it makes this creature identical to the other one, which can then - * be modified. Often used in combination with `[APPLY_CREATURE_VARIATION]` to import standard variations from a file. The vanilla giant animals and animal peoples are - * examples of this token combination. - * - * Arguments: - * - * * `creature`: The identifier of the creature to copy - * - * Appears as `COPY_TAGS_FROM:SomeCreature` - */ - | { - CopyTagsFrom: { - /** - * The identifier of the creature to copy - */ - creature: string; - }; - } - /** - * Creatures active in their civilization's military will use this tile instead. - * - * Appears as `CREATURE_SOLDIER_TILE:123` - */ - | { - CreatureSoldierTile: { - /** - * The character or tile number - */ - character: TileCharacter; - }; - } - /** - * The symbol of the creature in ASCII mode. - * - * Appears as `CREATURE_TILE:123` - */ - | { - CreatureTile: { - /** - * The character or tile number - */ - character: TileCharacter; - }; - } - /** - * The color of the creature's tile. - * - * Arguments: - * - * * `foreground`: The foreground color - * * `background`: The background color - * * `brightness`: The brightness of the color - * - * Appears as `COLOR:0:0:0` - */ - | { - Color: { - /** - * The foreground color - */ - foreground: number; - /** - * The background color - */ - background: number; - /** - * The brightness of the color - */ - brightness: number; - }; - } - /** - * Adding this token to a creature prevents it from appearing in generated worlds (unless it's marked as always present for a particular - * civilization). For example, adding it to dogs will lead to worlds being generated without dogs in them. Also removes the creature from the - * object testing arena's spawn list. If combined with [Fanciful], artistic depictions of the creature will occur regardless. Used by centaurs, - * chimeras and griffons in the vanilla game. - * - * Appears as `DOES_NOT_EXIST` - */ - | "DoesNotExist" - /** - * Makes the creature appear as a large 3x3 wagon responsible for carrying trade goods, pulled by two `[WAGON_PULLER]` creatures and driven by a merchant. - * - * Appears as `EQUIPMENT_WAGON` - */ - | "EquipmentWagon" - /** - * The creature is considered evil and will only show up in evil biomes. Civilizations with `[EntityToken::UseEvilAnimals]` can domesticate them - * regardless of exotic status. Has no effect on cavern creatures except to restrict taming. A civilization with evil creatures can colonize evil areas. - * - * Appears as `EVIL` - */ - | "Evil" - /** - * The creature is a thing of legend and known to all civilizations. Its materials cannot be requested or preferred. The tag also adds some art value modifiers. - * Used by a number of creatures. Conflicts with `[CasteToken::CommonDomestic]`. - */ - | "Fanciful" - /** - * Determines the chances of a creature appearing within its environment, with higher values resulting in more frequent appearance. Also affects the chance of a - * creature being brought in a caravan for trading. The game effectively considers all creatures that can possibly appear and uses the FREQUENCY value as a weight - * - * For example, if there are three creatures with frequencies 10/25/50, the creature with [FREQUENCY:50] will appear approximately 58.8% of the time. - * - * Defaults to 50 if not specified. Not to be confused with `[PopulationRatio]`. - * - * Appears as `FREQUENCY:50` - */ - | { - Frequency: { - /** - * The frequency of the creature, a number between 0 and 100 (inclusive) - */ - frequency: number; - }; - } - /** - * Name of the creatures baby form. Applies to all castes but can be overridden by `[CasteToken::BabyName]`. - * - * Appears as `GENERAL_BABY_NAME:BabyName:BabyNames` - */ - | { - GeneralBabyName: { - /** - * The name of the baby - */ - singular: string; - /** - * The plural name of the baby - */ - plural: string; - }; - } - /** - * Name of the creatures child form. Applies to all castes but can be overridden by `[CasteToken::ChildName]`. - * - * Appears as `GENERAL_CHILD_NAME:ChildName:ChildNames` - */ - | { - GeneralChildName: { - /** - * The name of the child - */ - singular: string; - /** - * The plural name of the child - */ - plural: string; - }; - } - /** - * Found on procedurally generated creatures like forgotten beasts, titans, demons, angels, and night creatures. Cannot be specified in user-defined raws. - * - * Appears as `GENERATED` - */ - | "Generated" - /** - * The color of the creature's glow tile. - * - * Arguments: - * - * * `foreground`: The foreground color - * * `background`: The background color - * * `brightness`: The brightness of the color - * - * Appears as `GLOWCOLOR:0:0:0` - */ - | { - GlowColor: { - /** - * The foreground color - */ - foreground: number; - /** - * The background color - */ - background: number; - /** - * The brightness of the color - */ - brightness: number; - }; - } - /** - * The creature's tile when it is glowing. - * - * Arguments: - * - * * `character`: The character or tile number - * - * Appears as `GLOWTILE:123` - */ - | { - GlowTile: { - /** - * The character or tile number - */ - character: TileCharacter; - }; - } - /** - * Creature is considered good and will only show up in good biomes - unicorns, for example. Civilizations with `[EntityToken::UseGoodAnimals]` can - * domesticate them regardless of exotic status. Has no effect on cavern creatures except to restrict taming. A civilization that has good - * creatures can colonize good areas in world-gen. - * - * Appears as `GOOD` - */ - | "Good" - /** - * When using tags from an existing creature, inserts new tags at the end of the creature. - * - * Appears as `GO_TO_END` - */ - | "GoToEnd" - /** - * When using tags from an existing creature, inserts new tags at the beginning of the creature. - * - * Appears as `GO_TO_START` - */ - | "GoToStart" - /** - * When using tags from an existing creature, inserts new tags after the specified tag. - * - * Arguments: - * - * * `tag`: The tag to insert after - * - * Appears as `GO_TO_TAG:TAG` - */ - | { - GoToTag: { - /** - * The tag to insert after - */ - tag: string; - }; - } - /** - * What product is harvested from beekeeping. - * - * Arguments: - * - * * `number`: The number of products harvested - * * `time`: The time it takes before the next harvest - * * `item tokens`: The item tokens that are harvested (some arbitrary list of items) - * - * Appears as `HARVEST_PRODUCT:1:1:ITEM_TOKENS` - */ - | { - HarvestProduct: { - /** - * The number of products harvested - */ - number: number; - /** - * The time it takes before the next harvest - */ - time: number; - /** - * The item tokens that are harvested (some arbitrary list of items) - */ - item_tokens: string[]; - }; - } - /** - * This is the core requisite tag allowing the creature to spawn as a wild animal in the appropriate biomes. Requires specifying a [Biome] in which the creature will spawn. - * Does not require specifying a frequency, population number, or cluster number. - * - * This tag stacks with `[CasteToken::Megabeast]`, `[CasteToken::SemiMegabeast]`, or `[CasteToken::NightCreatureHunter]`; if used with one of these tags, the creature will spawn - * as both a boss and as a wild animal. This tag does not stack with `[CasteToken::FeatureBeast]` and if both are used the creature will not spawn. This tag is unaffected by - * `[CasteToken::Demon]`. - * - * Appears as `LARGE_ROAMING` - */ - | "LargeRoaming" - /** - * Allows you to play as a wild animal of this species in adventurer mode. Prevents trading of (tame) instances of this creature in caravans. - * - * Appears as `LOCAL_POPS_CONTROLLABLE` - */ - | "LocalPopsControllable" - /** - * Wild animals of this species may occasionally join a civilization. Prevents trading of (tame) instances of this creature in caravans. - * - * Appears as `LOCAL_POPS_PRODUCE_HEROES` - */ - | "LocalPopsProduceHeroes" - /** - * The creatures will scatter if they have this tag, or form tight packs if they don't. - * - * Appears as `LOOSE_CLUSTERS` - */ - | "LooseClusters" - /** - * Marks if the creature is an actual real-life creature. Only used for age-names at present. - */ - | "Mundane" - /** - * The generic name for any creature of this type - will be used when distinctions between caste are unimportant. For names for specific castes, use `[CASTE_NAME]` instead. - * If left undefined, the creature will be labeled as "nothing" by the game. - * - * Appears as `NAME:Name:Names:NameAdj` - */ - | { - Name: { - /** - * The name of the creature - */ - name: string; - /** - * The plural name of the creature - */ - plural_name: string; - /** - * The adjective form of the creature's name - */ - adjective: string; - }; - } - /** - * Adds a material to selected materials. Used immediately after `[SELECT_MATERIAL]`. - * - * Appears as `PLUS_MATERIAL:Material` - */ - | { - PlusMaterial: { - /** - * The material to add - */ - material: string; - }; - } - /** - * The minimum/maximum numbers of how many of these creatures are present in each world map tile of the appropriate region. Defaults to 1:1 if not specified. - * - * Appears as `POPULATION_NUMBER:1:1` - */ - | { - PopulationNumber: { - /** - * The minimum number of creatures per spawned cluster - */ - min: number; - /** - * The maximum number of creatures per spawned cluster - */ - max: number; - }; - } - /** - * Sets what other creatures prefer about this creature. - * - * "Urist likes dwarves for their beards." - * - * Multiple entries will be chosen from at random. Creatures lacking a PREFSTRING token will never appear under another's preferences. - * - * Appears as `PREFSTRING:PrefString` - */ - | { - PrefString: { - /** - * The preference string - */ - pref_string: string; - }; - } - /** - * The generic name for members of this profession, at the creature level. In order to give members of specific castes different names for professions, - * use `[CASTE_PROFESSION_NAME]` instead. - * - * Appears as `PROFESSION_NAME:ProfessionId:ProfessionName:ProfessionNames` - */ - | { - ProfessionName: { - /** - * The profession id - */ - id: string; - /** - * The name of the profession - */ - name: string; - /** - * The plural name of the profession - */ - plural_name: string; - }; - } - /** - * Removes a material from the creature. - * - * Appears as `REMOVE_MATERIAL:Material` - */ - | { - RemoveMaterial: { - /** - * The material to remove - */ - material: string; - }; - } - /** - * Removes a tissue from the creature. - * - * Appears as `REMOVE_TISSUE:Tissue` - */ - | { - RemoveTissue: { - /** - * The tissue to remove - */ - tissue: string; - }; - } - /** - * The creature will only show up in "savage" biomes. Has no effect on cavern creatures. Cannot be combined with [GOOD] or [EVIL]. - * - * Appears as `SAVAGE` - */ - | "Savage" - /** - * Adds an additional previously defined caste to the selection. Used after `[SELECT_CASTE]`. - * - * Appears as `SELECT_ADDITIONAL_CASTE:Caste` - */ - | { - SelectAdditionalCaste: { - /** - * The caste to add - */ - caste: string; - }; - } - /** - * Selects a previously defined caste - * - * Appears as `SELECT_CASTE:Caste` - */ - | { - SelectCaste: { - /** - * The caste to select - */ - caste: string; - }; - } - /** - * Selects a locally defined material. Can be ALL. - * - * Appears as `SELECT_MATERIAL:Material` - */ - | { - SelectMaterial: { - /** - * The material to select - */ - material: string; - }; - } - /** - * Selects a tissue for editing. - * - * Appears as `SELECT_TISSUE:Tissue` - */ - | { - SelectTissue: { - /** - * The tissue to select - */ - tissue: string; - }; - } - /** - * Boasting speeches relating to killing this creature. Examples include `text_dwarf.txt` and `text_elf.txt` in `data\vanilla\vanilla_creatures\objects`. - * - * Appears as `SLAIN_CASTE:SomeSpeechSet` - */ - | { - SlainSpeech: { - /** - * The speech set to use - */ - slain_speech: string; - }; - } - /** - * Determines how keen a creature's sense of smell is - lower is better. At 10000, a creature cannot smell at all. - * - * Appears as `SMELL_TRIGGER:10000` - */ - | { - SmellTrigger: { - /** - * The smell trigger - */ - smell_trigger: number; - }; - } - /** - * If this creature is active in its civilization's military, it will blink between its default tile and this one. - * - * Appears as `SOLDIER_ALTTILE:SomeTile` - */ - | { - SoldierAltTile: { - /** - * The tile to use - */ - tile: string; - }; - } - /** - * Found on generated angels. This is the historical figure ID of the deity with which the angel is associated. Since HFIDs are not predictable before worldgen, - * this isn't terribly usable in mods. - * - * Appears as `SOURCE_HFID:123` - */ - | { - SourceHfid: { - /** - * The historical figure ID - */ - hfid: number; - }; - } - /** - * Sets what religious spheres the creature is aligned to, for purposes of being worshipped via the [POWER] token. Also affects the layout of hidden fun stuff, - * and the creature's name. - * - * Appears as `SPHERE:SomeSphere` - */ - | { - Sphere: { - /** - * The sphere to use - */ - sphere: string; - }; - } - /** - * Begins defining a tissue in the creature file. Follow this with standard tissue definition tokens to define the tissue properties. - * - * Arguments: - * - * * `name`: The name of the tissue - * - * Appears as `TISSUE:SomeTissue` - */ - | { - Tissue: { - /** - * The name of the tissue - */ - name: string; - }; - } - /** - * A large swarm of vermin can be disturbed, usually in adventurer mode. - * - * Appears as `TRIGGERABLE_GROUP:5:10` - */ - | { - TriggerableGroup: { - /** - * The minimum number of vermin in the swarm - */ - min: number; - /** - * The maximum number of vermin in the swarm - */ - max: number; - }; - } - /** - * Creature will occur in every region with the correct biome. Does not apply to [EVIL]/[GOOD] tags. - * - * Appears as `UBIQUITOUS` - */ - | "Ubiquitous" - /** - * Depth that the creature appears underground. Numbers can be from 0 to 5. 0 is actually 'above ground' and can be used if the creature is to appear both above and below ground. - * Values from 1-3 are the respective cavern levels, 4 is the magma sea and 5 is the HFS. A single argument may be used instead of min and max. Demons use only 5:5; - * user-defined creatures with both this depth and [FLIER] will take part in the initial wave from the HFS alongside generated demons, but without [FLIER] they will only spawn from - * the map edges. Civilizations that can use underground plants or animals will only export (via the embark screen or caravans) things that are available at depth 1. - * - * Arguments: - * - * * `min`: The minimum depth - * * `max`: The maximum depth - * - * Appears as `UNDERGROUND_DEPTH:1:3` - */ - | { - UndergroundDepth: { - /** - * The minimum depth - */ - min: number; - /** - * The maximum depth - */ - max: number; - }; - } - /** - * Defines a new caste derived directly from a previous caste. The new caste inherits all properties of the old one. The effect of this tag is automatic if one has not yet defined any castes: - * "Any caste-level tag that occurs before castes are explicitly declared is saved up and placed on any caste that is declared later, unless the caste is explicitly derived from another caste." - * - * "When DF detects duplicate tokens in the raws of the same object, a failsafe seems to kick in; it takes the bottom-most of the duplicates, and disregards the others. In the case of tokens - * added by a mod, it prioritizes the duplicate in the mod." This means that if a tag is defined in the base-caste and redefined in the derived caste, the derived tag overwrites the base tag. - * - * Arguments: - * - * * `caste`: The name of the new caste - * * `original_caste`: The name of the original caste to copy - * - * Appears as `USE_CASTE:SomeCaste:SomeOriginalCaste` - */ - | { - UseCaste: { - /** - * The name of the new caste - */ - caste: string; - /** - * The name of the original caste to copy - */ - original_caste: string; - }; - } - /** - * Defines a new local creature material and populates it with all properties defined in the specified local creature material. - * - * Arguments: - * - * * `material`: The name of the new material - * * `original_material`: The name of the original material to copy - * - * Appears as `USE_MATERIAL:SomeMaterial:SomeOriginalMaterial` - */ - | { - UseMaterial: { - /** - * The name of the new material - */ - material: string; - /** - * The name of the original material to copy - */ - original_material: string; - }; - } - /** - * Defines a new local creature material and populates it with all properties defined in the specified template. There seems to be a limit of 200 materials per creature. - * - * Arguments: - * - * * `material`: The name of the new material - * * `template`: The name of the template to copy - * - * Appears as `USE_MATERIAL_TEMPLATE:SomeMaterial:SomeTemplate` - */ - | { - UseMaterialTemplate: { - /** - * The name of the new material - */ - material: string; - /** - * The name of the template to copy - */ - template: string; - }; - } - /** - * Defines a new local creature tissue and populates it with all properties defined in the local tissue specified in the second argument. - * - * Arguments: - * - * * `tissue`: The name of the new tissue - * * `original_tissue`: The name of the original tissue to copy - * - * Appears as `USE_TISSUE:SomeTissue:SomeOriginalTissue` - */ - | { - UseTissue: { - /** - * The name of the new tissue - */ - tissue: string; - /** - * The name of the original tissue to copy - */ - original_tissue: string; - }; - } - /** - * Loads a tissue template listed in `OBJECT:TISSUE_TEMPLATE` files, such as `tissue_template_default.txt`. - * - * Arguments: - * - * * `tissue`: The name of the new tissue - * * `template`: The name of the template to copy - * - * Appears as `USE_TISSUE_TEMPLATE:SomeTissue:SomeTemplate` - */ - | { - UseTissueTemplate: { - /** - * The name of the new tissue - */ - tissue: string; - /** - * The name of the template to copy - */ - template: string; - }; - } - /** - * Changes the language of the creature into unintelligible 'kobold-speak', which creatures of other species will be unable to understand. If a civilized creature has this and is not - * part of a [SKULKING] civ, it will tend to start wars with all nearby civilizations and will be unable to make peace treaties due to 'inability to communicate'. - * - * Appears as `UTTERNANCES` - */ - | "Utterances" - /** - * The vermin creature will attempt to eat exposed food. See `[PENETRATEPOWER]`. Distinct from `[VERMIN_ROTTER]`. - * - * Appears as `VERMIN_EATER` - */ - | "VerminEater" - /** - * The vermin appears in water and will attempt to swim around. - * - * Appears as `VERMIN_FISH` - */ - | "VerminFish" - /** - * The creature appears in "general" surface ground locations. Note that this doesn't stop the creature from flying if it can (most vermin birds have this tag). - * - * Appears as `VERMIN_GROUNDER` - */ - | "VerminGrounder" - /** - * The vermin are attracted to rotting stuff and loose food left in the open and cause unhappy thoughts to dwarves who encounter them. Present on flies, knuckle worms, - * acorn flies, and blood gnats. Speeds up decay? - * - * Appears as `VERMIN_ROTTER` - */ - | "VerminRotter" - /** - * The creature randomly appears near dirt or mud, and may be uncovered by creatures that have the `[ROOT_AROUND]` interaction such as geese and chickens. - * Dwarves will ignore the creature when given the "Capture live land animal" task. - * - * Appears as `VERMIN_SOIL` - */ - | "VerminSoil" - /** - * The vermin will appear in a single tile cluster of many vermin, such as a colony of ants. - * - * Appears as `VERMIN_SOIL_COLONY` - */ - | "VerminSoilColony" - /** - * An unknown tag. - */ - | "Unknown" - /** - * Mates to breed - */ - | "MatesToBreed" - /** - * Has two genders - */ - | "TwoGenders" - /** - * All castes are alive - */ - | "AllCastesAlive" - /** - * Is a small race - */ - | "SmallRace" - /** - * Occurs as an entity - */ - | "OccursAsEntityRace" - /** - * Equipment used - */ - | "Equipment"; - +material: string; /** - * A creature variation. + * What the trunk of the tree is named */ -export type CreatureVariation = { - /** - * Common Raw file Things - */ - metadata?: Metadata | null; - identifier: string; - objectId: string; - /** - * Creature variations are basically just a set of simple tag actions which are applied to - * the creature which is being modified. The tags are applied in order EXCEPT for the convert - * tags which are applied in a reverse order. - */ - rules: CreatureVariationRuleTag[]; - /** - * A creature variation can define any number of arguments which can be used in the rules. - * These arguments replace instances of `!ARGn` in the rules. Use `apply_arguments` to apply - * a set of arguments to a creature variation (and get a very specific variation back). Use - * `apply_to_creature` to apply the variation to a creature (it also takes arguments and will - * apply them to the variation before applying the variation to the creature). - */ - argumentCount: string; -}; - +trunkName: Name | null; /** - * A variation rule for a creature. + * The maximum z-level height of the trunk, starting from +2 z-levels above the ground. + * Valid values: 1-8 + * Default: 1 */ -export type CreatureVariationRuleTag = - /** - * An unknown rule. - */ - | "Unknown" - /** - * Removes a tag from a creature. - */ - | { - RemoveTag: { - /** - * The tag to remove. - */ - tag: string; - /** - * The value to remove. - */ - value: string | null; - }; - } - /** - * Adds a new tag to a creature. - */ - | { - NewTag: { - /** - * The tag to add. - */ - tag: string; - /** - * The value to add. - */ - value: string | null; - }; - } - /** - * Adds a new tag to a creature. - */ - | { - AddTag: { - /** - * The tag to add. - */ - tag: string; - /** - * The value to add. - */ - value: string | null; - }; - } - /** - * Converts a tag on a creature. - */ - | { - ConvertTag: { - /** - * The tag to convert. - */ - tag: string; - /** - * The target value to convert. - */ - target: string | null; - /** - * The replacement value to convert to. - */ - replacement: string | null; - }; - } - /** - * Adds a new tag to a creature if a condition is met. - */ - | { - ConditionalNewTag: { - /** - * The tag to add. - */ - tag: string; - /** - * The value to add. - */ - value: string | null; - /** - * The index of the argument to check. - */ - argument_index: string; - /** - * The requirement for the argument. - */ - argument_requirement: string; - }; - } - /** - * Adds a new tag to a creature if a condition is met. - */ - | { - ConditionalAddTag: { - /** - * The tag to add. - */ - tag: string; - /** - * The value to add. - */ - value: string | null; - /** - * The index of the argument to check. - */ - argument_index: string; - /** - * The requirement for the argument. - */ - argument_requirement: string; - }; - } - /** - * Removes a tag from a creature if a condition is met. - */ - | { - ConditionalRemoveTag: { - /** - * The tag to remove. - */ - tag: string; - /** - * The value to remove. - */ - value: string | null; - /** - * The index of the argument to check. - */ - argument_index: string; - /** - * The requirement for the argument. - */ - argument_requirement: string; - }; - } - /** - * Converts a tag on a creature if a condition is met. - */ - | { - ConditionalConvertTag: { - /** - * The tag to convert. - */ - tag: string; - /** - * The target value to convert. - */ - target: string | null; - /** - * The replacement value to convert to. - */ - replacement: string | null; - /** - * The index of the argument to check. - */ - argument_index: string; - /** - * The requirement for the argument. - */ - argument_requirement: string; - }; - }; - +maxTrunkHeight: number | null; /** - * An enum representing a creature variation tag. + * Upper limit of trunk thickness, in tiles. Has a geometric effect on log yield. + * Valid values: 1-3 + * Default: 1 */ -export type CreatureVariationTag = - /** - * A tag to add a new tag to the creature. - */ - | "NewTag" - /** - * A tag to add a tag to the creature. - */ - | "AddTag" - /** - * A tag to remove a tag from the creature. - */ - | "RemoveTag" - /** - * A tag to convert a tag to a new tag. - */ - | "ConvertTag" - /** - * A tag to convert a tag to a new tag with specific token - */ - | "ConvertTagMaster" - /** - * A tag to convert a tag to a new tag with specific target - */ - | "ConvertTagTarget" - /** - * A tag to convert a tag to a new tag with specific replacement - */ - | "ConvertTagReplacement" - /** - * Conditionally add a new tag to the creature. - */ - | "ConditionalNewTag" - /** - * Conditionally add a tag to the creature. - */ - | "ConditionalAddTag" - /** - * Conditionally remove a tag from the creature. - */ - | "ConditionalRemoveTag" - /** - * Conditionally convert a tag to a new tag. - */ - | "ConditionalConvertTag" - /** - * An unknown tag. - */ - | "Unknown"; - +maxTrunkDiameter: number | null; /** - * A custom graphic extension. + * The number of years the trunk takes to grow one z-level upward. Default: 1 */ -export type CustomGraphicExtension = { - extensionType: GraphicTypeTag; - tilePageId?: string | null; - value1?: number | null; - value2?: number | null; -}; - +trunkPeriod: number | null; /** - * A struct representing a Dimensions object. + * The number of years the trunk takes to grow one tile wider. Default: 1 */ -export type Dimensions = { x: number; y: number }; - +trunkWidthPeriod: number | null; /** - * A struct representing an Entity object. + * What thin branches of the tree are named. */ -export type Entity = { - metadata?: Metadata | null; - identifier: string; - objectId: string; - tags: EntityTag[]; - creature?: string | null; - translation?: string | null; - exclusiveStartBiome?: string | null; - biomeSupport?: [string, number][] | null; - settlementBiome?: string[] | null; - startBiome?: string[] | null; - likesSites?: string[] | null; - toleratesSites?: string[] | null; - worldConstructions?: string[] | null; - maxPopNumber?: number | null; - maxSitePopNumber?: number | null; - maxStartingCivNumber?: number | null; - permittedBuildings?: string[] | null; - permittedJobs?: string[] | null; - permittedReactions?: string[] | null; - currency?: [string, number][] | null; - artFacetModifier?: [string, number][] | null; - artImageElementModifier?: [string, number][] | null; - itemImprovementModifier?: [string, number][] | null; - selectSymbols?: [string, string][] | null; - subselectSymbols?: [string, string][] | null; - cullSymbols?: [string, string][] | null; - friendlyColor?: Color | null; - religion?: string | null; - religionSpheres?: string[] | null; - sphereAlignments?: string[] | null; - positions?: Position[] | null; - landHolderTrigger?: string | null; - siteVariablePositions?: string[] | null; - variablePositions?: string[] | null; - ethics?: [string, string][] | null; - values?: [string, number][] | null; - variableValues?: [string, number, number][] | null; - activeSeason?: string | null; - banditry?: number | null; - progressTriggerPopulation?: number | null; - progressTriggerProduction?: number | null; - progressTriggerTrade?: number | null; - progressTriggerPopulationSiege?: number | null; - progressTriggerProductionSiege?: number | null; - progressTriggerTradeSiege?: number | null; - scholars?: string[] | null; - ammo?: string[] | null; - armors?: [string, number][] | null; - diggers?: string[] | null; - gloves?: [string, number][] | null; - helms?: [string, number][] | null; - instrument?: string[] | null; - pants?: [string, number][] | null; - shields?: string[] | null; - shoes?: [string, number][] | null; - siegeAmmo?: string[] | null; - tool?: string[] | null; - toys?: string[] | null; - trapComponents?: string[] | null; - weapons?: string[] | null; - gemShape?: string[] | null; - stoneShape?: string[] | null; - sourceHfid?: number | null; -}; - +branchName: Name | null; /** - * Tokens that can be found in an entity raw file. + * How dense the branches grow on this tree. */ -export type EntityTag = - /** - * Allows adventure mode for entities with sites. - */ - | "AllMainPopsControllable" - /** - * Allows fortress mode. If multiple entities have the `SITE_CONTROLLABLE` token, then at embark the specific civs can be chosen on - * the civ list screen. At least one civilization must have this token. - */ - | "SiteControllable" - /** - * Arguments: creature - * - * The type of creature that will inhabit the civilization. If multiple creature types are specified, each civilization will randomly - * choose one of the creatures. In entities with multiple possible creatures, you can manipulate the chance of one creature being - * chosen by adding multiple identical creature tags. For instance adding `[CREATURE:DWARF][CREATURE:DWARF][CREATURE:DWARF][CREATURE:ELF]` - * to the same entity will make the civs created about 75% dwarven, 25% elven. It should be noted that civilizations are in general - * weighted by this token. - * - * For example, if you have one entity with three `[CREATURE:DWARF]` entries and another separate entity with a single `[CREATURE:ELF]` entry, - * then you can expect to see three times as many of the former placed as the latter. - */ - | "Creature" - /** - * Arguments: number (integer) - * - * Found on generated angel entities. Appears to draw from creatures with this HFID, which associates the entity with a historical - * figure of the same ID corresponding to a deity. - */ - | "SourceHfid" - /** - * Arguments: biome, frequency - * - * Controls the expansion of the civilization's territory. The higher the number is relative to other `BIOME_SUPPORT` tokens in the entity, - * the faster it can spread through the biome. These numbers are evaluated relative to each other, i.e. if one biome is 1 and the other is 2, - * the spread will be the same as if one was 100 and the other was 200. Civs can spread out over biomes they cannot actually build in; - * - * For example, humans spread quickly over oceans but cannot actually build in them. - * - * e.g. `[BIOME_SUPPORT:ANY_GRASSLAND:4]` - */ - | "BiomeSupport" - /** - * Arguments: biome - * - * If the civ's territory crosses over this biome, it can build settlements here. - * - * e.g. `[SETTLEMENT_BIOME:ANY_GRASSLAND]` - */ - | "SettlementBiome" - /** - * Arguments: biome - * - * Combination of `EXCLUSIVE_START_BIOME` and `SETTLEMENT_BIOME`; allows the civ to start in and create settlements in the biome. - * - * e.g. `[START_BIOME:ANY_FOREST]` - */ - | "StartBiome" - /** - * Arguments: biome - * - * The birth of the civilization can occur in this biome, but cannot (necessarily) build in it. - * If the civ does not have `SETTLEMENT_BIOME` or `START_BIOME` for the biome in question, it will only construct a single settlement there. - * - * e.g. `[EXCLUSIVE_START_BIOME:MOUNTAIN]` - */ - | "ExclusiveStartBiome" - /** - * Arguments: site type - * - * Valid site types are `DARK_FORTRESS` (π), `CAVE` (•), `CAVE_DETAILED` (Ω), `TREE_CITY` (î), and `CITY` (#). - * Also recognizes `PLAYER_FORTRESS` (creates a civ of hillocks only), and `MONUMENT` (creates a civ without visible sites - * (except tombs and castles), but may cause worldgen crashes). `FORTRESS` is no longer a valid entry, castles are - * currently controlled by `BUILDS_OUTDOOR_FORTIFICATIONS`. Defaults to `CITY`. Selecting `CAVE` causes the classic kobold behavior - * of not showing up on the "neighbors" section of the site selection screen. Selecting `DARK_FORTRESS` also allows generation - * of certain other structures. It also gives the civ a special overlord. - * - * `CAVE_DETAILED` civilizations will create fortresses in mountainous regions and hillocks in non-mountainous regions. - * - * e.g. `[DEFAULT_SITE_TYPE:CAVE_DETAILED]` - */ - | "DefaultSiteType" - /** - * Arguments: site type - * - * Most residents will try to move to this site type, unless already at one. - * - * e.g. `[LIKES_SITE:CAVE_DETAILED]` - */ - | "LikesSite" - /** - * Arguments: site type - * - * Some residents will try to move to this site type, unless already at one. - * - * e.g. `[TOLERATES_SITE:CITY]` - */ - | "ToleratesSite" - /** - * Arguments: construction - * - * Controls which constructions the civ will build on the world map. Valid constructions are ROAD, TUNNEL, BRIDGE, and WALL. - * - * e.g. `[WORLD_CONSTRUCTION:BRIDGE] [WORLD_CONSTRUCTION:ROAD] [WORLD_CONSTRUCTION:TUNNEL] [WORLD_CONSTRUCTION:WALL]` - */ - | "WorldConstruction" - /** - * Arguments: number - * - * Max historical population per entity. Multiply this by max starting civ to get the total maximum historical population of the species. - * - * Defaults to 500. - * - * e.g. `[MAX_POP_NUMBER:500]` - */ - | "MaxPopNumber" - /** - * Arguments: number - * - * Max historical population per individual site. - * - * Defaults to 50. - * - * e.g. `[MAX_SITE_POP_NUMBER:200]` - */ - | "MaxSitePopNumber" - /** - * Arguments: number - * - * Max number of civ to spawn at world generation. Worldgen picks entities in some sequential order from the raws, - * and once it reaches the end of the list, it will begin again at the top. Setting this number lower than 100, - * like say, 7, will cause worldgen to skip over the civ for placement if there are already 7 civs of this type. - * - * Note that if all civs are set to lower numbers, and the number of starting civs is set higher than the - * maximum possible amount of civs in total, it will gracefully stop placing civs and get down to the history - * aspect of worldgen. - * - * Defaults to 3. - * - * e.g `[MAX_STARTING_CIV_NUMBER:3]` - */ - | "MaxStartingCivNumber" - /** - * Arguments: building name - * - * The named, custom building can be built by a civilization in Fortress Mode. - * - * e.g. `[PERMITTED_BUILDING:SOAP_MAKER]` - */ - | "PermittedBuilding" - /** - * Arguments: profession - * - * Allows this job type to be selected. This applies to worldgen creatures, in the embark screen, and in play. - * Certain professions also influence the availability of materials for trade. - * - * e.g. `[PERMITTED_JOB:MINER]` - */ - | "PermittedJob" - /** - * Arguments: reaction name - * - * Allows this reaction to be used by a civilization. It is used primarily in Fortress Mode, - * but also allows certain resources, such as steel, to be available to a race. When creating custom reactions, - * this token must be present or the player will not be able to use the reaction in Fortress Mode. - * - * e.g. `[PERMITTED_REACTION:TAN_A_HIDE]` - */ - | "PermittedReaction" - /** - * Causes the civ's currency to be numbered with the year it was minted. - */ - | "CurrencyByYear" - /** - * Arguments: inorganic material, value - * - * What kind of metals the civ uses for coin minting, as well as the value of the coin. - * Due to the Dwarven economy having been disabled since version 0.31, the value doesn't actually do anything. - * - * e.g `[CURRENCY:SILVER:5]` - */ - | "Currency" - /** - * Arguments: type, number - * - * `OWN_RACE`, `FANCIFUL`, `EVIL`, `GOOD` - * - * Number goes from `0` to `25_600` where `256` is the default. - * - * e.g. `[ART_FACET_MODIFIER:OWN_RACE:512]` - */ - | "ArtFacetModifier" - /** - * Arguments: item, number - * - * Allowed item: CREATURE, PLANT, TREE, SHAPE, ITEM - * - * Allowed number: 0-25600 - * - * Determines the chance of each image occurring in that entity's artwork, such as engravings and on artifacts, - * for default (non-historical) artwork. - * - * e.g. `[ART_IMAGE_ELEMENT_MODIFIER:TREE:512]` - */ - | "ArtImageElementModifier" - /** - * Arguments: item, number - * - * Allowed item: `ART_IMAGE`, `COVERED` or `GLAZED`, `RINGS_HANGING`, `BANDS`, `SPIKES`, `ITEMSPECIFIC`, `THREAD`, `CLOTH`, `SEWN_IMAGE` - * - * Allowed number: 0-25600 - * - * Determines the chance of the entity using that particular artwork method, such as "encircled with bands" or "menaces with spikes". - * - * `[ITEM_IMPROVEMENT_MODIFIER:SPIKES:0]` - * - * This also seems to change the amount that the entity will pay for items that are improved in these ways in their tokens. - */ - | "ItemImprovementModifier" - /** - * Arguments: language - * - * What language raw the entity uses. - * - * - If an entity lacks this tag, translations are drawn randomly from all translation files. Multiple translation tags will only - * result in the last one being used. Migrants will sometimes arrive with no name. - * - If `GEN_DIVINE` is entered, the entity will use a generated divine language, that is, the same language that is used for the names of angels. - * - * e.g. `[TRANSLATION:DWARF]` - */ - | "Translation" - /** - * Arguments: noun, symbol - * - * Allowed Values: - * `ALL`, `REMAINING`, `BATTLE`, `BRIDGE`, `CIV`, `CRAFT_GUILD`, `FESTIVAL`, `LIBRARY`, `MERCHANT_COMPANY`, `MILITARY_UNIT`, - * `OTHER`, `RELIGION`, `ROAD`, `SIEGE`, `SITE`, `TEMPLE`, `TUNNEL`, `VESSEL`, `WALL`, `WAR` - * - * Causes the entity to more often use these symbols in the particular SYM set. - * - * REMAINING will select all symbols that have not already been declared above it. - * - * e.g. `[SELECT_SYMBOL:ALL:PEACE]` - */ - | "SelectSymbol" - /** - * Arguments: noun, symbol - * - * Causes the symbol set to be preferred as adjectives by the civilization. Used in vanilla to put violent names in sieges and battles. - * - * e.g. `[SELECT_SYMBOL:SIEGE:NAME_SIEGE] [SUBSELECT_SYMBOL:SIEGE:VIOLENT]` - */ - | "SubselectSymbol" - /** - * Arguments: noun, symbol - * - * Causes the entity to not use the words in these SYM sets. - * - * e.g. `[CULL_SYMBOL:ALL:UGLY]` - */ - | "CullSymbol" - /** - * Arguments: color - * - * The color of this entity's civilization settlements in the world gen and embark screens, also used when announcing arrival of their caravan. - * - * Defaults to 7:0:1. - * - * e.g. `[FRIENDLY_COLOR:1:0:1]` - */ - | "FriendlyColor" - /** - * Arguments: type - * - * - `REGIONAL_FORCE`: The creatures will worship a single force associated with the terrain of their initial biome. - * - `PANTHEON`: The creatures will worship a group of gods, each aligned with their spheres and other appropriate ones as well. - * - * e.g. `[RELIGION:PANTHEON]` - */ - | "Religion" - /** - * Arguments: sphere - * - * Can be any available sphere - multiple entries are possible. Choosing a religious sphere will automatically make - * its opposing sphere not possible for the species to have: adding WATER, for example, means civilizations of this entity will - * never get FIRE as a religious sphere. Note that the DEATH sphere favours the appearance of necromancers - * (and therefore, towers) "in" the entity. - * - * e.g. `[RELIGION_SPHERE:FORTRESSES]` - */ - | "ReligionSphere" - /** - * Arguments: sphere, number - * - * This token forces an entity to favor or disfavor particular religious spheres, causing them to acquire those spheres more - * often when generating a pantheon. - * - * Default is 256, minimum is 0, maximum is 25600. - * - * e.g. `[SPHERE_ALIGNMENT:TREES:512]` - */ - | "SphereAlignment" - /** - * Defines a leader/noble position for a civilization. These replace previous tags such as `[MAYOR]` and `[CAN_HAVE_SITE_LEADER]` and so on. - * - * To define a position further, see Position token. - */ - | "Position" - /** - * Arguments: land holder ID, population, wealth exported, wealth created - * - * Defines when a particular land-holding noble (baron, count, duke in vanilla) will arrive at a fortress. - * - * As of version 0.44.11, however, this is obsolete due to the changes in how sites are elevated in status. - */ - | "LandHolderTrigger" - /** - * Arguments: position responsibility or 'ALL' - * - * Allows a site responsibility to be taken up by a dynamically generated position (lords, hearth-persons, etc.). - * Any defined positions holding a given responsibility will take precedence over generated positions for that responsibility. - * Also appears to cause site disputes. - */ - | "SiteVariablePositions" - /** - * Arguments: position responsibility or 'ALL' - * - * Allows a responsibility to be taken up by a dynamically generated position (such as Law-maker). - * Any defined positions holding a given responsibility will take precedence over generated positions for that responsibility. - */ - | "VariablePositions" - /** - * Arguments: behavior, action - * - * Sets the civ's view of ethics (certain behaviors), from capital punishment to completely acceptable. - * This also causes the civ to look upon opposing ethics with disfavor if their reaction to it is opposing, - * and when at extremes (one ACCEPTABLE, another civ UNTHINKABLE; for example) they will often go to war over it. - * - * e.g. `[ETHIC:EAT_SAPIENT_KILL:ACCEPTABLE]` - */ - | "Ethic" - /** - * Arguments: value, number - * - * Sets the civ's cultural values. Numbers range from -50 (complete anathema) to 0 (neutral) to 50 (highly valued). - * - * e.g. `[VALUE:CRAFTSMANSHIP:50]` - * - * Certain values must be set to 15 or more for civilizations to create structures and form entities during history gen: - * - * - 15+ KNOWLEDGE for libraries - * - 15+ COOPERATION and 15+ CRAFTSMANSHIP for craft guilds - * - * Guilds also need guild-valid professions (see `PERMITTED_JOB`) - */ - | "Value" - /** - * Arguments: value or `ALL`, min, max - * - * Makes values randomized rather than specified. - * - * This tag overrides the VALUE tag. Using `[VARIABLE_VALUE:ALL:x:y]` and then overwriting single values with further - * - * e.g. `[VARIABLE_VALUE:value:x:y]` tags works - */ - | "VariableValue" - /** - * Makes the civ's traders accept offered goods. - */ - | "WillAcceptTribute" - /** - * The civ will send out Wanderer adventurers in worldgen, which seems to increase Tracker skill. - * - * These types of adventurers will sometimes be seen leading a battle (instead of war leaders or generals) in remote locations during world-gen, in charge of the defenders. - * - * Mercenaries and monster hunters from the civ may visit player's fortress and petition for residency there to enlist in the military or hunt monsters in caverns, respectively. - */ - | "Wanderer" - /** - * The civ will send out `BeastHunter` adventurers in worldgen, which seems to increase Tracker skill. - * - * These types of adventurers will sometimes be seen leading a battle (instead of war leaders or generals) in remote locations during world-gen, in charge of the defenders. - * - * Mercenaries and monster hunters from the civ may visit player's fortress and petition for residency there to enlist in the military or hunt monsters in caverns, respectively. - */ - | "BeastHunter" - /** - * The civ will send out Scout adventurers in worldgen, which seems to increase Tracker skill. - * - * These types of adventurers will sometimes be seen leading a battle (instead of war leaders or generals) in remote locations during world-gen, in charge of the defenders. - * - * Mercenaries and monster hunters from the civ may visit player's fortress and petition for residency there to enlist in the military or hunt monsters in caverns, respectively. - */ - | "Scout" - /** - * The civ will send out Mercenary adventurers in worldgen, which seems to increase Tracker skill. - * - * These types of adventurers will sometimes be seen leading a battle (instead of war leaders or generals) in remote locations during world-gen, in charge of the defenders. - * - * Mercenaries and monster hunters from the civ may visit player's fortress and petition for residency there to enlist in the military or hunt monsters in caverns, respectively. - */ - | "Mercenary" - /** - * The civilization will mutilate bodies when they are the victors in history-gen warfare, such as hanging bodies from trees, putting them on spikes, and so forth. - * Adventurers killed in Adventurer mode will sometimes be impaled on spikes wherever they died, with or without this token, - * and regardless of whether they actually antagonized the townspeople. - */ - | "AbuseBodies" - /** - * Arguments: season - * - * The season when the civ is most active: when they will trade, interact with you via diplomats, and/or invade you. - * Civilizations can have multiple season entries. Note: If multiple caravans arrive at the same time, you are able to select - * which civ to trade with at the depot menu. `ACTIVE_SEASON` tags may be changed for a currently active fort. - * - * e.g. `[ACTIVE_SEASON:SUMMER]` - */ - | "ActiveSeason" - /** - * When invading, sneaks around and shoots at straggling members of your society. They will spawn on the edge of the map and will only be visible when - * one of their party are spotted; this can be quite dangerous to undefended trade depots. If the civilization also has the SIEGER token, - * they will eventually ramp it up to less subtle means of warfare. - */ - | "Ambusher" - /** - * Will not attack wildlife, and will not be attacked by them, even if you have them in your party. This can be somewhat disconcerting when attacked - * by bears in the forest, and your elven ally sits back and does nothing. Additionally, this token determines if the entity can settle in savage biomes. - */ - | "AtPeaceWithWildlife" - /** - * Sends thieves to steal babies. Without this tag (or `AMBUSHER`, or `ITEM_THIEF`), enemy civilizations will only siege (if capable), - * and will siege as early as they would otherwise babysnatch. This can happen as early as the first year of the fort! - * In addition, babysnatcher civilizations will snatch children during worldgen, allowing them to become part of the civ if they do not escape. - * - * Note: If the playable civ in fortress mode has this tag (e.g. you add BABYSNATCHER to the dwarf entity) then the roles will be reversed ==> - * elves and humans will siege and ambush and goblins will be friendly to you. - * However, animals traded away to one's own caravan will count as snatched, reported upon the animal leaving the map, - * and the animal will not count as having been exported. - */ - | "BabySnatcher" - /** - * Makes the civilization build castles from mead halls. Only functions when the type of site built is a hamlet/town. - * This, combined with the correct type of position associated with a site, is why adventurers can only lay claim to human sites. - */ - | "BuildsOutdoorFortifications" - /** - * Makes the civilization build tombs. - */ - | "BuildsOutdoorTombs" - /** - * Arguments: percentage - * - * Sets a percentage of the entity population to be used as bandits. - */ - | "Banditry" - /** - * Visiting diplomats are accompanied by a pair of soldiers. - */ - | "DiplomatBodyguards" - /** - * Found on generated divine "HF Guardian Entities". Cannot be used in user-defined raws. - */ - | "Generated" - /** - * Causes invaders to ignore visiting caravans and other neutral creatures - */ - | "InvadersIgnoreNeutrals" - /** - * Sends thieves to steal items. This will also occur in history generation, and thieves will have the "thief" profession. - * Items stolen in history gen will be scattered around that creature's home. - * - * Also causes that civ to be hostile to any entity without this token. Without this tag (or AMBUSHER, or BABYSNATCHER), enemy civs will only siege - * (if capable), and will siege as early as they would otherwise steal. - * - * Note: If the playable civ in Fortress Mode has this tag (e.g. you add `ITEM_THIEF` to the Dwarf entity) then the roles will be reversed ==> - * elves and humans will siege and ambush and kobolds will be friendly to you. However, ALL items traded away to one's own caravan will count as stolen, - * reported when the items leave the map, and the stolen items will not count as exported - */ - | "ItemThief" - /** - * Causes the entity to send out patrols that can ambush adventurers. Said patrols will be hostile to any adventurers they encounter, - * regardless of race or nationality. - */ - | "LocalBanditry" - /** - * Caravan merchants are accompanied by soldiers. - */ - | "MerchantBodyguards" - /** - * Merchants will engage in cross-civ trading and form companies. - * - * In previous versions, this resulted in the civ having a Guild Representative / Merchant Baron / Merchant Prince, - * but now this is controlled solely by positions - */ - | "MerchantNobility" - /** - * Arguments: level - * - * 0 to 5, civ will come to site once population at site has reached that level. If multiple progress triggers exist for a civ, - * it will come when any one of them is fulfilled instead of waiting for all of them to be reached. - * - * - A value of 0 disables the trigger. - * - 1 corresponds to 20 dwarves, - * - 2 to 50 dwarves, - * - 3 to 80, - * - 4 to 110, and - * - 5 to 140. - * - * Progress triggers may be changed, added, or deleted for a currently active fort. - * - * Note: hostile civs require that this be fulfilled as well as at least one other non-siege trigger before visiting for non-siege activities. - */ - | "ProgressTriggerPopulation" - /** - * Arguments: level - * - * 0 to 5, civ will come to site once created wealth has reached that level. If multiple progress triggers exist for a civ, - * it will come when any one of them is fulfilled instead of waiting for all of them to be reached. - * - * - A value of 0 disables the trigger. - * - 1 corresponds to 5000☼ created wealth, - * - 2 to 25000☼, - * - 3 to 100000☼, - * - 4 to 200000☼, and - * - 5 to 300000☼. - * - * Progress triggers may be changed, added, or deleted for a currently active fort. - */ - | "ProgressTriggerProduction" - /** - * Arguments: level - * - * 0 to 5, civ will come to site once exported goods has reached that level. If multiple progress triggers exist for a civ, - * it will come when any one of them is fulfilled instead of waiting for all of them to be reached. - * - * - A value of 0 disables the trigger. - * - 1 corresponds to 500☼ exported wealth, - * - 2 to 2500☼, - * - 3 to 10000☼, - * - 4 to 20000☼, and - * - 5 to 30000☼. - * - * Progress triggers may be changed, added, or deleted for a currently active fort. - */ - | "ProgressTriggerTrade" - /** - * Arguments: level - * - * 0 to 5, civ will begin to send sieges against the player civ when this level is reached if it is hostile. - * - * If multiple progress triggers exist for a civ, it will come when any one of them is fulfilled instead of - * waiting for all of them to be reached. A value of 0 disables the trigger - */ - | "ProgressTriggerPopulationSiege" - /** - * Arguments: level - * - * 0 to 5, civ will begin to send sieges against the player civ when this level is reached if it is hostile. - * - * If multiple progress triggers exist for a civ, it will come when any one of them is fulfilled instead of - * waiting for all of them to be reached. A value of 0 disables the trigger - */ - | "ProgressTriggerProductionSiege" - /** - * Arguments: level - * - * 0 to 5, civ will begin to send sieges against the player civ when this level is reached if it is hostile. - * - * If multiple progress triggers exist for a civ, it will come when any one of them is fulfilled instead of - * waiting for all of them to be reached. A value of 0 disables the trigger - */ - | "ProgressTriggerTradeSiege" - /** - * Will start campfires and wait around at the edge of your map for a month or two before rushing in to attack. - * This will occur when the progress triggers for sieging are reached. If the civ lacks smaller methods of conflict - * (`AMBUSHER`, `BABYSNATCHER`, `ITEM_THIEF`), they will instead send smaller-scale sieges when their triggers for - * "first contact" are reached. - */ - | "Sieger" - /** - * Guards certain special sites, such as a vault belonging to a demon allied with a deity. Used in generated divine entities. - */ - | "SiteGuardian" - /** - * This makes the severity of attacks depend on the extent of item/baby thievery rather than the passage of time. - * Designed to go with `ITEM_THIEF`, may or may not work with BABYSNATCHER. Prevents the civ from engaging in diplomacy - * or ending up at war. - */ - | "Skulking" - /** - * Visiting diplomats impose tree cutting quotas; without this, they will simply compliment your fortress and leave. - * Also causes the diplomat to make unannounced first contact at the very beginning of the first spring after your - * fortress becomes a land holder. - */ - | "TreeCapDiplomacy" - /** - * Defines if a civilization is a hidden subterranean entity, such as bat man civilizations. - * May spawn in any of the three caverns; cavern dweller raids due to agitation will pull from these. - * If you embark as this civ, you have access to pets and trees from all three layers, not only the first. - */ - | "LayerLinked" - /** - * Makes civilizations generate keyboard instruments - */ - | "GenerateKeyboardInstruments" - /** - * Makes civilizations generate percussion instruments - */ - | "GeneratePercussionInstruments" - /** - * Makes civilizations generate stringed instruments - */ - | "GenerateStringedInstruments" - /** - * Makes civilizations generate wind instruments - */ - | "GenerateWindInstruments" - /** - * Makes civilizations generate dance forms. - */ - | "GenerateDanceForms" - /** - * Makes civilizations generate musical forms. - */ - | "GenerateMusicalForms" - /** - * Makes civilizations generate poetic forms. - */ - | "GeneratePoeticForms" - /** - * Arguments: scholar type - * - * `ALL`, `ASTRONOMER`, `CHEMIST`, `DOCTOR`, `ENGINEER`, `GEOGRAPHER`, `HISTORIAN`, `MATHEMATICIAN`, `NATURALIST`, `PHILOSOPHER` - */ - | "Scholar" - /** - * Generates scholars based on the values generated with the `VARIABLE_VALUE` tag. - */ - | "SetScholarsOnValuesAndJobs" - /** - * Used for kobolds. - */ - | "NoArtifactClaims" - /** - * The civilization can breach the Underworld during world generation. - */ - | "MiningUnderworldDisasters" - /** - * Arguments: `item_token` - * - * Used before a ranged weapon type. - * - * e.g. `[AMMO:ITEM_AMMO_BOLTS]` - */ - | "Ammo" - /** - * Arguments: `item_token`, rarity - * - * Rarity is optional, and valid values are FORCED, COMMON, UNCOMMON, and RARE (anything else is treated as COMMON). - * FORCED items will be available 100% of the time, COMMON items 50%, UNCOMMON items 10%, and RARE items 1%. - * If certain armor types are lacking after performing one pass of randomized checks, the game will repeat random checks - * until an option is successfully chosen. - * - * e.g. `[ARMOR:ITEM_ARMOR_PLATEMAIL:COMMON]` - */ - | "Armor" - /** - * Arguments: `item_token` - * - * Causes the selected weapon to fall under the "digging tools" section of the embark screen. - * Also forces the weapon to be made out of metal, which can cause issues if a modded entity has access to picks without - * access to metal - for those cases, listing the pick under the `[WEAPON]` token works just as well. Note that this tag is - * neither necessary nor sufficient to allow use of that item as a mining tool -– - * for that, the item itself needs to be a weapon with `[SKILL:MINING]`. - * - * e.g. `[DIGGER:ITEM_WEAPON_PICK]` - */ - | "Digger" - /** - * Arguments: `item_token`, `rarity` - * - * Rarity is optional, and valid values are FORCED, COMMON, UNCOMMON, and RARE (anything else is treated as COMMON). - * Uses the same rarity values and methods as outlined in ARMOR. - * - * e.g. `[GLOVES:ITEM_GLOVES_GAUNTLETS:COMMON]` - */ - | "Gloves" - /** - * Arguments: `item_token`, `rarity` - * - * Rarity is optional, and valid values are FORCED, COMMON, UNCOMMON, and RARE (anything else is treated as COMMON). - * Uses the same rarity values and methods as outlined in ARMOR. - * - * e.g. `[HELM:ITEM_HELM_HELM:COMMON]` - */ - | "Helm" - /** - * Arguments: `item_token` - * - * No longer used as of Version 0.42.01 due to the ability to generate instruments in world generation. - * - * It is still usable if pre-defined instruments are modded in, and generated musical forms are capable - * of selecting pre-defined instruments to use. However, reactions for making instruments, instrument parts, - * and/or assembling such instruments need to be added as well, as this token no longer adds such instruments - * to the craftsdwarf workshop menu. - * - * e.g. `[INSTRUMENT:ITEM_INSTRUMENT_FLUTE]` - */ - | "Instrument" - /** - * Arguments: `item_token`, `rarity` - * - * Rarity is optional, and valid values are FORCED, COMMON, UNCOMMON, and RARE (anything else is treated as COMMON). - * Uses the same rarity values and methods as outlined in ARMOR. - * - * e.g. `[PANTS:ITEM_PANTS_LEGGINGS:COMMON]` - */ - | "Pants" - /** - * Arguments: `item_token` - * - * e.g. `[SHIELD:ITEM_SHIELD_BUCKLER]` - */ - | "Shield" - /** - * Arguments: `item_token`, `rarity` - * - * Rarity is optional, and valid values are FORCED, COMMON, UNCOMMON, and RARE (anything else is treated as COMMON). - * Uses the same rarity values and methods as outlined in ARMOR. - * - * e.g. `[SHOES:ITEM_SHOES_BOOTS:COMMON]` - */ - | "Shoes" - /** - * Arguments: `item_token` - * - * e.g. `[SIEGEAMMO:ITEM_SIEGEAMMO_BALLISTA]` - */ - | "SiegeAmmo" - /** - * Arguments: `item_token` - * - * e.g. `[TOOL:ITEM_TOOL_NEST_BOX]` - */ - | "Tool" - /** - * Arguments: `item_token` - * - * e.g. `[TOY:ITEM_TOY_PUZZLEBOX]` - */ - | "Toy" - /** - * Arguments: `item_token` - * - * e.g. `[TRAPCOMP:ITEM_TRAPCOMP_GIANTAXEBLADE]` - */ - | "TrapComponent" - /** - * Arguments: `item_token` - * - * While this does not accept a rarity value, something similar can be achieved by having multiple variations of a weapon type - * with small differences and specifying each of them. - * - * e.g. `[WEAPON:ITEM_WEAPON_AXE_BATTLE]` - */ - | "Weapon" - /** - * Allows use of products made from animals. All relevant creatures will be able to provide wool, silk, and extracts (including milk and venom) - * for trade, and non-sentient creatures (unless ethics state otherwise) will be able to provide eggs, caught fish, meat, leather, bone, - * shell, pearl, horn, and ivory. - */ - | "UseAnimalProducts" - /** - * Any creature in the civilization's list of usable's (from the surrounding 7x7 or so of squares and map features in those squares) - * which has `PET` or `PET_EXOTIC` will be available as a pet, pack animal (with `PACK_ANIMAL`), - * wagon puller (with `WAGON_PULLER`), - * mount (with `MOUNT` or `MOUNT_EXOTIC`), or - * siege minion (with `TRAINABLE_WAR ` and without `CAN_LEARN`). - * - * This notion of the initial usable creature list, which then gets pared down or otherwise considered, applies below as well. - * - * All common domestic and equipment creatures are also added to the initial list. - */ - | "UseAnyPetRace" - /** - * Without this, creatures with exclusively subterranean biomes are skipped. - * - * If they have it, cave creatures with PET will also be available as pets, pack animals (with `PACK_ANIMAL`), wagon pullers (with `WAGON_PULLER`), - * mounts (with `MOUNT` or `MOUNT_EXOTIC`), and siege minions (with `TRAINABLE_WAR` and without `CAN_LEARN`). - */ - | "UseCaveAnimals" - /** - * Without this, `EVIL` creatures are skipped. - * - * Otherwise, evil creatures with `SLOW_LEARNER` or without `CAN_LEARN` will be also available as pets (with `PET`), - * pack animals (with `PACK_ANIMAL`), wagon pullers (with `WAGON_PULLER`), mounts (with `MOUNT` or `MOUNT_EXOTIC`), - * and siege minions (with `TRAINABLE_WAR` or `SLOW_LEARNER`), even the normally untameable species. - */ - | "UseEvilAnimals" - /** - * Same as `USE_EVIL_ANIMALS` for all uses of plants. - */ - | "UseEvilPlants" - /** - * Same as `USE_EVIL_ANIMALS` for all uses of wood. - */ - | "UseEvilWood" - /** - * Without this `GOOD` creatures are skipped, otherwise, good creatures without `CAN_LEARN` will also be available as pets (with `PET`), - * pack animals (with `PACK_ANIMAL`), wagon pullers (with `WAGON_PULLER`), mounts (with `MOUNT` or `MOUNT_EXOTIC`), and siege minions - * (with `TRAINABLE_WAR`), even the normally untameable species. - */ - | "UseGoodAnimals" - /** - * Same as `USE_GOOD_ANIMALS` for all uses of plants. - */ - | "UseGoodPlants" - /** - * Same as `USE_GOOD_ANIMALS` for all uses of wood. - */ - | "UseGoodWood" - /** - * If the relevant professions are permitted, controls availability of lye (`LYE_MAKING`), charcoal (`BURN_WOOD`), and potash (`POTASH_MAKING`). - */ - | "UseMiscProcessedWoodProducts" - /** - * Makes the civilization use all locally available non-exotic pets. - */ - | "UseNoneExoticPetRace" - /** - * Gives the civilization access to creatures with `COMMON_DOMESTIC` and `MOUNT`. Additionally, all available - * (based on `USE_ANY_PET_RACE`, `USE_CAVE_ANIMALS`, `USE_GOOD_ANIMALS`, and `USE_EVIL_ANIMALS`) creature with `MOUNT` and `PET` - * will be allowed for use as mounts during combat. - */ - | "CommonDomesticMount" - /** - * Gives the civilization access to creatures with `COMMON_DOMESTIC` and `PACK_ANIMAL`. - * Additionally, all available (see above) creatures with `PACK_ANIMAL` and `PET` will be allowed for use during trade as pack animals. - */ - | "CommonDomesticPackAnimal" - /** - * Gives the civilization access to creatures with `COMMON_DOMESTIC` and `PET`. - * Additionally, all available (see above) creatures with PET will be allowed for use as pets. - */ - | "CommonDomesticPet" - /** - * Gives the civilization access to creatures with `COMMON_DOMESTIC` and `WAGON_PULLER`. Additionally, all - * available (see above) creatures with `WAGON_PULLER` and PET will be allowed for use during trade to pull wagons. - */ - | "CommonDomesticPullAnimal" - /** - * Allow use of river products in the goods available for trade. - */ - | "RiverProducts" - /** - * Allow use of ocean products (including amber and coral) in the goods available for trade. - * Without `OCEAN_PRODUCTS`, civilizations will not be able to trade ocean fish even if they are also - * available from other sources (e.g. sturgeons and stingrays). - */ - | "OceanProducts" - /** - * Allow use of underground plant products in the goods available for trade. - * Lack of suitable vegetation in the caverns will cause worldgen rejections. - */ - | "IndoorFarming" - /** - * Allow use of outdoor plant products in the goods available for trade. - * Lack of suitable vegetation in this civilization's starting area will cause worldgen rejections. - */ - | "OutdoorFarming" - /** - * Allow use of underground plant growths (quarry bush leaves, in unmodded games) in the goods available for trade. - */ - | "IndoorGardens" - /** - * Allow use of outdoor plant growths in the goods available for trade. - */ - | "OutdoorGardens" - /** - * Allows use of indoor tree growths in the goods available for trade. - * Not used in vanilla entities, as vanilla underground trees do not grow fruit. - * - * Needs `INDOOR_WOOD` to function. - * - * Will cause rejections, if growths are unavailable. - */ - | "IndoorOrchards" - /** - * Allows use of outdoor tree growths in the goods available for trade. - * - * Needs `OUTDOOR_WOOD` to function. - */ - | "OutdoorOrchards" - /** - * Civilization members will attempt to wear clothing. - */ - | "Clothing" - /** - * Will wear things made of spider silk and other subterranean materials. - */ - | "SubterraneanClothing" - /** - * Adds decorations to equipment based on the level of the generated unit. Also improves item quality. - */ - | "EquipmentImprovements" - /** - * Adds decorations to weapons generated for bowman and master bowman. - */ - | "ImprovedBows" - /** - * Allows metal materials to be used to make cages (inexpensive metals only) and crafts. - */ - | "MetalPref" - /** - * Allows the civilization to make use of nearby stone types. If the `FURNACE_OPERATOR` job is permitted, - * also allows ore-bearing stones to be smelted into metals. - */ - | "StonePref" - /** - * The civilization can make traditionally metallic weapons such as swords and spears from wood. - */ - | "WoodWeapons" - /** - * The civilization can make traditionally metallic armor such as mail shirts and helmets from wood. - */ - | "WoodArmor" - /** - * Enables creatures of this entity to bring gems in trade. - */ - | "GemPref" - /** - * Allow use of subterranean wood types, such as tower-cap and fungiwood logs. - */ - | "IndoorWood" - /** - * Allow use of outdoor wood types, such as mangrove and oak. - */ - | "OutdoorWood" - /** - * Arguments: shape - * - * Precious gems cut by this civilization's jewelers can be of this shape. - */ - | "GemShape" - /** - * Arguments: shape - * - * Ordinary non-gem stones cut by this civilization's jewelers can be of this shape. - */ - | "StoneShape" - /** - * Allows use of materials with `[DIVINE]` for clothing. Used for generated divine entities. - */ - | "DivineMatClothing" - /** - * Allows use of materials with `[DIVINE]` for crafts. Used for generated divine entities. - */ - | "DivineMatCrafts" - /** - * Allows use of metals with `[DIVINE]` for weapons. Used for generated divine entities. - */ - | "DivineMatWeapons" - /** - * Allows use of metals with `[DIVINE]` for armor. Used for generated divine entities. - */ - | "DivineMatArmor" - /** - * Start an animal definition. - */ - | "Animal" - /** - * Arguments: creature token - * - * Select specific creature. - */ - | "AnimalToken" - /** - * Arguments: creature caste token - * - * Select specific creature caste (requires `ANIMAL_TOKEN`). Sites with animal populations will still include all castes, - * but only the selected ones will be used for specific roles. - */ - | "AnimalCasteToken" - /** - * Arguments: creature class - * - * Select creature castes with this creature class (multiple uses allowed). - */ - | "AnimalClass" - /** - * Arguments: creature class - * - * Forbid creature castes with this creature class (multiple uses allowed). - */ - | "AnimalForbiddenClass" - /** - * Animal will be present even if it does not naturally occur in the entity's terrain. - * All creatures, including demons, night trolls and other generated ones will be used if no specific creature or class is selected. - */ - | "AnimalAlwaysPresent" - /** - * Override creature usage tokens, ALWAYS overrides NEVER if a caste is matched by more than one animal definition - */ - | "AnimalNeverMount" - /** - * Override creature usage tokens, ALWAYS overrides NEVER if a caste is matched by more than one animal definition - */ - | "AnimalAlwaysMount" - /** - * Override creature usage tokens, ALWAYS overrides NEVER if a caste is matched by more than one animal definition - */ - | "AnimalNeverWagonPuller" - /** - * Override creature usage tokens, ALWAYS overrides NEVER if a caste is matched by more than one animal definition - */ - | "AnimalAlwaysWagonPuller" - /** - * Override creature usage tokens, ALWAYS overrides NEVER if a caste is matched by more than one animal definition - */ - | "AnimalNeverSiege" - /** - * Override creature usage tokens, ALWAYS overrides NEVER if a caste is matched by more than one animal definition - */ - | "AnimalAlwaysSiege" - /** - * Override creature usage tokens, ALWAYS overrides NEVER if a caste is matched by more than one animal definition - */ - | "AnimalNeverPet" - /** - * Override creature usage tokens, ALWAYS overrides NEVER if a caste is matched by more than one animal definition - */ - | "AnimalAlwaysPet" - /** - * Override creature usage tokens, ALWAYS overrides NEVER if a caste is matched by more than one animal definition - */ - | "AnimalNeverPackAnimal" - /** - * Override creature usage tokens, ALWAYS overrides NEVER if a caste is matched by more than one animal definition - */ - | "AnimalAlwaysPackAnimal" - /** - * Arguments: tissue style unit ID - * - * Select a tissue layer which has the ID attached using `TISSUE_STYLE_UNIT` token in unit raws. - * This allows setting further cultural style parameters for the selected tissue layer. - */ - | "TissueStyle" - /** - * Arguments: min : max - * - * Presumably sets culturally preferred tissue length for selected tissue. Needs testing. - * Dwarves have their beards set to 100:NONE by default. - */ - | "TissueStyleMaintainLength" - /** - * Arguments: styling token - * - * Valid tokens are `NEATLY_COMBED`, `BRAIDED`, `DOUBLE_BRAIDS`, `PONY_TAILS`, `CLEAN_SHAVEN` and `STANDARD_HAIR/BEARD/MOUSTACHE/SIDEBURNS_SHAPINGS`. - * Presumably sets culturally preferred tissue shapings for selected tissue. Needs testing. - */ - | "TissueStylePreferredShaping" - /** - * An unknown token - */ - | "Unknown" - /** - * Found in raws as `SIEGE_SKILLED_MINERS` - * Presumed to mean that the entity can bring skilled miners to a siege - */ - | "SiegeSkilledMiners" - /** - * Prefers wood - */ - | "WoodPref" - /** - * An undead candidate - */ - | "UndeadCandidate" - /** - * Cut an existing entity - */ - | "CutEntity" - /** - * Select an entity to modify - */ - | "SelectEntity"; - +branchDensity: number | null; /** - * The class of environment that the stone appears in. + * The radius to which branches can reach. Appears to never reach further than seven tiles from the centre. + * Does not depend on the trunk branching amount or where trunks are. + * The values used in the game go from 0-3. Higher values than that can cause crashes. */ -export type EnvironmentClassTag = - /** - * Will appear in every stone. - */ - | "AllStone" - /** - * Will appear in all igneous layers - */ - | "IgneousAll" - /** - * Will appear in igneous extrusive layers - */ - | "IgneousExtrusive" - /** - * Will appear in igneous intrusive layers - */ - | "IgneousIntrusive" - /** - * Will appear in soil. - */ - | "Soil" - /** - * Will appear in sand. - */ - | "SoilSand" - /** - * Will appear in soil in the oceans. - */ - | "SoilOcean" - /** - * Will appear in sedimentary layers. - */ - | "Sedimentary" - /** - * Will appear in metamorphic layers. - */ - | "Metamorphic" - /** - * Will appear in alluvial layers. - */ - | "Alluvial" - /** - * Default value means parsing error. - */ - | "None"; - +branchRadius: number | null; /** - * A material fuel type that can be set in a material definition. + * What thick branches of the tree are named. */ -export type FuelTypeTag = - /** - * Charcoal or coal - */ - | "Charcoal" - /** - * Coal coke - */ - | "Coke" - /** - * No glass furnace fuel - */ - | "NoMaterialGloss" - /** - * None is an invalid option, so its a hint that this is not set. - */ - | "None"; - +heavyBranchesName: Name | null; /** - * Gaits are a way to describe how a creature moves. Defined in the raws with: - * - * "GAIT:type:name:full speed:build up time:turning max:start speed:energy use" - * - * * use `NO_BUILD_UP` if you jump immediately to full speed - * - * these optional flags go at the end: - * - * * `LAYERS_SLOW` - fat/muscle layers slow the movement (muscle-slowing counter-acted by strength bonus) - * * `STRENGTH` - strength attribute can speed/slow movement - * * `AGILITY` - agility attribute can speed/slow movement - * * `STEALTH_SLOWS:` - n is percentage slowed - * * it would be interesting to allow quirky attributes (like mental stats), but they aren't supported yet - * - * Examples: - * - * `[CV_NEW_TAG:GAIT:WALK:Sprint:!ARG4:10:3:!ARG2:50:LAYERS_SLOW:STRENGTH:AGILITY:STEALTH_SLOWS:50]` - * `[CV_NEW_TAG:GAIT:WALK:Run:!ARG3:5:3:!ARG2:10:LAYERS_SLOW:STRENGTH:AGILITY:STEALTH_SLOWS:20]` - * `[CV_NEW_TAG:GAIT:WALK:Jog:!ARG2:NO_BUILD_UP:5:LAYERS_SLOW:STRENGTH:AGILITY:STEALTH_SLOWS:10]` - * `[CV_NEW_TAG:GAIT:WALK:Walk:!ARG1:NO_BUILD_UP:0]` - * `[CV_NEW_TAG:GAIT:WALK:Stroll:!ARG5:NO_BUILD_UP:0]` - * `[CV_NEW_TAG:GAIT:WALK:Creep:!ARG6:NO_BUILD_UP:0]` + * Similar to `BRANCH_DENSITY` for thick branches. Default: 0 */ -export type Gait = { - /** - * The type of gait - */ - gaitType: GaitTypeTag; - /** - * The name of the gait - */ - name: string; - /** - * The maximum speed achievable by a creature using this gait. - */ - maxSpeed: number; - /** - * The energy use of the gait - */ - energyUse: number; - /** - * The gait modifiers - * - * These are optional, and may be empty. - */ - modifiers: GaitModifierTag[]; -}; - +heavyBranchDensity: number | null; /** - * An enum representing a gait modifier. + * Similar as `BRANCH_DENSITY` for thick branches. Values outside 0-3 can cause crashes. Default: 0 */ -export type GaitModifierTag = - /** - * Fat/muscle layers slow the movement (muscle-slowing counter-acted by strength bonus) - * Makes `THICKENS_ON_ENERGY_STORAGE` and `THICKENS_ON_STRENGTH` tissue layers slow movement depending on how thick they are. - * Adding the `STRENGTH` gait flag counteracts the impact of the latter layer. - */ - | "LayersSlow" - /** - * Speeds/slows movement depending on the creature's Strength stat. - */ - | "Strength" - /** - * Speeds/slows movement depending on the creature's Agility stat. - */ - | "Agility" - /** - * Stealth slows movement by the specified percentage when the creature is sneaking. - */ - | { - StealthSlows: { - /** - * The percentage slowed - */ - percentage: number; - }; - } - /** - * No build up time - */ - | "NoBuildUp" - /** - * Build up time. Only used if the gait has a build up time. - */ - | { - BuildUp: { - /** - * The build up time indicates how long it will take for a creature using this gait to go from `` to ``. - * For example, a value of 10 means that it should be able to reach the maximum speed by moving 10 tiles in a straight line over even terrain. - */ - time: number; - /** - * The turning max indicates the maximum speed permissible when the creature suddenly changes its direction of motion. - * The creature's speed will be reduced to `` if traveling at a higher speed than this before turning. - */ - turning_max: number; - /** - * The creature's speed when it starts moving using this gait - */ - start_speed: number; - }; - }; - +heavyBranchRadius: number | null; /** - * An enum representing a gait type. + * How much the trunk branches out. 0 makes the trunk straight (default) */ -export type GaitTypeTag = - /** - * Travel on foot/the ground - * Used for moving normally over ground tiles. - */ - | "Walk" - /** - * Travel on foot/the ground - * Used for moving over ground tiles whilst prone. - */ - | "Crawl" - /** - * Climbing on walls, etc. - * Used for moving whilst climbing. - */ - | "Climb" - /** - * Swimming in water/liquid - * Used for moving through tiles containing water or magma at a depth of at least 4/7. - */ - | "Swim" - /** - * Flying through the air - * Used for moving through open space. - */ - | "Fly" - /** - * Other gait type which is unexpected, but we can still handle it - */ - | { Other: string } - /** - * Unknown gait type (unset) - */ - | "Unknown"; - +trunkBranching: number | null; /** - * A struct representing a Graphic object. + * What the roots of the tree are named. */ -export type Graphic = { - metadata?: Metadata | null; - identifier: string; - objectId: string; - casteIdentifier?: string | null; - kind: GraphicTypeTag; - sprites?: SpriteGraphic[] | null; - layers?: [string, SpriteLayer[]][] | null; - growths?: [string, SpriteGraphic[]][] | null; - customExtensions?: CustomGraphicExtension[] | null; - tags?: string[] | null; -}; - +rootName: Name | null; /** - * The graphic type of the tile + * Density of the root growth. Defaults to 0. */ -export type GraphicTypeTag = - /** - * The tile is a creature - */ - | "creature" - /** - * The tile is a creature caste - */ - | "creatureCaste" - /** - * The tile is a statue of a creature - */ - | "statueCreature" - /** - * The tile is a statue of a creature caste - */ - | "statueCreatureCaste" - /** - * The tile is a statue of a creature caste with a giant size - */ - | "statuesSurfaceGiant" - /** - * A tile - */ - | "tile" - /** - * An empty tile - */ - | "empty" - /** - * A plant - */ - | "plant" - /** - * An unknown type - */ - | "unknown" - /** - * A template - */ - | "template" - /** - * The tile is soil background - */ - | "soilBackground" - /** - * The tile is grass-1 - */ - | "grass1" - /** - * The tile is grass-2 - */ - | "grass2" - /** - * The tile is grass-3 - */ - | "grass3" - /** - * The tile is grass-4 - */ - | "grass4" - /** - * The tile is custom edging - */ - | "customEdging" - /** - * The tile is custom ramp - */ - | "customRamp" - /** - * The tile is custom corner (W) - */ - | "customEdgeW" - /** - * The tile is custom corner (E) - */ - | "customEdgeE" - /** - * The tile is custom corner (N) - */ - | "customEdgeN" - /** - * The tile is custom corner (S) - */ - | "customEdgeS" - /** - * The tile is custom corner (NW) - */ - | "customEdgeNW" - /** - * The tile is custom corner (NE) - */ - | "customEdgeNE" - /** - * The tile is custom corner (SW) - */ - | "customEdgeSW" - /** - * The tile is custom corner (SE) - */ - | "customEdgeSE" - /** - * The tile is a custom workshop - */ - | "customWorkshop" - /** - * The tile is a list icon - */ - | "listIcon" - /** - * The tile is an add tool - */ - | "addTool" - /** - * The tile is ammo - */ - | "ammo" - /** - * The tile is ammo straight default - */ - | "ammoStraightDefault" - /** - * The tile is ammo straight wood - */ - | "ammoStraightWood" - /** - * The tile is ammo diagonal default - */ - | "ammoDiagonalDefault" - /** - * The tile is ammo diagonal wood - */ - | "ammoDiagonalWood" - /** - * The tile is an armor - */ - | "armor" - /** - * The tile is food - */ - | "food" - /** - * The graphic is of gloves - */ - | "gloves" - /** - * The graphic is of a helm - */ - | "helm" - /** - * The graphic is of pants - */ - | "pants" - /** - * The graphic is of a rough gem - */ - | "roughGem" - /** - * The graphic is of a large gem - */ - | "shapeLargeGem" - /** - * The graphic is of a small gem - */ - | "shapeSmallGem" - /** - * The graphic is of a shield - */ - | "shield" - /** - * The graphic is of a wooden shield - */ - | "shieldWooden" - /** - * The graphic is of shoes - */ - | "shoes" - /** - * The graphic is of metal shoes - */ - | "shoesMetal" - /** - * The graphic is of siege ammo - */ - | "siegeAmmo" - /** - * The graphic is of siege ammo straight default - */ - | "siegeAmmoStraightDefault" - /** - * The graphic is of siege ammo straight wood - */ - | "siegeAmmoStraightWood" - /** - * The graphic is of siege ammo diagonal default - */ - | "siegeAmmoDiagonalDefault" - /** - * The graphic is of siege ammo diagonal wood - */ - | "siegeAmmoDiagonalWood" - /** - * The graphic is of a tool - */ - | "tool" - /** - * The graphic is of a tool wood - */ - | "toolWood" - /** - * The graphic is of a tool stone - */ - | "toolStone" - /** - * The graphic is of a tool metal - */ - | "toolMetal" - /** - * The graphic is of a beehive - */ - | "toolHiveBuilding" - /** - * The graphic is of a glass tool - */ - | "toolGlass" - /** - * The graphic is of a tool shape - */ - | "toolShape" - /** - * The graphic is a glass tool variant - */ - | "toolGlassVariant" - /** - * The graphic is a metal tool variant - */ - | "toolMetalVariant" - /** - * The graphic is a stone tool variant - */ - | "toolStoneVariant" - /** - * The graphic is a wood tool variant - */ - | "toolWoodVariant" - /** - * The graphic is a mud tool - */ - | "toolMud" - /** - * The graphic is a water tool - */ - | "toolWater" - /** - * The graphic is a vomit tool - */ - | "toolVomit" - /** - * The graphic is a blood tool - */ - | "toolBlood" - /** - * The graphic is a plant tool - */ - | "toolDamage" - /** - * The graphic is a tool with binds on it - */ - | "toolBands" - /** - * The graphic is a tool with engravings - */ - | "toolEngraving" - /** - * The graphic is a tool with studs - */ - | "toolStuds" - /** - * The graphic is a tool with rings - */ - | "toolRings" - /** - * The graphic is a tool with spikes - */ - | "toolSpikes" - /** - * The graphic is a toy - */ - | "toy" - /** - * The graphic is a trap component - */ - | "trapComponent" - /** - * The graphic is a weapon trap - */ - | "trapComponentWeaponTrap" - /** - * The graphic is a weapon trap upright 1-T - */ - | "trapComponentUpright1T" - /** - * The graphic is a weapon trap upright 2-T - */ - | "trapComponentUpright2T" - /** - * The graphic is a weapon trap upright 3-T - */ - | "trapComponentUpright3T" - /** - * The graphic is a weapon trap upright 4-T - */ - | "trapComponentUpright4T" - /** - * The graphic is a weapon trap upright 5-T - */ - | "trapComponentUpright5T" - /** - * The graphic is a weapon trap upright 6-T - */ - | "trapComponentUpright6T" - /** - * The graphic is a weapon trap upright 7-T - */ - | "trapComponentUpright7T" - /** - * The graphic is a weapon trap upright 8-T - */ - | "trapComponentUpright8T" - /** - * The graphic is a weapon trap upright 9-T - */ - | "trapComponentUpright9T" - /** - * The graphic is a weapon trap upright 10-T - */ - | "trapComponentUpright10T" - /** - * The graphic is a weapon trap upright 1-B - */ - | "trapComponentUpright1B" - /** - * The graphic is a weapon trap upright 2-B - */ - | "trapComponentUpright2B" - /** - * The graphic is a weapon trap upright 3-B - */ - | "trapComponentUpright3B" - /** - * The graphic is a weapon trap upright 4-B - */ - | "trapComponentUpright4B" - /** - * The graphic is a weapon trap upright 5-B - */ - | "trapComponentUpright5B" - /** - * The graphic is a weapon trap upright 6-B - */ - | "trapComponentUpright6B" - /** - * The graphic is a weapon trap upright 7-B - */ - | "trapComponentUpright7B" - /** - * The graphic is a weapon trap upright 8-B - */ - | "trapComponentUpright8B" - /** - * The graphic is a weapon trap upright 9-B - */ - | "trapComponentUpright9B" - /** - * The graphic is a weapon trap upright 10-B - */ - | "trapComponentUpright10B" - /** - * The graphic is a weapon - */ - | "weapon" - /** - * The graphic is a weapon default - */ - | "weaponDefault" - /** - * The graphic is a weapon made of wood - */ - | "weaponWood" - /** - * The graphic is a weapon made of grown wood - */ - | "weaponWoodGrown" - /** - * The graphic is a weapon made of material - */ - | "weaponMaterial" - /** - * The graphic is of a weapon used in traps - */ - | "weaponTrap" - /** - * The graphic is of a weapon upright 1-T - */ - | "weaponUpright1T" - /** - * The graphic is of a weapon upright 2-T - */ - | "weaponUpright2T" - /** - * The graphic is of a weapon upright 3-T - */ - | "weaponUpright3T" - /** - * The graphic is of a weapon upright 4-T - */ - | "weaponUpright4T" - /** - * The graphic is of a weapon upright 5-T - */ - | "weaponUpright5T" - /** - * The graphic is of a weapon upright 6-T - */ - | "weaponUpright6T" - /** - * The graphic is of a weapon upright 7-T - */ - | "weaponUpright7T" - /** - * The graphic is of a weapon upright 8-T - */ - | "weaponUpright8T" - /** - * The graphic is of a weapon upright 9-T - */ - | "weaponUpright9T" - /** - * The graphic is of a weapon upright 10-T - */ - | "weaponUpright10T" - /** - * The graphic is of a weapon upright 1-B - */ - | "weaponUpright1B" - /** - * The graphic is of a weapon upright 2-B - */ - | "weaponUpright2B" - /** - * The graphic is of a weapon upright 3-B - */ - | "weaponUpright3B" - /** - * The graphic is of a weapon upright 4-B - */ - | "weaponUpright4B" - /** - * The graphic is of a weapon upright 5-B - */ - | "weaponUpright5B" - /** - * The graphic is of a weapon upright 6-B - */ - | "weaponUpright6B" - /** - * The graphic is of a weapon upright 7-B - */ - | "weaponUpright7B" - /** - * The graphic is of a weapon upright 8-B - */ - | "weaponUpright8B" - /** - * The graphic is of a weapon upright 9-B - */ - | "weaponUpright9B" - /** - * The graphic is of a weapon upright 10-B - */ - | "weaponUpright10B"; - +rootDensity: number | null; /** - * The growth tag of the tile + * How wide the roots reach out. Defaults to 0. */ -export type GrowthTag = - /** - * The tile is a fruit - */ - | "fruit" - /** - * The tile is growth-1 - */ - | "growth1" - /** - * The tile is growth-2 - */ - | "growth2" - /** - * The tile is growth-3 - */ - | "growth3" - /** - * The tile is growth-4 - */ - | "growth4" - /** - * The tile is "as-is" - */ - | "asIs"; - +rootRadius: number | null; /** - * The 'HABIT_NUM' value which can be a number or "TEST_ALL" + * What the twigs of the tree are named. */ -export type HabitCount = "TestAll" | { Specific: number }; - +twigsName: Name | null; /** - * The type of inclusion that the stone has. + * Where twigs appear, defaults to `[SideBranches, AboveBranches]` */ -export type InclusionTypeTag = - /** - * Large ovoids that occupy their entire 48x48 embark tile. Microcline is an example. When mined, stone has a 25% yield (as with layer stones). - */ - | "Cluster" - /** - * Blobs of 3-9 tiles. Will always be successfully mined. Red pyropes are an example. When mined, stone has a 100% yield. - */ - | "ClusterSmall" - /** - * Single tiles. Will always be successfully mined. Clear diamonds are an example. When mined, stone has a 100% yield. - */ - | "ClusterOne" - /** - * Large streaks of stone. Native gold is an example. When mined, stone has a 33% yield instead of the usual 25%. - */ - | "Vein" - /** - * Default value means parsing error. - */ - | "None"; - +twigsPlacement: TwigPlacementTag[] | null; /** - * Represents the `info.txt` file for a raw module + * What this mushroom-cap is called. Only makes sense with `TREE_HAS_MUSHROOM_CAP`. */ -export type InfoFile = { - identifier: string; - objectId: string; - location: RawModuleLocation; - parentDirectory: string; - numericVersion: number; - displayedVersion: string; - earliestCompatibleNumericVersion: number; - earliestCompatibleDisplayedVersion: string; - author: string; - name: string; - description: string; - requiresIds?: string[] | null; - conflictsWithIds?: string[] | null; - requiresIdsBefore?: string[] | null; - requiresIdsAfter?: string[] | null; - steamData?: SteamData | null; -}; - +capName: Name | null; /** - * The raw representation of an inorganic object. + * Similar to the other PERIOD tags, influences the rate of the mushroom cap growth. Only makes sense with `TREE_HAS_MUSHROOM_CAP`. Default: 1 */ -export type Inorganic = { - identifier: string; - metadata?: Metadata | null; - objectId: string; - material: Material; - metalOreChance?: [string, number][] | null; - threadMetalChance?: [string, number][] | null; - environmentClass?: EnvironmentClassTag | null; - environmentInclusionType?: InclusionTypeTag | null; - environmentInclusionFrequency?: number | null; - environmentClassSpecific?: string[] | null; - tags?: InorganicTag[] | null; -}; - +capPeriod: number | null; /** - * Tags that can be used in inorganic raws. + * The radius of a mushroom cap. Only makes sense with `TREE_HAS_MUSHROOM_CAP`. Default: 0 */ -export type InorganicTag = - /** - * Used on metals, causes the metal to be made into wafers instead of bars. - */ - | "Wafers" - /** - * Causes the stone to form hollow tubes leading to the Underworld. Used for raw adamantine. When mined, stone has a 100% yield. - * If no material with this token exists, hollow veins will instead be made of the first available inorganic, usually iron. Implies \[SPECIAL\]. - */ - | "DeepSpecial" - /** - * Allows the ore to be smelted into metal in the smelter. Each token with a non-zero chance causes the game to roll d100 four times, - * each time creating one bar of the type requested on success. - */ - | "MetalOre" - /** - * Allows strands to be extracted from the metal at a craftsdwarf's workshop. - */ - | "ThreadMetal" - /** - * Causes the stone to line the landscape of the Underworld. Used for slade. When mined (if it's mineable), stone has a 100% yield. If no material with this token exists, - * other materials will be used in place of slade. Underworld spires will still be referred to as a "spire of slade" in the world's history. - */ - | "DeepSurface" - /** - * Allows the stone to support an aquifer. - */ - | "Aquifer" - /** - * Causes the material to form metamorphic layers. - */ - | "Metamorphic" - /** - * Causes the material to form sedimentary layers. - */ - | "Sedimentary" - /** - * Causes the material to form soil layers, allowing it to appear in (almost) any biome. Mining is faster and produces no stones. - */ - | "Soil" - /** - * Causes the material to form pelagic sediment layers beneath deep oceans. Mining is faster and produces no stones. - */ - | "SoilOcean" - /** - * Causes the material to form sand layers, allowing it to appear in sand deserts and shallow oceans. Mining is faster and produces no stones. - * Sand layers can also be used for making glass. Can be combined with \[SOIL\]. - */ - | "SoilSand" - /** - * Permits an already \[SEDIMENTARY\] stone layer to appear underneath shallow ocean regions. - */ - | "SedimentaryOceanShallow" - /** - * Permits an already \[SEDIMENTARY\] stone layer to appear underneath deep ocean regions. - */ - | "SedimentaryOceanDeep" - /** - * Causes the material to form igneous intrusive layers. - */ - | "IgneousExtrusive" - /** - * Causes the material to form igneous extrusive layers. - */ - | "IgneousIntrusive" - /** - * Specifies what types of layers will contain this mineral. Multiple instances of the same token segment will cause the rock type to occur more frequently, - * but won't increase its abundance in the specified environment. See below. - */ - | "Environment" - /** - * Specifies which specific minerals will contain this mineral. See below. - */ - | "EnvironmentSpecific" - /** - * Specifies that the stone is created when combining water and magma, also causing it to line the edges of magma pools and volcanoes. - * If multiple minerals are marked as lava stones, a different one will be used in each biome or geological region. - */ - | "Lava" - /** - * Prevents the material from showing up in certain places. AI-controlled entities won't use the material to make items and don't bring it in caravans, - * though the player can use it as normal. Also, inorganic generated creatures (forgotten beasts, titans, demons) will never be composed of this material. - * Explicitly set by all evil weather materials and implied by `[DEEP_SURFACE]` and `[DEEP_SPECIAL]`. - */ - | "Special" - /** - * Indicates that this is a generated material. Cannot be specified in user-defined raws. - */ - | "Generated" - /** - * Found on random-generated metals and cloth. Marks this material as usable by Deity-created generated entities. - */ - | "Divine" - /** - * Found on divine materials. Presumably links the material to a god of the same sphere. - */ - | "Sphere" - /** - * Default value means parsing error. - */ - | "Unknown"; - +capRadius: number | null; /** - * Helper struct for managing locations related to the game directory and user directory. + * The tile used for trees of this type on the world map. Defaults to 24 (↑). */ -export type LocationHelper = { - df_directory: string | null; - user_data_directory: string | null; -}; - +treeTile: string | null; /** - * A struct representing a material + * The tile used for (un)dead trees and deciduous trees (generally in winter) of this type. Defaults to 198 (╞). */ -export type Material = { - /** - * The type of the material is also the trigger to start tracking a material - */ - materialType?: MaterialTypeTag | null; - /** - * The material might have a name, but its more likely that there is only an identifier to - * refer to another creature/plant/reaction, which are listed elsewhere. - * If there is no name provided, then it is a special hardcoded case, e.g. magma or green glass. - */ - name?: string | null; - /** - * For the coal tag, it specifies the type of fuel that can be used. It will never be None. - */ - fuelType?: FuelTypeTag | null; - /** - * Linked creature identifier (and then `material_name` might be "skin", like for "`CREATURE_MAT:DWARF:SKIN`") - */ - creatureIdentifier?: string | null; - /** - * Linked plant identifier (and then `material_name` might be "leaf", like for "`PLANT_MAT:BUSH_QUARRY:LEAF`") - */ - plantIdentifier?: string | null; - /** - * If a material is defined within a creature itself, it will use `LOCAL_CREATURE_MAT` tag, which implies - * that the material is only used by that creature. This is also true for plants and `LOCAL_PLANT_MAT`. - */ - isLocalMaterial?: boolean | null; - /** - * Within a reaction, there can be special material definitions. Todo: Figure this out. - */ - reagentIdentifier?: string | null; - reactionProductIdentifier?: string | null; - /** - * If material is defined from a template, we need a way to refer to that - */ - templateIdentifier?: string | null; - /** - * Usage tags - */ - usage?: MaterialUsageTag[] | null; - value?: number | null; - color?: Color | null; - stateNames?: StateName | null; - stateAdjectives?: StateName | null; - stateColors?: StateName | null; - temperatures?: Temperatures | null; - /** - * Catch-all for remaining tags we identify but don't do anything with... yet. - */ - properties?: string[] | null; - syndromes?: Syndrome[] | null; - mechanicalProperties?: MaterialMechanics | null; - liquidDensity?: number | null; - molarMass?: number | null; - buildColor?: Color | null; - displayColor?: Color | null; - tile?: Tile | null; - itemSymbol?: string | null; -}; - +deadTreeTile: string | null; /** - * Represents the specific yield, fracture, and elasticity of a material for the various - * types of mechanical stress. + * The tile used for saplings of this tree. Defaults to 231 (τ). */ -export type MaterialMechanics = { - impact?: MechanicalProperties | null; - compressive?: MechanicalProperties | null; - tensile?: MechanicalProperties | null; - torsion?: MechanicalProperties | null; - shear?: MechanicalProperties | null; - bending?: MechanicalProperties | null; - maxEdge?: number | null; - solidDensity?: number | null; -}; - +saplingTile: string | null; /** - * A material property that can be set in a material definition. + * The tile used for dead saplings of this tree. Defaults to 231 (τ). */ -export type MaterialPropertyTag = - /** - * Imports the properties of the specified preexisting material template. - */ - | "UseMaterialTemplate" - /** - * Applies a prefix to all items made from the material. For `PLANT` and `CREATURE` materials, this defaults to the plant/creature name. - * Not permitted in material template definitions. - */ - | "Prefix" - /** - * Overrides the name of `BOULDER` items (i.e. mined-out stones) made of the material (used for native copper/silver/gold/platinum - * to make them be called "nuggets" instead of "boulders"). - */ - | "StoneName" - /** - * Used to indicate that said material is a gemstone - when tiles are mined out, rough gems will be yielded instead of boulders. - * Plural can be "STP" to automatically append an "s" to the singular form, and `OVERWRITE_SOLID` will override the relevant `STATE_NAME` and `STATE_ADJ` values. - */ - | "IsGem" - /** - * Specifies what the material should be treated as when drinking water contaminated by it, for generating unhappy thoughts. - * Valid values are `BLOOD`, `SLIME`, `VOMIT`, `ICHOR`, `PUS`, `GOO`, `GRIME`, and `FILTH`. - */ - | "TempDietInfo" - /** - * Allows the material to be used as dye, and defines color of dyed items. - */ - | "PowderDye" - /** - * Specifies the tile that will be used to represent unmined tiles made of this material. Generally only used with stones. Defaults to 219 ('█'). - */ - | "Tile" - /** - * Specifies the tile that will be used to represent BOULDER items made of this material. Generally only used with stones. Defaults to 7 ('•'). - */ - | "ItemSymbol" - /** - * The on-screen color of the material. Uses a standard 3-digit color token. Equivalent to `TILE_COLOR:a:b:c`, - * `BUILD_COLOR:b:a:X` (X = 1 if 'a' equals 'b', 0 otherwise), and `BASIC_COLOR:a:c` - */ - | "DisplayColor" - /** - * The color of objects made of this material which use both the foreground and background color: doors, floodgates, hatch covers, bins, barrels, and cages. - * Defaults to `7:7:1` (white). - */ - | "BuildColor" - /** - * The color of unmined tiles containing this material (for stone and soil), as well as engravings in this material. Defaults to `7:7:1` (white). - */ - | "TileColor" - /** - * The color of objects made of this material which use only the foreground color, including workshops, floors and boulders, and smoothed walls. Defaults to `7:1` (white). - */ - | "BasicColor" - /** - * Determines the color of the material at the specified state. See below for a list of valid material states. Color comes from `descriptor_color_standard.txt`. - * The nearest color value is used to display contaminants and body parts made of this material in ASCII and to color items and constructions made from this - * material with graphics. - * Example:`STATE_COLOR:ALL_SOLID:GRAY` - */ - | "StateColor" - /** - * Determines the name of the material at the specified state, as displayed in-game. `STATE_NAME:ALL_SOLID:stone` - */ - | "StateName" - /** - * Like `STATE_NAME`, but used in different situations. Equipment made from the material uses the state adjective and not the state name. - */ - | "StateAdjective" - /** - * Sets both `STATE_NAME` and `STATE_ADJ` at the same time. - */ - | "StateNameAdjective" - /** - * The material's tendency to absorb liquids. Containers made of materials with nonzero absorption cannot hold liquids unless they have been glazed. - * Defaults to 0. - */ - | "Absorption" - /** - * Specifies how hard of an impact (in kilopascals) the material can withstand before it will start deforming permanently. - * Used for blunt-force combat. Defaults to `10_000`. - */ - | "ImpactYield" - /** - * Specifies how hard of an impact the material can withstand before it will fail entirely. Used for blunt-force combat. Defaults to `10_000`. - */ - | "ImpactFracture" - /** - * Specifies how much the material will have given (in parts-per-`100_000`) when the yield point is reached. Used for blunt-force combat. Defaults to 0. - * Apparently affects in combat whether the corresponding tissue is bruised (`value >= 50_000`), torn (value between `25_000` and `49_999`), or fractured (`value <= 24_999`) - */ - | "ImpactElasticity" - /** - * Specifies how hard the material can be compressed before it will start deforming permanently. Determines a tissue's resistance to pinching and response to strangulation. - * Defaults to `10_000`. - */ - | "CompressiveYield" - /** - * Specifies how hard the material can be compressed before it will fail entirely. Determines a tissue's resistance to pinching and response to strangulation. - * Defaults to `10_000`. - */ - | "CompressiveFracture" - /** - * Specifies how much the material will have given when it has been compressed to its yield point. Determines a tissue's resistance to pinching and - * response to strangulation. Defaults to 0. - */ - | "CompressiveElasticity" - /** - * Specifies how hard the material can be stretched before it will start deforming permanently. Determines a tissue's resistance to a latching and tearing bite. - * Defaults to `10_000`. - */ - | "TensileYield" - /** - * Specifies how hard the material can be stretched before it will fail entirely. Determines a tissue's resistance to a latching and tearing bite. Defaults to `10_000`. - */ - | "TensileFracture" - /** - * Specifies how much the material will have given when it is stretched to its yield point. Determines a tissue's resistance to a latching and tearing bite. - * Defaults to 0. - */ - | "TensileElasticity" - /** - * Specifies how hard the material can be twisted before it will start deforming permanently. Used for latching and shaking with a blunt attack - * (no default creature has such an attack, but they can be modded in). Defaults to `10_000`. - */ - | "TorsionYield" - /** - * Specifies how hard the material can be twisted before it will fail entirely. Used for latching and shaking with a blunt attack - * (no default creature has such an attack, but they can be modded in). Defaults to `10_000`. - */ - | "TorsionFracture" - /** - * Specifies how much the material will have given when it is twisted to its yield point. Used for latching and shaking with a blunt attack - * (no default creature has such an attack, but they can be modded in). Defaults to 0. - */ - | "TorsionElasticity" - /** - * Specifies how hard the material can be sheared before it will start deforming permanently. Used for cutting calculations. Defaults to `10_000`. - */ - | "ShearYield" - /** - * Specifies how hard the material can be sheared before it will fail entirely. Used for cutting calculations. Defaults to `10_000`. - */ - | "ShearFracture" - /** - * Specifies how much the material will have given when sheared to its yield point. Used for cutting calculations. Defaults to 0. - */ - | "ShearElasticity" - /** - * Specifies how hard the material can be bent before it will start deforming permanently. Determines a tissue's resistance to being mangled with a joint lock. - * Defaults to `10_000`. - */ - | "BendingYield" - /** - * Specifies how hard the material can be bent before it will fail entirely. Determines a tissue's resistance to being mangled with a joint lock. Defaults to `10_000`. - */ - | "BendingFracture" - /** - * Specifies how much the material will have given when bent to its yield point. Determines a tissue's resistance to being mangled with a joint lock. Defaults to 0. - */ - | "BendingElasticity" - /** - * How sharp the material is. Used in cutting calculations. Applying a value of at least `10_000` to a stone will allow weapons to be made from that stone. Defaults to `10_000`. - */ - | "MaxEdge" - /** - * Value modifier for the material. Defaults to 1. This number can be made negative by placing a "-" in front, resulting in things that you are paid to buy and - * must pay to sell. - */ - | "MaterialValue" - /** - * Multiplies the value of the material. Not permitted in material template definitions. - */ - | "MultiplyValue" - /** - * Rate at which the material heats up or cools down (in joules/kilogram-kelvin). If set to `NONE`, the temperature will be fixed at its initial value. - * Defaults to `NONE`. - */ - | "SpecificHeat" - /** - * Temperature above which the material takes damage from heat. Defaults to `NONE`. - * If the material has an ignite point but no heatdam point, it will burn for a very long time (9 months and 16.8 days). - */ - | "HeatDamagePoint" - /** - * Temperature below which the material takes damage from cold. Defaults to `NONE`. - */ - | "ColdDamagePoint" - /** - * Temperature at which the material will catch fire. Defaults to `NONE`. - */ - | "IgnitionPoint" - /** - * Temperature at which the material melts. Defaults to `NONE`. - */ - | "MeltingPoint" - /** - * Temperature at which the material boils. Defaults to `NONE`. - */ - | "BoilingPoint" - /** - * Items composed of this material will initially have this temperature. - * Used in conjunction with `SPEC_HEAT:NONE` to make material's temperature fixed at the specified value. - * Defaults to `NONE`. - */ - | "MaterialFixedTemperature" - /** - * Changes a material's `HEATDAM_POINT`, but only if it was not set to `NONE`. Not permitted in material template definitions. - */ - | "IfExistsSetHeatDamagePoint" - /** - * Changes a material's `COLDDAM_POINT`, but only if it was not set to `NONE`. Not permitted in material template definitions. - */ - | "IfExistsSetColdDamagePoint" - /** - * Changes a material's `IGNITE_POINT`, but only if it was not set to `NONE`. Not permitted in material template definitions. - */ - | "IfExistsSetIgnitePoint" - /** - * Changes a material's `MELTING_POINT`, but only if it was not set to `NONE`. Not permitted in material template definitions. - */ - | "IfExistsSetMeltingPoint" - /** - * Changes a material's `BOILING_POINT`, but only if it was not set to `NONE`. Not permitted in material template definitions. - */ - | "IfExistsSetBoilingPoint" - /** - * Changes a material's `MAT_FIXED_TEMP`, but only if it was not set to `NONE`. Not permitted in material template definitions. - */ - | "IfExistsSetMatFixedTemp" - /** - * Specifies the density (in kilograms per cubic meter) of the material when in solid form. Also affects combat calculations; - * affects blunt-force damage and ability of weak-in-impact-yield blunt attacks to pierce armor. Defaults to `NONE`. - */ - | "SolidDensity" - /** - * Specifies the density of the material when in liquid form. Defaults to `NONE`. Also affects combat calculations; - * affects blunt force damage like `SOLID_DENSITY`, but only for attacks made by liquids (e.g. forgotten beasts made of water). - */ - | "LiquidDensity" - /** - * Specifies (in kg/mol) the molar mass of the material in gaseous form. Also affects combat calculations like the densities, - * but only for attacks made by gases (e.g. forgotten beasts made of steam). - */ - | "MolarMass" - /** - * Specifies the type of container used to store the material. Used in conjunction with the `EXTRACT_BARREL`, `EXTRACT_VIAL`, - * or `EXTRACT_STILL_VIAL` plant tokens. - * Defaults to `BARREL`. - */ - | "ExtractStorage" - /** - * Specifies the item type used for butchering results made of this material. Stock raws use `GLOB:NONE` for fat and `MEAT:NONE` for other meat materials. - */ - | "ButcherSpecial" - /** - * When a creature is butchered, meat yielded from organs made from this material will be named via this token. - */ - | "MeatName" - /** - * Specifies the name of blocks made from this material. - */ - | "BlockName" - /** - * The material forms "wafers" instead of "bars". - */ - | "Wafers" - /** - * Used with reaction raws to associate a reagent material with a product material. The first argument is used by `HAS_MATERIAL_REACTION_PRODUCT` and `GET_MATERIAL_FROM_REAGENT` in reaction raws. - * The remainder is a material reference, generally `LOCAL_CREATURE_MAT:SUBTYPE` or `LOCAL_PLANT_MAT:SUBTYPE` or `INORGANIC:STONETYPE`. - * `MATERIAL_REACTION_PRODUCT:TAN_MAT:LOCAL_CREATURE_MAT:LEATHER` - */ - | "MaterialReactionProduct" - /** - * Used with reaction raws to associate a reagent material with a complete item. The first argument is used by `HAS_ITEM_REACTION_PRODUCT` and `GET_ITEM_DATA_FROM_REAGENT` in reaction raws. - * The rest refers to the type of item, then its material. - * `ITEM_REACTION_PRODUCT:BAG_ITEM:PLANT_GROWTH:LEAVES:LOCAL_PLANT_MAT:LEAF` - */ - | "ItemReactionProduct" - /** - * "Used to classify all items made of the material, so that reactions can use them as generic reagents.In default raws, the following are used: - * `FAT`, `TALLOW`, `SOAP`, `PARCHMENT`, `PAPER_PLANT`, `PAPER_SLURRY`, `MILK`, `CHEESE`, `WAX`. - * `CAN_GLAZE` - items made from this material can be glazed. - * `FLUX` - can be used as flux in pig iron and steel making. - * `GYPSUM` - can be processed into gypsum plaster. - * `CALCIUM_CARBONATE` - can be used in production of quicklime." - */ - | "ReactionClass" - /** - * Makes `BOULDER` acceptable as a reagent in reactions that require `METAL_ORE:MATERIAL_NAME`, as well as smelting directly into metal bars. - * Places the material under Metal Ores in Stone stockpiles. The specified value determines the probability for this product (see Tetrahedrite or Galena for details). - */ - | "MetalOre" - /** - * Makes `BOULDER` items made of the material acceptable for strand extraction into threads; see also `STOCKPILE_THREAD_METAL`. - * Value presumably determines the probability of this product extracted. - */ - | "ThreadMetal" - /** - * Allows the material to be used to make casts. - */ - | "HardensWithWater" - /** - * Determines effectiveness of soap - if the amount of grime on a body part is more than 3-SOAP_LEVEL, it sets it to 3-SOAP_LEVEL; as such setting it above 3 is bad. - * Soap has `[SOAP_LEVEL:2]`. Defaults to 0. - */ - | "SoapLevel" - /** - * Begins defining a syndrome applied by the material. Multiple syndromes can be specified. See Syndrome token. - */ - | "Syndrome" - /** - * This is since .50 in the raws of several antler-wielding animals. It is used to show an antler as bodypart. - */ - | "Antler" - /** - * Hair material - */ - | "Hair" - /** - * Feather material - */ - | "Feather" - /** - * Scale material - */ - | "Scale" - /** - * Hoof material - */ - | "Hoof" - /** - * Chitin material - */ - | "Chitin" - /** - * Cartilage material - */ - | "Cartilage" - /** - * Nervous tissue - */ - | "NervousTissue" - /** - * Category of meat - */ - | "MeatCategory" - /** - * For default value, use unknown. - */ - | "Unknown"; - +deadSaplingTile: string | null; /** - * A material state that can be set in a material definition. + * The color of the tree on the map. Defaults to 2:0:0 (dark green). */ -export type MaterialStateTag = - /** - * Solid state of the material - */ - | "Solid" - /** - * Liquid state of the material - */ - | "Liquid" - /** - * Gas state of the material - */ - | "Gas" - /** - * `POWDER` or `SOLID_POWDER` - */ - | "Powder" - /** - * `PASTE` or `SOLID_PASTE` - */ - | "Paste" - /** - * `PRESSED` or `SOLID_PRESSED` - */ - | "Pressed" - /** - * Default value is invalid, so its a hint that this is not set. - */ - | "Unknown" - /** - * Denotes all possible material states - */ - | "All" - /** - * Denotes '`Solid`', '`Powder`', '`Paste`', and '`Pressed`' - */ - | "AllSolid"; - +treeColor: Color | null; /** - * A struct representing a material template + * The color of the tree on the map when (un)dead. Defaults to 0:0:1 (dark gray). */ -export type MaterialTemplate = { - identifier: string; - metadata?: Metadata | null; - objectId: string; - material: Material; -}; - +deadTreeColor: Color | null; /** - * A material template + * The color of saplings of this tree. Defaults to 2:0:0 (dark green). */ -export type MaterialTypeTag = - /** - * An inorganic material - */ - | "Inorganic" - /** - * A stone material - */ - | "Stone" - /** - * A metal material - */ - | "Metal" - /** - * A coal material - */ - | "Coal" - /** - * A creature material - */ - | "CreatureMaterial" - /** - * A creature material for the current creature token only - */ - | "LocalCreatureMaterial" - /** - * A plant material - */ - | "PlantMaterial" - /** - * A plant material for the current plant token only - */ - | "LocalPlantMaterial" - /** - * A material from a reaction - */ - | "GetMaterialFromReagent" - /** - * Amber - */ - | "Amber" - /** - * Coral - */ - | "Coral" - /** - * Green Glass - */ - | "GlassGreen" - /** - * Clear Glass - */ - | "GlassClear" - /** - * Crystal Glass - */ - | "GlassCrystal" - /** - * Water - */ - | "Water" - /** - * Potash - */ - | "Potash" - /** - * Ash - */ - | "Ash" - /** - * Pearl Ash - */ - | "PearlAsh" - /** - * Lye - */ - | "Lye" - /** - * Mud - */ - | "Mud" - /** - * Vomit - */ - | "Vomit" - /** - * Salt - */ - | "Salt" - /** - * Brown Filth - */ - | "FilthB" - /** - * Yellow Filth - */ - | "FilthY" - /** - * Unnknown Substance - */ - | "UnknownSubstance" - /** - * Grime - */ - | "Grime" - /** - * An unknown token - */ - | "Unknown"; - +saplingColor: Color | null; /** - * A material usage that can be set in a material definition. + * The color of dead saplings of this tree. Defaults to 0:0:1 (dark gray). */ -export type MaterialUsageTag = - /** - * Lets the game know that an animal was likely killed in the production of this item. - * Entities opposed to killing animals (Elves in vanilla) will refuse to accept these items in trade. - */ - | "ImpliesAnimalKill" - /** - * Classifies the material as plant-based alcohol, allowing its storage in food stockpiles under "Drink (Plant)". - */ - | "AlcoholPlant" - /** - * Classifies the material as animal-based alcohol, allowing its storage in food stockpiles under "Drink (Animal)". - */ - | "AlcoholCreature" - /** - * Classifies the material as generic alcohol. Implied by both `ALCOHOL_PLANT` and `ALCOHOL_CREATURE`. Exact behavior unknown, possibly vestigial. - */ - | "Alcohol" - /** - * Classifies the material as plant-based cheese, allowing its storage in food stockpiles under "Cheese (Plant)". - */ - | "CheesePlant" - /** - * Classifies the material as animal-based cheese, allowing its storage in food stockpiles under "Cheese (Animal)". - */ - | "CheeseCreature" - /** - * Classifies the material as generic cheese. Implied by both `CHEESE_PLANT` and `CHEESE_CREATURE`. Exact behavior unknown, possibly vestigial. - */ - | "Cheese" - /** - * Classifies the material as plant powder, allowing its storage in food stockpiles under "Milled Plant". - */ - | "PowderMiscPlant" - /** - * Classifies the material as creature powder, allowing its storage in food stockpiles under "Bone Meal". - * Unlike milled plants, such as sugar and flour, "Bone Meal" barrels or pots may not contain bags. - * Custom reactions using this product better use buckets or jugs instead. - */ - | "PowderMiscCreature" - /** - * Classifies the material as generic powder. Implied by both `POWDER_MISC_PLANT` and `POWDER_MISC_CREATURE`. - * Exact behavior unknown, possibly vestigial. - */ - | "PowderMisc" - /** - * Permits globs of the material in solid form to be stored in food stockpiles under "Fat" - without it, - * dwarves will come by and "clean" the items, destroying them (unless `DO_NOT_CLEAN_GLOB` is also included). - */ - | "StockpileGlobOrStockpileGlobSolid" - /** - * Classifies the material as milled paste, allowing its storage in food stockpiles under "Paste". - */ - | "StockpileGlobPaste" - /** - * Classifies the material as pressed goods, allowing its storage in food stockpiles under "Pressed Material". - */ - | "StockpileGlobPressed" - /** - * Classifies the material as a plant growth (e.g. fruits, leaves), allowing its storage in food stockpiles under Plant Growth/Fruit. - */ - | "StockpilePlantGrowth" - /** - * Classifies the material as a plant extract, allowing its storage in food stockpiles under "Extract (Plant)". - */ - | "LiquidMiscPlant" - /** - * Classifies the material as a creature extract, allowing its storage in food stockpiles under "Extract (Animal)". - */ - | "LiquidMiscCreature" - /** - * Classifies the material as a miscellaneous liquid, allowing its storage in food stockpiles under "Misc. Liquid" along with lye. - */ - | "LiquidMiscOther" - /** - * Classifies the material as a generic liquid. Implied by `LIQUID_MISC_PLANT`, `LIQUID_MISC_CREATURE`, and `LIQUID_MISC_OTHER`. Exact behavior unknown, possibly vestigial. - */ - | "LiquidMisc" - /** - * Classifies the material as a plant, allowing its storage in food stockpiles under "Plants". - */ - | "StructuralPlantMat" - /** - * Classifies the material as a plant seed, allowing its storage in food stockpiles under "Seeds". - */ - | "SeedMat" - /** - * Classifies the material as bone, allowing its use for bone carvers and restriction from stockpiles by material. - */ - | "Bone" - /** - * Classifies the material as wood, allowing its use for carpenters and storage in wood stockpiles. - * Entities opposed to killing plants (i.e. Elves) will refuse to accept these items in trade. - */ - | "Wood" - /** - * Classifies the material as plant fiber, allowing its use for clothiers and storage in cloth stockpiles under "Thread (Plant)" and "Cloth (Plant)". - */ - | "ThreadPlant" - /** - * Classifies the material as tooth, allowing its use for bone carvers and restriction from stockpiles by material. - */ - | "Tooth" - /** - * Classifies the material as horn, allowing its use for bone carvers and restriction from stockpiles by material. - */ - | "Horn" - /** - * Classifies the material as hair, allowing for its use for spinners and restriction from refuse stockpiles by material. - */ - | "Hair" - /** - * Classifies the material as pearl, allowing its use for bone carvers and restriction from stockpiles by material. - */ - | "Pearl" - /** - * Classifies the material as shell, allowing its use for bone carvers and restriction from stockpiles by material. - */ - | "Shell" - /** - * Classifies the material as leather, allowing its use for leatherworkers and storage in leather stockpiles. - */ - | "Leather" - /** - * Classifies the material as silk, allowing its use for clothiers and storage in cloth stockpiles under "Thread (Silk)" and "Cloth (Silk)". - */ - | "Silk" - /** - * Classifies the material as soap, allowing it to be used as a bath detergent and stored in bar/block stockpiles under "Bars: Other Materials". - */ - | "Soap" - /** - * Material generates miasma when it rots. - */ - | "GeneratesMiasma" - /** - * Classifies the material as edible meat. - */ - | "Meat" - /** - * Material will rot if not stockpiled appropriately. Currently only affects food and refuse, other items made of this material will not rot. - */ - | "Rots" - /** - * In most living creatures, it controls many bodily functions and movements by sending signals around the body. See: Nervous tissue - */ - | "NervousTissue" - /** - * Tells the game to classify contaminants of this material as being "blood" in Adventurer mode tile descriptions ("Here we have a Dwarf in a slurry of blood."). - */ - | "BloodMapDescriptor" - /** - * Tells the game to classify contaminants of this material as being "ichor". - */ - | "IchorMapDescriptor" - /** - * Tells the game to classify contaminants of this material as being "goo". - */ - | "GooMapDescriptor" - /** - * Tells the game to classify contaminants of this material as being "slime". - */ - | "SlimeMapDescriptor" - /** - * Tells the game to classify contaminants of this material as being "pus". - */ - | "PusMapDescriptor" - /** - * Tells the game to classify contaminants of this material as being "sweat". - */ - | "SweatMapDescriptor" - /** - * Tells the game to classify contaminants of this material as being "tears". - */ - | "TearsMapDescriptor" - /** - * Tells the game to classify contaminants of this material as being "spit". - */ - | "SpitMapDescriptor" - /** - * Contaminants composed of this material evaporate over time, slowly disappearing from the map. Used internally by water. - */ - | "Evaporates" - /** - * Used for materials which cause syndromes, causes it to enter the creature's blood instead of simply spattering on the surface. - */ - | "EntersBlood" - /** - * Can be eaten by vermin. - */ - | "EdibleVermin" - /** - * Can be eaten raw. - */ - | "EdibleRaw" - /** - * Can be cooked and then eaten. - */ - | "EdibleCooked" - /** - * Prevents globs made of this material from being cleaned up and destroyed. - */ - | "DoNotCleanGlob" - /** - * Prevents the material from showing up in Stone stockpile settings. - */ - | "NoStoneStockpile" - /** - * The material can be made into minecarts, wheelbarrows, and stepladders at the metalsmith's forge. - */ - | "ItemsMetal" - /** - * Equivalent to `ITEMS_HARD`. Given to bone. - */ - | "ItemsBarred" - /** - * Equivalent to `ITEMS_HARD`. Given to shell. - */ - | "ItemsScaled" - /** - * Equivalent to `ITEMS_SOFT`. Given to leather. - */ - | "ItemsLeather" - /** - * The material can be made into clothing, amulets, bracelets, earrings, backpacks, and quivers, contingent - * on which workshops accept the material. Given to plant fiber, silk and wool. - */ - | "ItemsSoft" - /** - * The material can be made into furniture, crafts, mechanisms, and blocks, contingent on which workshops accept the material. - * Random crafts made from this material include all seven items. Given to stone, wood, bone, shell, chitin, claws, teeth, - * horns, hooves and beeswax. Hair, pearls and eggshells also have the tag. - */ - | "ItemsHard" - /** - * Used to define that the material is a stone. Allows its usage in masonry and stonecrafting and storage in stone stockpiles, among other effects. - */ - | "IsStone" - /** - * Defines the material is a ceramic. - */ - | "IsCeramic" - /** - * Used for a stone that cannot be dug into. - */ - | "Undiggable" - /** - * Causes containers made of this material to be prefixed with "unglazed" if they have not yet been glazed. - */ - | "DisplayUnglazed" - /** - * Classifies the material as yarn, allowing its use for clothiers and its storage in cloth stockpiles under "Thread (Yarn)" and "Cloth (Yarn)". - */ - | "Yarn" - /** - * Classifies the material as metal thread, permitting thread and cloth to be stored in cloth stockpiles under "Thread (Metal)" and "Cloth (Metal)". - */ - | "StockpileThreadMetal" - /** - * Defines the material as being metal, allowing it to be used at forges. - */ - | "IsMetal" - /** - * Used internally by green glass, clear glass, and crystal glass. Appears to only affect the `GLASS_MATERIAL` reaction token. Does not cause the game - * to treat the material like glass, i.e being referred to as "raw" instead of "rough" in its raw form or being displayed in the "glass" trade/embark category. - */ - | "IsGlass" - /** - * Can be used in the production of crystal glass. - */ - | "CrystalGlassable" - /** - * Melee weapons can be made out of this material. - */ - | "ItemsWeapon" - /** - * Ranged weapons can be made out of this material. - */ - | "ItemsWeaponRanged" - /** - * Anvils can be made out of this material. - */ - | "ItemsAnvil" - /** - * Ammunition can be made out of this material. - */ - | "ItemsAmmo" - /** - * Picks can be made out of this material. - */ - | "ItemsDigger" - /** - * Armor can be made out of this material. - */ - | "ItemsArmor" - /** - * Used internally by amber and coral. Functionally equivalent to `ITEMS_HARD`. - */ - | "ItemsDelicate" - /** - * Siege engine parts can be made out of this material. Does not appear to work. - */ - | "ItemsSiegeEngine" - /** - * Querns and millstones can be made out of this material. Does not appear to work. - */ - | "ItemsQuern" - /** - * An unknown token - */ - | "Unknown"; - +deadSaplingColor: Color | null; /** - * Represents the mechanical properties of a material via the yield, fracture, and elasticity + * The sapling of this tree will drown once the water on its tile reaches this level. Defaults to 4. */ -export type MechanicalProperties = { - yield: number; - fracture: number; - elasticity: number; -}; - +saplingDrownLevel: number | null; /** - * The `RawMetadata` struct represents metadata about a raw module in Rust, including its name, - * version, file path, identifier, object type, module location, and visibility status. - * - * Properties: - * - * * `module_name`: The name of the raw module the raw is from. - * * `module_version`: The version of the raw module the raw is from. - * * `raw_file_path`: The `raw_file_path` property is a string that represents the path to the file - * containing the raw data. It specifies the location of the file on the file system. - * * `raw_identifier`: The raw identifier is a unique identifier for the raw data. It is typically - * found at the top of the raw text file and is used to identify and reference the specific raw data. - * * `object_type`: The `object_type` property represents the type of the raw data. It could be a - * creature, plant, or any other type specified in the raw text file. - * * `raw_module_location`: The `raw_module_location` property represents the location of the owning - * raw module. It can have one of the following values: - * - * - `RawModuleLocation::InstalledMods`: The raw module is located in the `installed_mods` folder. - * - `RawModuleLocation::Mods`: The raw module is located in the `mods` folder. - * - `RawModuleLocation::Vanilla`: The raw module is located in the `vanilla` folder. - * - * * `hidden`: The `hidden` property is a boolean value that indicates whether the raw metadata should - * be hidden or not when exporting. By default, it is set to `true`, meaning that the raw metadata will - * be hidden unless specified in the `ParsingOptions` struct. + * The water depth at which this tree will drown. Exact behavior is unknown. Defaults to 7. */ -export type Metadata = { - moduleObjectId: string; - moduleName: string; - moduleVersion: string; - rawFilePath: string; - rawIdentifier: string; - objectType: ObjectType; - rawModuleLocation: RawModuleLocation; -}; - +treeDrownLevel: number | null; /** - * How often a creature can be milked and what material it produces + * Token tags for the tree. */ -export type Milkable = { material: string; frequency: number }; +tags: TreeTag[] | null } /** - * A struct representing a modification to a creature + * The tokens for the tree parser */ -export type ModificationTag = - /** - * `COPY_TAGS_FROM` tag - */ - | { - CopyTagsFrom: { - /** - * The creature to copy tags from - */ - identifier: string; - }; - } - /** - * `APPLY_CREATURE_VARIATION` tag - */ - | { - ApplyCreatureVariation: { - /** - * The creature to apply the variation from - */ - identifier: string; - }; - } - /** - * Follows `GO_TO_END` until `GO_TO_START` or object definition finishes - * - * When using tags from an existing creature, inserts new tags at the end of the creature. - */ - | { - AddToEnding: { - /** - * The set of raws to add to the end of the object - * - * This should be the entire raw in order to apply. - */ - raws: string[]; - }; - } - /** - * Follows `GO_TO_START` until `GO_TO_END` or object definition finishes - * - * When using tags from an existing creature, inserts new tags at the beginning of the creature. - */ - | { - AddToBeginning: { - /** - * The set of raws to add to the beginning of the object - * - * This should be the entire raw in order to apply. - */ - raws: string[]; - }; - } - /** - * `GO_TO_TAG:tag` raw instruction - * - * When using tags from an existing creature, inserts new tags before the specified tag. - */ - | { - AddBeforeTag: { - /** - * The tag to insert before - * - * Since we don't actually know the tag order after parsing, this will be ignored in parsing, and - * instead will just apply the raws... - */ - tag: string; - /** - * The set of raws to add before the tag - * - * This should be the entire raw in order to apply. - */ - raws: string[]; - }; - } - /** - * The main body of the object - */ - | { - MainRawBody: { - /** - * The set of raws that make up the object. This is usually defined first unless - * its specified to be added to the end or beginning (or before a tag) - * - * This should be the entire raw in order to apply. - */ - raws: string[]; - }; - }; - +export type TreeTag = /** - * A name with a singular, plural, and adjective form + * A tree */ -export type Name = { - singular: string; - plural: string; - adjective: string | null; -}; - +"Tree" | /** - * The various types of objects that are within the raw files. + * The name of the tree */ -export type ObjectType = - /** - * A creature - */ - | "Creature" - /** - * An inorganic material - */ - | "Inorganic" - /** - * A plant - */ - | "Plant" - /** - * An item - */ - | "Item" - /** - * An item of type ammo - */ - | "ItemAmmo" - /** - * An item of type armor - */ - | "ItemArmor" - /** - * An item of type food - */ - | "ItemFood" - /** - * An item of type gloves - */ - | "ItemGloves" - /** - * An item of type helm - */ - | "ItemHelm" - /** - * An item of type instrument - */ - | "ItemInstrument" - /** - * An item of type pants - */ - | "ItemPants" - /** - * An item of type shield - */ - | "ItemShield" - /** - * An item of type shoes - */ - | "ItemShoes" - /** - * An item of type siege ammo - */ - | "ItemSiegeAmmo" - /** - * An item of type tool - */ - | "ItemTool" - /** - * An item of type toy - */ - | "ItemToy" - /** - * An item of type trap component - */ - | "ItemTrapComponent" - /** - * An item of type weapon - */ - | "ItemWeapon" - /** - * A building - */ - | "Building" - /** - * A workshop building - */ - | "BuildingWorkshop" - /** - * A furnace building - */ - | "BuildingFurnace" - /** - * A reaction - */ - | "Reaction" - /** - * Graphics - */ - | "Graphics" - /** - * A material template - */ - | "MaterialTemplate" - /** - * A body detail plan - */ - | "BodyDetailPlan" - /** - * A body - */ - | "Body" - /** - * An entity - */ - | "Entity" - /** - * A language - */ - | "Language" - /** - * A translation - */ - | "Translation" - /** - * A tissue template - */ - | "TissueTemplate" - /** - * A creature variation - */ - | "CreatureVariation" - /** - * A text set - */ - | "TextSet" - /** - * A tile page - */ - | "TilePage" - /** - * A descriptor color - */ - | "DescriptorColor" - /** - * A descriptor pattern - */ - | "DescriptorPattern" - /** - * A descriptor shape - */ - | "DescriptorShape" - /** - * A palette - */ - | "Palette" - /** - * Music - */ - | "Music" - /** - * Sound - */ - | "Sound" - /** - * An interaction - */ - | "Interaction" - /** - * An unknown object type - */ - | "Unknown" - /** - * `SelectCreature` tag - */ - | "SelectCreature" - /** - * A creature caste - */ - | "CreatureCaste"; - +"TrunkName" | /** - * # Parsing Options - * - * Specify what to parse and where to parse it from. - * - * ## Parsing `info.txt` vs the raw files - * - * There are two main parsing functions: `parse` and `parse_module_info_files`. - * - * Both use the same options struct, but they use it in different ways. - * - * When calling `parse`, the `ParserOptions` struct is used to specify what raws to parse and where to parse them from. - * Any specified `raw_modules_to_parse` will not be parsed in the `parse` function, and the only items parsed in the - * `parse_module_info_files` function are the `module_info_files_to_parse`. - * - * ## Example - * - * ```rust - * use std::path::PathBuf; - * use dfraw_parser::metadata::{ParserOptions, ObjectType, RawModuleLocation}; - * use dfraw_parser::traits::RawObject; - * - * let mut options = ParserOptions::new(); - * options.add_location_to_parse(RawModuleLocation::Vanilla); - * // Clear the default object types - * options.set_object_types_to_parse(vec![]); - * // Add back in the ones we want - * options.add_object_type_to_parse(ObjectType::Creature); - * options.add_object_type_to_parse(ObjectType::CreatureVariation); - * // Include the metadata with the parsed raws - * options.attach_metadata_to_raws(); - * - * // Then you could parse the raws and info.txt files - * // let parsed_raws = dfraw_parser::parse(&options); - * ``` - * - */ -export type ParserOptions = { - /** - * Whether to attach a metadata field to the raws. - * If true, all raws will have a `metadata` field which shows information about the - * raw file, its path, its module, and its parent directory. - * - * Default: false. - */ - attachMetadataToRaws: boolean; - /** - * Whether to skip the "copy tags from" resolution step. - * If true, the creature will have a populated `copy_tags_from` field instead. - * - * Default: false. - */ - skipApplyCopyTagsFrom: boolean; - /** - * Whether to skip the apply "creature variations" resolution step. - * When this is true, it will just leave the variations attached to the creature - * in a `creature_variations` field. - * If false, it will modify the creature data to include the variations. - * - * Note: This is currently not implemented. - * - * Default: false. - */ - skipApplyCreatureVariations: boolean; - /** - * What types of raws to parse. If this is left empty, all parsable raws will be parsed. - * - * Default: `[Creature, CreatureVariation, Entity, Plant, Inorganic, MaterialTemplate, Graphics, TilePage]` - */ - objectTypesToParse: ObjectType[]; - /** - * What locations to parse raws from. If this is left empty, no locations will be parsed. - * - * Setting locations to parse requires a valid `dwarf_fortress_directory` to be set. - * - * Default: None - */ - locationsToParse: RawModuleLocation[]; - /** - * The paths to the locations used for parsing: the Dwarf Fortress installation directory and the - * Dwarf Fortress user data directory. - * - * This can be automatically gathered or explicitly set. - * - * Default: Attempted to be automatically gathered. - */ - locations: LocationHelper; - /** - * Optionally specify one or more `legends_plus` exports to parse in addition to the raws. - * These exports include information about generated creatures which are not included in the - * raws. - * - * Default: None - */ - legendsExportsToParse: string[]; - /** - * Optionally specify one or more raw files to parse directly. These should be the raw files - * themselves, not the containing directory. - * - * (e.g. `creature_standard.txt` in `data/vanilla/vanilla_creatures/objects/`) - * - * Note that these will be parsed in addition to the raws in the specified locations in the other - * options. That means that if you specify a raw file that is also in the vanilla raws, it will - * be parsed twice (if vanilla is in the locations to parse). - * - * Default: None - */ - rawFilesToParse: string[]; - /** - * Optionally specify one or more raw modules to parse directly. These should be the module - * directories, not the info.txt file. - * - * (e.g. `vanilla_creatures` in `data/vanilla/`) - * - * Note that these will be parsed in addition to the raws in the specified locations in the other - * options. That means that if you specify a module that is also in the vanilla raws, it will - * be parsed twice (if vanilla is in the locations to parse). - * - * Default: None - */ - rawModulesToParse: string[]; - /** - * Optionally specify one or more module info files to parse directly. These should be the info.txt - * files themselves, not the containing directory. - * - * (e.g. `info.txt` in `data/vanilla/vanilla_creatures/`) - * - * Note that if you are calling the `parse` function, this will be ignored. This is only used - * when calling the `parse_module_info_files` function. - */ - moduleInfoFilesToParse: string[]; - /** - * Include a summary of what was parsed in the log. - * - * If running with `tauri`, this will emit a `PARSE_SUMMARY` event with the summary as well. - * - * Default: false - */ - logSummary: boolean; - /** - * Log warnings about the format of the info.txt file. - * - * Typically this includes non-integer "before version" tags or other format errors which Dwarf Fortress - * will ignore/do its best to parse. They tend to not prevent the module to work, but they are technically - * incorrectly formatted. This would mostly be useful for mod authors to check. - * - * Default: false - */ - includeWarningsForInfoFileFormat: boolean; -}; - + * The maximum height of the trunk + */ +"MaxTrunkHeight" | /** - * A struct representing a plant + * The maximum diameter of the trunk */ -export type Plant = { - /** - * Common Raw file Things - */ - metadata?: Metadata | null; - identifier: string; - objectId: string; - name: Name; - prefStrings?: string[] | null; - tags?: PlantTag[] | null; - /** - * Default [0, 0] (aboveground) - */ - undergroundDepth?: [number, number] | null; - /** - * Default frequency is 50 - */ - frequency?: number | null; - /** - * List of biomes this plant can grow in - */ - biomes?: BiomeTag[] | null; - /** - * Growth Tokens define the growths of the plant (leaves, fruit, etc.) - */ - growths?: PlantGrowth[] | null; - /** - * If plant is a tree, it will have details about the tree. - */ - treeDetails?: Tree | null; - /** - * If plant is a shrub, it will have details about the shrub. - */ - shrubDetails?: Shrub | null; - materials?: Material[] | null; -}; - +"MaxTrunkDiameter" | /** - * The graphic of the tile + * The period of the trunk */ -export type PlantGraphicTemplateTag = - /** - * The standard leaves - */ - | "standardLeaves" - /** - * The standard fruit 1 - */ - | "standardFruit1" - /** - * The standard fruit 2 - */ - | "standardFruit2" - /** - * The standard fruit 3 - */ - | "standardFruit3" - /** - * The standard fruit 4 - */ - | "standardFruit4" - /** - * The standard flowers 1 - */ - | "standardFlowers1" - /** - * The standard flowers 2 - */ - | "standardFlowers2" - /** - * The standard flowers 3 - */ - | "standardFlowers3" - /** - * The standard flowers 4 - */ - | "standardFlowers4"; - +"TrunkPeriod" | /** - * A struct representing a plant growth + * The period of the trunk width */ -export type PlantGrowth = { - /** - * Plant growths are not given an identifier, since they are just supporting - * data for the plant definition. They are defined instead by the type of growth. - */ - growthType: PlantGrowthTypeTag; - /** - * The name of the growth. This is actually defined with `GROWTH_NAME` key in the raws. - */ - name: Name; - /** - * The item grown by this growth. This is actually defined with `GROWTH_ITEM` key in the raws. - * This is a string until we make a proper item structure. Technically there are 2 arguments: - * 1. item token, 2: material token. Generally the item type should be `PLANT_GROWTH:NONE`. - */ - item: string; - /** - * Specifies on which part of the plant this growth grows. This is defined with `GROWTH_HOST_TILE` key. - * This can be unused, like in the case of crops where the plant is the growth (I think?). - */ - hostTiles?: PlantPartTag[] | null; - /** - * Controls the height on the trunk above which the growth begins to appear. - * The first value is the percent of the trunk height where the growth begins appearing: - * 0 will cause it along the entire trunk (above the first tile), 100 will cause it to appear - * at the topmost trunk tile. Can be larger than 100 to cause it to appear above the trunk. - * The second value must be -1, but might be intended to control whether it starts height counting - * from the bottom or top. - */ - trunkHeightPercentage?: [number, number] | null; - /** - * Currently has no effect. - */ - density?: number | null; - /** - * Specifies the appearance of the growth. This is defined with `GROWTH_PRINT` key. - * This is a string until we make a proper print structure. - */ - print?: string | null; - /** - * Specifies at which part of the year the growth appears. Default is all year round. - * Minimum: 0, Maximum: `402_200`. This is defined with `GROWTH_TIMING` key. - */ - timing?: [number, number] | null; - /** - * Where we gather some of the growth's tags. - */ - tags?: PlantGrowthTag[] | null; -}; - +"TrunkWidthPeriod" | /** - * The growth tag of a plant + * The name of the branches */ -export type PlantGrowthTag = - /** - * The beginning of a growth tag - */ - | "Growth" - /** - * The name of the growth - */ - | "GrowthName" - /** - * The item from the growth - */ - | "GrowthItem" - /** - * The host tile the growth grows on - */ - | "GrowthHostTile" - /** - * The trunk height percent of the growth - */ - | "GrowthTrunkHeightPercent" - /** - * The growth density - */ - | "GrowthDensity" - /** - * The growth timing - */ - | "GrowthTiming" - /** - * The growth print - */ - | "GrowthPrint" - /** - * If the growth has a seed - */ - | "GrowthHasSeed" - /** - * If the growth drops off the plant - */ - | "GrowthDropsOff" - /** - * If the growth drops off the plant and there is no cloud - */ - | "GrowthDropsOffNoCloud" - /** - * An unknown growth tag - */ - | "Unknown"; - +"BranchName" | /** - * The types of growths + * The density of the branches */ -export type PlantGrowthTypeTag = - /** - * The growth is a leaf - */ - | "Leaves" - /** - * The growth is a flower cluster - */ - | "Spathes" - /** - * The growth is a fruit - */ - | "Fruit" - /** - * The growth is a flower - */ - | "Flowers" - /** - * The growth is a nut - */ - | "Nut" - /** - * The growth is a seed catkin - */ - | "SeedCatkins" - /** - * The growth is a pollen catkin - */ - | "PollenCatkins" - /** - * The growth is a cone - */ - | "Cone" - /** - * The growth is a seed cone - */ - | "SeedCone" - /** - * The growth is a pollen cone - */ - | "PollenCone" - /** - * The growth is a feather - */ - | "Feathers" - /** - * The growth is an egg - */ - | "Eggs" - /** - * The growth is a pod - */ - | "Pod" - /** - * An unknown growth type - */ - | "None"; - +"BranchDensity" | /** - * Parts of a plant + * The radius of the branches */ -export type PlantPartTag = - /** - * Twigs - */ - | "Twigs" - /** - * Branches - */ - | "Branches" - /** - * Branches and twigs - */ - | "BranchesAndTwigs" - /** - * All branches and twigs - */ - | "AllBranchesAndTwigs" - /** - * Heavy branches - */ - | "HeavyBranches" - /** - * Heavy branches and twigs - */ - | "HeavyBranchesAndTrunk" - /** - * Trunk - */ - | "Trunk" - /** - * Roots - */ - | "Roots" - /** - * Cap (canopy) - */ - | "Cap" - /** - * Sapling - */ - | "Sapling" - /** - * An unknown part of the plant - */ - | "Unknown"; - +"BranchRadius" | /** - * The tags of a plant + * The name of the heavy branches */ -export type PlantTag = - /** - * The plant grows in dry conditions - */ - | "Dry" - /** - * The plant grows in evil conditions - */ - | "Evil" - /** - * The plant grows in good conditions - */ - | "Good" - /** - * The plant grows in savage conditions - */ - | "Savage" - /** - * The plant grows in wet conditions - */ - | "Wet" - /** - * The plant grows at a specific underground depth - */ - | "UndergroundDepth" - /** - * The plant grows in a specific biome - */ - | "Biome" - /** - * The frequency of the plant - */ - | "Frequency" - /** - * The material template to use for the plant - */ - | "UseMaterialTemplate" - /** - * The basic material to use for the plant - */ - | "BasicMaterial" - /** - * The material to use for the plant - */ - | "UseMaterial" - /** - * The material of the plant - */ - | "Material" - /** - * What dwarves prefer about the plant - */ - | "PrefString" - /** - * All names of the plant - */ - | "AllNames" - /** - * The adjective name of the plant - */ - | "NameAdjective" - /** - * The plural name of the plant - */ - | "NamePlural" - /** - * The singular name of the plant - */ - | "NameSingular" - /** - * An unknown plant tag - */ - | "Unknown"; - +"HeavyBranchesName" | /** - * Represents a position in the government of an entity + * The density of the heavy branches */ -export type Position = { - identifier: string; - allowedClasses?: string[] | null; - allowedCreatures?: string[] | null; - appointedBy?: string | null; - color?: Color | null; - commander?: string | null; - demandMax?: number | null; - executionSkill?: string | null; - gender?: string | null; - landHolder?: number | null; - landName?: string | null; - mandateMax?: number | null; - name?: Name | null; - nameMale?: Name | null; - nameFemale?: Name | null; - number?: number | null; - precedence?: number | null; - rejectedClasses?: string[] | null; - rejectedCreatures?: string[] | null; - replacedBy?: string | null; - requiredBedroom?: number | null; - requiredBoxes?: number | null; - requiredCabinets?: number | null; - requiredDining?: number | null; - requiredOffice?: number | null; - requiredRacks?: number | null; - requiredStands?: number | null; - requiredTomb?: number | null; - requiresPopulation?: number | null; - responsibilities?: string[] | null; - spouse?: Name | null; - spouseFemale?: Name | null; - spouseMale?: Name | null; - squad?: string | null; - succession?: string | null; - tags: PositionTag[]; -}; - +"HeavyBranchDensity" | /** - * Represents a position token + * The radius of the heavy branches */ -export type PositionTag = - /** - * The position holder is not subjected to the economy. Less than relevant right now. - */ - | "AccountExempt" - /** - * Arguments: creature class token - * - * Only creatures with the specified class token can be appointed to this position. Multiple entries are allowed - */ - | "AllowedClass" - /** - * Arguments: creature:caste token - * - * Restricts the position to only the defined caste. Only works with a caste of the entity's current race. - * (If the entity had multiple CREATURE: tokens). Multiple entries are allowed - */ - | "AllowedCreature" - /** - * Arguments: position - * - * This position can only be chosen for the task from the nobles screen, and is available only if there is an *argument* present. - * For example, the `GENERAL` is `[APPOINTED_BY:MONARCH]`. Contrast `[ELECTED]`. Being appointed by a `MONARCH` seems to handle a lot of - * worldgen stuff, and interferes with fort mode titles. Multiple entries are allowed. If you have neither an `ELECTED`-token nor a - * `APPOINTED_BY`-token, the holder may always be changed (like the expedition leader) - */ - | "AppointedBy" - /** - * A creature that kills a member of this position will be sure to talk about it a lot. - */ - | "BragOnKill" - /** - * In adventure mode, when referencing locations, an NPC may mention this position holder living there or having done some - * deed there, it also means that the position exists in world-gen, rather than being created only at the end of world-gen. - * - * Before 47.05, Dark Fortress civs cannot have this tag on anybody but their Law Maker, or the game will crash without - * leaving an errorlog. - */ - | "ChatWorthy" - /** - * Arguments: color:background:foreground - * - * Creatures of this position will have this color, instead of their profession color - * - * e.g. `[COLOR:5:0:1]`. - */ - | "Color" - /** - * Arguments: position, 'ALL' - * - * This position will act as a commander of the specified position. - * - * E.g. GENERAL is `[COMMANDER:LIEUTENANT:ALL]`. Unknown if values other than ALL work. Multiple entries are allowed - */ - | "Commander" - /** - * This position is a puppet ruler left behind in a conquered site. - */ - | "ConqueredSite" - /** - * Arguments: number (0-`100`) - * - * How many demands the position can make of the population at one time. - */ - | "DemandMax" - /** - * The site's (or civ's) minted coins, if any, will have images that reflect the personality of this position holder. - */ - | "DeterminesCoinDesign" - /** - * The position won't be culled from Legends as "unimportant" during world generation. - */ - | "DoNotCull" - /** - * Members of this position will never agree to 'join' your character during adventure mode. - */ - | "DutyBound" - /** - * The population will periodically select the most skill-eligible creature to fill this position for site-level positions - * at the player's fort. For responsibilities or positions that use more than one skill, no skill takes priority in electing - * a creature: an accomplished comedian is more qualified for the TRADE responsibility than a skilled appraiser. - * A creature may be elected to multiple positions at the same time. Contrast `[APPOINTED_BY]`. More info: Elections - */ - | "Elected" - /** - * Arguments: weapon skill - * - * A mandatory sub-tag of `[RESPONSIBILITY:EXECUTIONS]`. Determines the weapon chosen by the executioner for their work. - */ - | "ExecutionSkill" - /** - * The various members who have filled this role will be listed in the civilization's history. - */ - | "ExportedInLegends" - /** - * The creature holding this position will visibly flash, like legendary citizens. Represents a properly noble station by default. - */ - | "Flashes" - /** - * Arguments: 'MALE' or 'FEMALE' - * - * The position can only be held by the specified gender. Currently bugged Bug:2714 - */ - | "Gender" - /** - * The position can assign quests to adventurers. - */ - | "KillQuest" - /** - * Arguments: importance tier (1-`10`) - * - * This is an alternative to `SITE`. What it does is allow positions to be created at civ-level 'as needed' for all sites that - * meet the requirements to have them, which are the values set in `LAND_HOLDER_TRIGGER`. The character is tied permanently to - * a particular site but also operates at the civ-level. Since 50* modded levels of higher than 3 are possible. - */ - | "LandHolder" - /** - * Arguments: name (a string) - * - * The name the area takes on when under the control of a `LAND_HOLDER`. - * - * E.g. for the DUKE, `[LAND_NAME:a duchy]`. - * - * If the position is not a `LAND_HOLDER`, the `land_name` is still displayed left of the position in the nobles menu. - */ - | "LandName" - /** - * Arguments: number (0-`100`) - * - * The maximum number of mandates the position can make at once. - */ - | "MandateMax" - /** - * The position holder cannot be assigned labors. Currently nonfunctional.Bug:3721 - */ - | "MenialWorkExemption" - /** - * The spouse of the position holder doesn't have to work, either - see above. - */ - | "MenialWorkExemptionSpouse" - /** - * This position cannot be appointed from the nobles screen. Intended for militia captains and other squad leaders to reduce clutter. Currently nonfunctionalBug:8965 - */ - | "MilitaryScreenOnly" - /** - * Arguments: `SingPlurName` - * - * The name of the position. - */ - | "Name" - /** - * Arguments: `SingPlurName` - * - * If the creature holding the position is male, this is the position's name. - * - * E.g. for MONARCH, `[NAME_MALE:king:kings]` - */ - | "NameMale" - /** - * Arguments: `SingPlurName` - * - * If the creature holding the position is female, this is the position's name. - * - * E.g. for MONARCH, `[NAME_FEMALE:queen:queens]` - */ - | "NameFemale" - /** - * arguments: description - * - * Description of this position in the nobles screen. - */ - | "Description" - /** - * Arguments: number or `AS_NEEDED` - * - * How many of the position there should be. If the `[SITE]` token exists, this is per site, otherwise this is per civilization. - * - * `AS_NEEDED` applies only to positions involved with the military command chain; this is used to allow armies to expand to - * whatever size they need to be. Non-military positions with `NUMBER:AS_NEEDED` will not be appointed. - * The problem with Lieutenants and Captains not been created, is their `AS_NEEDED` number. - * They are only then created when the're needed, and that has some pretty unusual conditions. - * When a fixed number is used, they are appointed with the creation of the civ. - */ - | "Number" - /** - * Arguments: number (0 - `30_000`) or 'NONE' - * - * How important the position is in society; a lower number is more important and displayed higher in the Nobles menu. - * For `MONARCH` it's 1, for `MILITIA_CAPTAIN` it's 200. The game just assumes that anything with `[PRECEDENCE:1]` is the ruler, - * for both embark screen and mountain home purposes. - * - * A civ-position will also be created without precedence. Positions may have the same precedence and will be appointed, - * although the effect is unknown. - */ - | "Precedence" - /** - * The position holder will not be held accountable for his or her crimes. Currently nonfunctional. - */ - | "PunishmentExemption" - /** - * The position holder can give quests in Adventure mode. Functionality in 0.31.13 and later is uncertain. - */ - | "QuestGiver" - /** - * Arguments: creature class token - * - * Creatures of the specified class cannot be appointed to this position. Multiple entries are allowed - */ - | "RejectedClass" - /** - * Arguments: `creature:caste` token - * - * Restricts position holders by `CREATURE` type. Multiple entries are allowed - */ - | "RejectedCreature" - /** - * Arguments: position - * - * This position is absorbed by another down the line. For example, expedition leader is `[REPLACED_BY:MAYOR]`. - * Only a single entry is allowed. - */ - | "ReplacedBy" - /** - * Arguments: number (0 - `10_000_000`) - * - * The position holder requires a bedroom with at least this value. - */ - | "RequiredBedroom" - /** - * Arguments: number (0 - `100`) - * - * The position holder requires at least this many boxes. - */ - | "RequiredBoxes" - /** - * Arguments: number (0 - `100`) - * - * The position holder requires at least this many cabinets. - */ - | "RequiredCabinets" - /** - * Arguments: number (0 - `10_000_000`) - * - * The position holder requires a dining room with at least this value. - */ - | "RequiredDining" - /** - * Arguments: number (0 - `10_000_000`) - * - * The position holder requires an office with at least this value. - */ - | "RequiredOffice" - /** - * Arguments: number (0 - `100`) - * - * The position holder requires at least this many weapon racks. - */ - | "RequiredRacks" - /** - * Arguments: number (0 - `100`) - * - * The position holder requires at least this many armour stands. - */ - | "RequiredStands" - /** - * Arguments: number (0 - `10_000_000`) - * - * The position holder requires a tomb with at least this value. - */ - | "RequiredTomb" - /** - * Does not have anything directly to do with markets. It means that in minor sites (such as hillocks) the position will not - * appear, while in major sites (such as dwarf fortresses) it will. - */ - | "RequiresMarket" - /** - * Arguments: number - * - * The position requires the population to be at least this number before it becomes available, or before the position holder - * will move in. - */ - | "RequiresPopulation" - /** - * Arguments: responsibility - * - * The position holder does a thing. See the table below for suitable arguments. - * - * A position does not need to have a responsibility. - */ - | "Responsibility" - /** - * If there is a special location set aside for rulers, such as a human castle/mead hall, the position holder will always be - * found at that particular location. Does nothing for dwarven nobles, because at present, dwarves have no such special locations. - */ - | "RulesFromLocation" - /** - * Every site government will have the defined number of this position instead of the whole civilization; provided that other - * criteria (if any) are met. Unless `LAND_HOLDER` is present instead, the defined number of the position will be created only - * for the civilization as a whole. - */ - | "Site" - /** - * The position holder will get upset if someone with a higher `PRECEDENCE` holds quarters with a greater value than their own. - */ - | "SleepPretension" - /** - * The civilization will inter the corpse of the position holder in a special grave, either in catacombs or in monuments. - * If that grave is disturbed, the position holder can return as a mummy. - */ - | "SpecialBurial" - /** - * Arguments: `SingPlurName` - * - * The name of the position holder's spouse. - */ - | "Spouse" - /** - * Arguments: `SingPlurName` - * - * If the spouse of the creature holding the position is female, this is the spouse's position name. - */ - | "SpouseFemale" - /** - * Arguments: `SingPlurName` - * - * If the spouse of the creature holding the position is male, this is the spouse's position name. - */ - | "SpouseMale" - /** - * Arguments: `number:SingPlurName` - * - * The position holder is authorized to form a military squad, led by themselves using the leader and military tactics skills. - * The number denotes the maximum headcount. The noun used to describe the subordinates (e.g. royal guard) is used in adventure - * mode for the adventurer. - */ - | "Squad" - /** - * Arguments: `BY_HEIR` or `BY_POSITION:position` - * - * How a new position holder is chosen. A single position can have multiple `BY_POSITION` tokens. - * See Noble for more information on how succession is handled in the game. - */ - | "Succession" - /** - * An uknow token. - */ - | "Unknown"; - +"HeavyBranchRadius" | /** - * Raws are part of modules since 50.xx. Raw modules are loaded from 3 common locations: - * `{df_directory}/data/vanilla`, `{df_directory}/mods`, and `{df_directory/data/installed_mods}` + * The branching of the heavy branches */ -export type RawModuleLocation = - /** - * The "installed" mods directory - */ - | "InstalledMods" - /** - * The "downloaded" mods directory - */ - | "Mods" - /** - * The vanilla data file location - */ - | "Vanilla" - /** - * An unknown location - */ - | "Unknown" - /** - * Used for handling legends exported files - */ - | "LegendsExport"; - +"TrunkBranching" | /** - * The tokens for the seasons + * The name of the roots */ -export type SeasonTag = - /** - * The spring season - */ - | "Spring" - /** - * The summer season - */ - | "Summer" - /** - * The autumn season - */ - | "Autumn" - /** - * The winter season - */ - | "Winter" - /** - * An unknown season - */ - | "Unknown"; - +"RootName" | /** - * A struct representing a seed material + * The density of the roots */ -export type SeedMaterial = { name: Name; color: Color; material: string }; - +"RootDensity" | /** - * A struct representing a creature selection + * The radius of the roots */ -export type SelectCreature = { - metadata?: Metadata | null; - identifier: string; - objectId: string; - tags: string[]; -}; - +"RootRadius" | /** - * The rules for selecting a creature + * The name of the twigs */ -export type SelectCreatureRuleTag = - /** - * Selects a previously defined caste - */ - | { SelectCaste: string } - /** - * Selects a locally defined material. Can be ALL. - */ - | { SelectMaterial: string } - /** - * Selects a tissue for editing. - */ - | { SelectTissue: string } - /** - * Adds an additional previously defined caste to the selection. Used after `[SELECT_CASTE]`. - */ - | { SelectAdditionalCaste: string }; - +"TwigsName" | /** - * A shrub in the raws. + * Twigs are placed on the side of the branches */ -export type Shrub = { - /** - * Allows the plant to grow in farm plots during the given season. - * If the plant is a surface plant, allows it to grow in the wild during this season; wild surface plants without - * this token will disappear at the beginning of the season. Underground plants grow wild in all seasons, regardless - * of their season tokens. - * Default: empty (plant will not grow in farm plots) - */ - growingSeason?: SeasonTag[] | null; - /** - * How long the plant takes to grow to harvest in a farm plot. Unit hundreds of ticks. - * There are 1008 GROWDUR units in a season. Defaults to 300. - */ - growDuration?: number | null; - /** - * Has no known effect. Previously set the value of the harvested plant. - */ - value?: number | null; - /** - * The tile used when the plant is harvested whole, or is ready to be picked from a farm plot. May either be a cp437 - * tile number, or a character between single quotes. See character table. Defaults to 231 (τ). - */ - pickedTile?: number | null; - /** - * The tile used when a plant harvested whole has wilted. Defaults to 169 (⌐). - */ - deadPickedTile?: number | null; - /** - * The tile used to represent this plant when it is wild, alive, and has no growths. Defaults to 34 ("). - */ - shrubTile?: number | null; - /** - * The tile used to represent this plant when it is dead in the wild. Defaults to 34 ("). - */ - deadShrubTile?: number | null; - /** - * The maximum stack size collected when gathered via herbalism (possibly also from farm plots?). Defaults to 5. - */ - clusterSize?: number | null; - /** - * The color of the plant when it has been picked whole, or when it is ready for harvest in a farm plot. Defaults to 2:0:0 (dark green). - */ - pickedColor?: Color | null; - /** - * The color of the plant when it has been picked whole, but has wilted. Defaults to 0:0:1 (dark gray). - */ - deadPickedColor?: Color | null; - /** - * The color of the plant when it is alive, wild, and has no growths. Defaults to 2:0:0 (dark green). - */ - shrubColor?: Color | null; - /** - * The color of the plant when it is dead in the wild. Defaults to 6:0:0 (brown). - */ - deadShrubColor?: Color | null; - /** - * The shrub will drown once the water on its tile reaches this level. Defaults to 4. - */ - shrubDrownLevel?: number | null; - /** - * Names a drink made from the plant, allowing it to be used in entity resources. - * Previously also permitted brewing the plant into alcohol made of this material. - * Now, a `MATERIAL_REACTION_PRODUCT` of type `DRINK_MAT` should be used on the proper plant material. - */ - drink?: string | null; - /** - * Permits milling the plant at a quern or millstone into a powder made of this material and allows its use in entity resources. - * Said material should have `[POWDER_MISC_PLANT]` to permit proper stockpiling. This token makes the whole plant harvestable regardless - * of which material is designated for milling. - * For plants with millable growths, use only `MATERIAL_REACTION_PRODUCT` or `ITEM_REACTION_PRODUCT` tokens to define the milling products. - */ - mill?: string | null; - /** - * Permits processing the plant at a farmer's workshop to yield threads made of this material and allows its use in entity resources. - * Said material should have `[THREAD_PLANT]` to permit proper stockpiling. - */ - thread?: string | null; - /** - * Causes the plant to yield plantable seeds made of this material and having these properties. - * Said material should have `[SEED_MAT]` to permit proper stockpiling. - */ - seed?: SeedMaterial | null; - /** - * Permits processing the plant into a vial at a still to yield extract made of this material. - * Said material should have `[EXTRACT_STORAGE:FLASK]`. - */ - extractStillVial?: string | null; - /** - * Permits processing the plant into a vial at a farmer's workshop to yield extract made of this material. - * Said material should have `[EXTRACT_STORAGE:VIAL]`. - */ - extractVial?: string | null; - /** - * Permits processing the plant into a barrel at a farmer's workshop to yield extract made of this material. - * Said material should have `[EXTRACT_STORAGE:BARREL]`. - */ - extractBarrel?: string | null; -}; - +"TwigsSideBranches" | /** - * The tokens for the shrubs + * Twigs are placed above the branches */ -export type ShrubTag = - /** - * The spring season - */ - | "Spring" - /** - * The summer season - */ - | "Summer" - /** - * The autumn season - */ - | "Autumn" - /** - * The winter season - */ - | "Winter" - /** - * The growth duration - */ - | "GrowDuration" - /** - * The value of the shrub - */ - | "Value" - /** - * The tile used for the shrub once it is picked - */ - | "PickedTile" - /** - * The tile used for the shrub once it is dead and picked - */ - | "DeadPickedTile" - /** - * The tile used for the shrub - */ - | "ShrubTile" - /** - * The tile used for the dead shrub - */ - | "DeadShrubTile" - /** - * The cluster size the shrubs will spawn in - */ - | "ClusterSize" - /** - * The color of the shrub once it is picked - */ - | "PickedColor" - /** - * The color of the shrub once it is dead and picked - */ - | "DeadPickedColor" - /** - * The color of the shrub - */ - | "ShrubColor" - /** - * The color of the dead shrub - */ - | "DeadShrubColor" - /** - * The depth level the shrub will drown at - */ - | "ShrubDrownLevel" - /** - * The shrub can be brewed - */ - | "Drink" - /** - * The shrub can be milled - */ - | "Mill" - /** - * The shrub can be spun - */ - | "Thread" - /** - * The shrub has seeds - */ - | "Seed" - /** - * The shrub can have a liquid extracted from it using a still and vial - */ - | "ExtractStillVial" - /** - * The shrub can have a liquid extracted from it using a vial alone - */ - | "ExtractVial" - /** - * The shrub can have a liquid extracted from it using a barrel - */ - | "ExtractBarrel" - /** - * An unknown token - */ - | "Unknown"; - +"TwigsAboveBranches" | /** - * A struct representing a sprite graphic. + * Twigs are placed below the branches */ -export type SpriteGraphic = { - primaryCondition: ConditionTag; - tilePageId: string; - offset: Dimensions; - color?: ColorModificationTag | null; - largeImage?: boolean | null; - offset2?: Dimensions | null; - secondaryCondition?: ConditionTag | null; - colorPalletSwap?: number | null; - targetIdentifier?: string | null; - extraDescriptor?: string | null; -}; - +"TwigsBelowBranches" | /** - * A struct representing a `SpriteLayer` object. + * Twigs are placed on the side of heavy branches */ -export type SpriteLayer = { - layerName: string; - tilePageId: string; - offset: Dimensions; - offset2?: Dimensions | null; - largeImage?: boolean | null; - conditions?: [ConditionTag, string][] | null; -}; - +"TwigsSideHeavyBranches" | /** - * Represents the name of a materials 3 states (solid, liquid, gas) + * Twigs are placed above heavy branches */ -export type StateName = { solid: string; liquid: string; gas: string }; - +"TwigsAboveHeavyBranches" | /** - * The additional data specific to the steam workshop + * Twigs are placed below heavy branches */ -export type SteamData = { - title?: string | null; - description?: string | null; - tags?: string[] | null; - keyValueTags?: string[] | null; - metadata?: string[] | null; - changelog?: string | null; - fileId: string; -}; - +"TwigsBelowHeavyBranches" | /** - * A struct representing a syndrome + * Twigs are placed on the side of the trunk */ -export type Syndrome = { - /** - * Seen the `[SYN_IDENTIFIER:INEBRIATION]` tag in `material_templates.txt` - */ - identifier?: string | null; - name?: string | null; - affectedClasses?: string[] | null; - immuneClasses?: string[] | null; - affectedCreatures?: [string, string][] | null; - immuneCreatures?: [string, string][] | null; - classes?: string[] | null; - /** - * Seen the `[SYN_CONCENTRATION_ADDED:100:1000]` tag in `material_templates.txt` - * default is 0:0 - */ - concentrationAdded?: [number, number] | null; - tags?: SyndromeTag[] | null; - conditions?: string[] | null; -}; - +"TwigsSideTrunk" | /** - * Represents the tokens that can be used in a syndrome definition. + * Twigs are placed above the trunk */ -export type SyndromeTag = - /** - * Used to specify the name of the syndrome as it appears in-game. Names don't have to be unique; - * It's perfectly acceptable to have multiple syndromes with identical names. - */ - | "Name" - /** - * Can be included to create a syndrome class and assign the syndrome to it, for use with the `IT_CANNOT_HAVE_SYNDROME_CLASS` - * interaction token. Can be specified more than once to assign the syndrome to multiple classes. - * - * Other syndromes can also be assigned to the same class. - */ - | "Class" - /** - * If the syndrome is tied to a material, the injection of this material into a creature's bloodstream will cause it to contract - * the syndrome if this token is included. Injection can be carried out as part of a creature attack via `SPECIALATTACK_INJECT_EXTRACT`, - * or by piercing the flesh of a creature with an item that has been contaminated with the material. Thus, this token can be used as a - * more specific alternative to `SYN_CONTACT` for syndromes intended to be administered by envenomed weapons. - */ - | "Injected" - /** - * If the syndrome is tied to a material, creatures who come into contact with this material will contract the syndrome - * if this token is included in the syndrome definition. Methods of getting a material contaminant onto a creature's body include: - * - secretions - * - liquid projectiles - * - vapor and dust clouds - * - puddles and dust piles - * - freakish rain - * - unprotected contact with an infected creature (punching/wrestling/colliding) - * - equipped or hauled items melting - * - being struck with a contaminated item - */ - | "Contact" - /** - * If the syndrome is tied to a material, creatures who inhale the material will contract the syndrome if this token is included. - * Materials can only be inhaled in their gaseous state, which is attainable by boiling, or in the form of a `TRAILING_GAS_FLOW`, - * `UNDIRECTED_GAS` or `WEATHER_CREEPING_GAS`. Creatures can also be made to leak gaseous tissue when damaged. - */ - | "Inhaled" - /** - * If the syndrome is tied to a material, creatures who eat or drink substances comprising, containing or contaminated with this - * material will contract the syndrome if this token is included. This includes prepared meals when any of the constituent - * ingredients contains the material in question. - * - * This also applies to grazing creatures which happen to munch on a grass that has an ingestion-triggered syndrome tied to any of - * its constituent materials. - */ - | "Ingested" - /** - * If this is included, only creatures which belong to the specified creature class (as well as creatures which pass the - * `SYN_AFFECTED_CREATURE` check if this is included) will be able to contract the syndrome. This token can be specified multiple times - * per syndrome, in which case creatures which have at least one matching class will be considered susceptible. - * - * If `SYN_IMMUNE_CLASS` and/or `SYN_IMMUNE_CREATURE` are included, creatures which fail these checks will be unable to contract the syndrome - * even if they pass this class check. - */ - | "AffectedClass" - /** - * If this is included, creatures which belong to the specified creature class will be unable to contract the syndrome. This token can be - * specified multiple times per syndrome, in which case creatures with at least one matching class will be considered immune - * (unless overridden by `SYN_AFFECTED_CREATURE`). - */ - | "ImmuneClass" - /** - * If this is included, only the specified creature (and, if `SYN_AFFECTED_CLASS` is included, also creatures which pass this check as explained above) - * will be able to contract the syndrome. This token can be used multiple times per syndrome. If used alongside `SYN_IMMUNE_CLASS`, the specified - * creature will be able to contract the syndrome regardless of this class check. - * - * `DWARF:FEMALE` is an example of a valid `creature:caste` combination. - * - * `ALL` can be used in place of a specific caste so as to indicate that this applies to all castes of the specified creature. - */ - | "AffectedCreature" - /** - * If this is included, the specified creature will be unable to contract the syndrome (even if it matches `SYN_AFFECTED_CLASS`). - * It can be specified multiple times per syndrome. As above, `ALL` can be used in place of a specific caste. - */ - | "ImmuneCreature" - /** - * Syndrome concentration is essentially a quantity which impacts the severity of the syndrome's relevant effects. The higher the syndrome's concentration, - * the greater its severity. When a syndrome is contracted, the value specified in `amount` is its initial concentration level. - * - * As described above, if a creature is exposed to a syndrome with a particular `SYN_IDENTIFIER` when already possessing an active syndrome with the same identifier, - * then this later syndrome isn't contracted, instead contributing to the original syndrome's concentration as indicated by its `SYN_CONCENTRATION_ADDED` token, if present. - * The syndrome in question will increase the original syndrome's concentration by `amount` whenever the creature is exposed to it, until its specified `max` concentration - * is reached by the original syndrome, causing subsequent exposure to this particular syndrome to do nothing (that is, until the original syndrome ends, at which point - * a new one may be contracted normally). Should the creature be exposed to a different syndrome with the same identifier and a higher `max` value, the concentration will - * of course increase further. - * - * Example: `SYN_CONCENTRATION_ADDED:amount:max` - */ - | "ConcentrationAdded" - /** - * Prevents creatures from being admitted to hospital for problems arising directly as a result of the syndrome's effects, no matter how bad they get. - */ - | "NoHospital" - /** - * This token can be included to give a syndrome an identifier which can be shared between multiple syndromes. Only one identifier may be specified per syndrome. - * - * Syndrome identifiers can be used in conjunction with the `SYNDROME_DILUTION_FACTOR` creature token to alter a creature’s innate resistance to the relevant - * effects of any syndromes that possess the specified identifier. For example, every alcoholic beverage in unmodded games comes with its own copy of an intoxicating - * syndrome, each of which has a `[SYN_IDENTIFIER:INEBRIATION]` token. All dwarves have `[SYNDROME_DILUTION_FACTOR:INEBRIATION:150]`, which decreases the severity of - * any effects derived from a syndrome with the INEBRIATION identifier, thus enabling them to better handle all forms of alcohol. - */ - | "Identifier" - /** - * Unknown as default. - */ - | "Unknown"; - +"TwigsAboveTrunk" | /** - * The `Complexity` enum is used to determine how a token is parsed. - * - * A token can have no arguments, a single argument, or multiple arguments. This corresponds to the - * `None`, `Simple`, and `Complex` variants, respectively. + * Twigs are placed below the trunk */ -export type TagComplexity = - /** - * The token affects raws by itself with no arguments - */ - | "None" - /** - * The token affects raws and requires a single argument - */ - | "Simple" - /** - * The token affects raws and requires multiple arguments - */ - | "Complex"; - +"TwigsBelowTrunk" | /** - * The temperature properties of a material + * The name of the tree canopy */ -export type Temperatures = { - /** - * This determines how long it takes the material to heat up or cool down. - * A material with a high specific heat capacity will hold more heat and affect its surroundings more - * before cooling down or heating up to equilibrium. The input for this token is not temperature, - * but rather the specific heat capacity of the material. - */ - specificHeat?: number | null; - /** - * This is the temperature at which the material will catch fire. - */ - ignitionPoint?: number | null; - /** - * This is the temperature at which a liquid material will freeze, or a solid material will melt. - * In Dwarf Fortress the melting point and freezing point coincide exactly; this is contrary to many - * real-life materials, which can be supercooled. - */ - meltingPoint?: number | null; - /** - * This is the temperature at which the material will boil or condense. Water boils at 10180 °U - */ - boilingPoint?: number | null; - /** - * This is the temperature above which the material will begin to take heat damage. - * Burning items without a heat damage point (or with an exceptionally high one) will take damage very slowly, - * causing them to burn for a very long time (9 months and 16.8 days) before disappearing. - */ - heatDamagePoint?: number | null; - /** - * This is the temperature below which the material will begin to take frost damage. - */ - coldDamagePoint?: number | null; - /** - * A material's temperature can be forced to always be a certain value via the `MAT_FIXED_TEMP` - * material definition token. The only standard material which uses this is nether-cap wood, - * whose temperature is always at the melting point of water. If a material's temperature is fixed - * to between its cold damage point and its heat damage point, then items made from that material - * will never suffer cold/heat damage. This makes nether-caps fire-safe and magma-safe despite being a type of wood. - */ - materialFixedTemperature?: number | null; -}; - +"CapName" | /** - * Representation of a character tile (literally a single character) that is used in DF Classic + * The period of the tree canopy */ -export type Tile = { - character: string; - altCharacter?: string | null; - color?: Color | null; - glowCharacter?: string | null; - glowColor?: Color | null; -}; - +"CapPeriod" | /** - * Custom wrapper for the Tile character used in tags + * The radius of the tree canopy */ -export type TileCharacter = { - /** - * The character used for the tile - */ - value: string; -}; - +"CapRadius" | /** - * A struct representing a `TilePage` object. + * The tile to use for the tree */ -export type TilePage = { - metadata?: Metadata | null; - identifier: string; - objectId: string; - file: string; - tileDim: Dimensions; - pageDim: Dimensions; -}; - +"TreeTile" | /** - * The tokens used to define the tile page + * The tile to use for a dead tree */ -export type TilePageTag = - /** - * The dimensions of the tile - */ - | "tileDim" - /** - * The dimensions of the page - */ - | "pageDim" - /** - * The file path - */ - | "file" - /** - * An unknown token - */ - | "unknown"; - +"DeadTreeTile" | /** - * A struct representing a tree. + * The tile to use for a sapling */ -export type Tree = { - /** - * Tree will yield logs made of that material. Instead, if it's `[TREE:NONE]`, no logs will result. - * Materials are typically found in other raws.. - */ - material: string; - /** - * What the trunk of the tree is named - */ - trunkName?: Name | null; - /** - * The maximum z-level height of the trunk, starting from +2 z-levels above the ground. - * Valid values: 1-8 - * Default: 1 - */ - maxTrunkHeight?: number | null; - /** - * Upper limit of trunk thickness, in tiles. Has a geometric effect on log yield. - * Valid values: 1-3 - * Default: 1 - */ - maxTrunkDiameter?: number | null; - /** - * The number of years the trunk takes to grow one z-level upward. Default: 1 - */ - trunkPeriod?: number | null; - /** - * The number of years the trunk takes to grow one tile wider. Default: 1 - */ - trunkWidthPeriod?: number | null; - /** - * What thin branches of the tree are named. - */ - branchName?: Name | null; - /** - * How dense the branches grow on this tree. - */ - branchDensity?: number | null; - /** - * The radius to which branches can reach. Appears to never reach further than seven tiles from the centre. - * Does not depend on the trunk branching amount or where trunks are. - * The values used in the game go from 0-3. Higher values than that can cause crashes. - */ - branchRadius?: number | null; - /** - * What thick branches of the tree are named. - */ - heavyBranchesName?: Name | null; - /** - * Similar to `BRANCH_DENSITY` for thick branches. Default: 0 - */ - heavyBranchDensity?: number | null; - /** - * Similar as `BRANCH_DENSITY` for thick branches. Values outside 0-3 can cause crashes. Default: 0 - */ - heavyBranchRadius?: number | null; - /** - * How much the trunk branches out. 0 makes the trunk straight (default) - */ - trunkBranching?: number | null; - /** - * What the roots of the tree are named. - */ - rootName?: Name | null; - /** - * Density of the root growth. Defaults to 0. - */ - rootDensity?: number | null; - /** - * How wide the roots reach out. Defaults to 0. - */ - rootRadius?: number | null; - /** - * What the twigs of the tree are named. - */ - twigsName?: Name | null; - /** - * Where twigs appear, defaults to `[SideBranches, AboveBranches]` - */ - twigsPlacement?: TwigPlacementTag[] | null; - /** - * What this mushroom-cap is called. Only makes sense with `TREE_HAS_MUSHROOM_CAP`. - */ - capName?: Name | null; - /** - * Similar to the other PERIOD tags, influences the rate of the mushroom cap growth. Only makes sense with `TREE_HAS_MUSHROOM_CAP`. Default: 1 - */ - capPeriod?: number | null; - /** - * The radius of a mushroom cap. Only makes sense with `TREE_HAS_MUSHROOM_CAP`. Default: 0 - */ - capRadius?: number | null; - /** - * The tile used for trees of this type on the world map. Defaults to 24 (↑). - */ - treeTile?: string | null; - /** - * The tile used for (un)dead trees and deciduous trees (generally in winter) of this type. Defaults to 198 (╞). - */ - deadTreeTile?: string | null; - /** - * The tile used for saplings of this tree. Defaults to 231 (τ). - */ - saplingTile?: string | null; - /** - * The tile used for dead saplings of this tree. Defaults to 231 (τ). - */ - deadSaplingTile?: string | null; - /** - * The color of the tree on the map. Defaults to 2:0:0 (dark green). - */ - treeColor?: Color | null; - /** - * The color of the tree on the map when (un)dead. Defaults to 0:0:1 (dark gray). - */ - deadTreeColor?: Color | null; - /** - * The color of saplings of this tree. Defaults to 2:0:0 (dark green). - */ - saplingColor?: Color | null; - /** - * The color of dead saplings of this tree. Defaults to 0:0:1 (dark gray). - */ - deadSaplingColor?: Color | null; - /** - * The sapling of this tree will drown once the water on its tile reaches this level. Defaults to 4. - */ - saplingDrownLevel?: number | null; - /** - * The water depth at which this tree will drown. Exact behavior is unknown. Defaults to 7. - */ - treeDrownLevel?: number | null; - /** - * Token tags for the tree. - */ - tags?: TreeTag[] | null; -}; - +"SaplingTile" | /** - * The tokens for the tree parser + * The tile to use for a dead sapling + */ +"DeadSaplingTile" | +/** + * The color of the tree + */ +"TreeColor" | +/** + * The color of a dead tree + */ +"DeadTreeColor" | +/** + * The color of a sapling + */ +"SaplingColor" | +/** + * The color of a dead sapling + */ +"DeadSaplingColor" | +/** + * The level at which the spling will drown (in water) + */ +"SaplingDrownLevel" | +/** + * The level at which the tree will drown (in water) + */ +"TreeDrownLevel" | +/** + * The tree has a rounded cap-hood like a giant mushroom. This severely stunts a tree's maximum height (known bug) + */ +"TreeHasMushroomCap" | +/** + * Uses the standard names for the tree components (roots, trunk, branches, etc.) + */ +"StandardTileNames" | +/** + * Makes young versions of the tree be called "[tree name] sapling"; otherwise, they are called "young [tree name]". + */ +"Sapling" | +/** + * An unknown tree token */ -export type TreeTag = - /** - * A tree - */ - | "Tree" - /** - * The name of the tree - */ - | "TrunkName" - /** - * The maximum height of the trunk - */ - | "MaxTrunkHeight" - /** - * The maximum diameter of the trunk - */ - | "MaxTrunkDiameter" - /** - * The period of the trunk - */ - | "TrunkPeriod" - /** - * The period of the trunk width - */ - | "TrunkWidthPeriod" - /** - * The name of the branches - */ - | "BranchName" - /** - * The density of the branches - */ - | "BranchDensity" - /** - * The radius of the branches - */ - | "BranchRadius" - /** - * The name of the heavy branches - */ - | "HeavyBranchesName" - /** - * The density of the heavy branches - */ - | "HeavyBranchDensity" - /** - * The radius of the heavy branches - */ - | "HeavyBranchRadius" - /** - * The branching of the heavy branches - */ - | "TrunkBranching" - /** - * The name of the roots - */ - | "RootName" - /** - * The density of the roots - */ - | "RootDensity" - /** - * The radius of the roots - */ - | "RootRadius" - /** - * The name of the twigs - */ - | "TwigsName" - /** - * Twigs are placed on the side of the branches - */ - | "TwigsSideBranches" - /** - * Twigs are placed above the branches - */ - | "TwigsAboveBranches" - /** - * Twigs are placed below the branches - */ - | "TwigsBelowBranches" - /** - * Twigs are placed on the side of heavy branches - */ - | "TwigsSideHeavyBranches" - /** - * Twigs are placed above heavy branches - */ - | "TwigsAboveHeavyBranches" - /** - * Twigs are placed below heavy branches - */ - | "TwigsBelowHeavyBranches" - /** - * Twigs are placed on the side of the trunk - */ - | "TwigsSideTrunk" - /** - * Twigs are placed above the trunk - */ - | "TwigsAboveTrunk" - /** - * Twigs are placed below the trunk - */ - | "TwigsBelowTrunk" - /** - * The name of the tree canopy - */ - | "CapName" - /** - * The period of the tree canopy - */ - | "CapPeriod" - /** - * The radius of the tree canopy - */ - | "CapRadius" - /** - * The tile to use for the tree - */ - | "TreeTile" - /** - * The tile to use for a dead tree - */ - | "DeadTreeTile" - /** - * The tile to use for a sapling - */ - | "SaplingTile" - /** - * The tile to use for a dead sapling - */ - | "DeadSaplingTile" - /** - * The color of the tree - */ - | "TreeColor" - /** - * The color of a dead tree - */ - | "DeadTreeColor" - /** - * The color of a sapling - */ - | "SaplingColor" - /** - * The color of a dead sapling - */ - | "DeadSaplingColor" - /** - * The level at which the spling will drown (in water) - */ - | "SaplingDrownLevel" - /** - * The level at which the tree will drown (in water) - */ - | "TreeDrownLevel" - /** - * The tree has a rounded cap-hood like a giant mushroom. This severely stunts a tree's maximum height (known bug) - */ - | "TreeHasMushroomCap" - /** - * Uses the standard names for the tree components (roots, trunk, branches, etc.) - */ - | "StandardTileNames" - /** - * Makes young versions of the tree be called "[tree name] sapling"; otherwise, they are called "young [tree name]". - */ - | "Sapling" - /** - * An unknown tree token - */ - | "Unknown"; +"Unknown" /** * The placement of twigs on a tree */ -export type TwigPlacementTag = - /** - * Twigs are placed on the side of the tree - */ - | "SideBranches" - /** - * Twigs are placed above the tree - */ - | "AboveBranches" - /** - * Twigs are placed below the tree - */ - | "BelowBranches" - /** - * Twigs are placed on the side of heavy branches - */ - | "SideHeavyBranches" - /** - * Twigs are placed above heavy branches - */ - | "AboveHeavyBranches" - /** - * Twigs are placed below heavy branches - */ - | "BelowHeavyBranches" - /** - * Twigs are placed on the side of the trunk - */ - | "SideTrunk" - /** - * Twigs are placed above the trunk - */ - | "AboveTrunk" - /** - * Twigs are placed below the trunk - */ - | "BelowTrunk" - /** - * An unknown twig placement - */ - | "Unknown"; +export type TwigPlacementTag = +/** + * Twigs are placed on the side of the tree + */ +"SideBranches" | +/** + * Twigs are placed above the tree + */ +"AboveBranches" | +/** + * Twigs are placed below the tree + */ +"BelowBranches" | +/** + * Twigs are placed on the side of heavy branches + */ +"SideHeavyBranches" | +/** + * Twigs are placed above heavy branches + */ +"AboveHeavyBranches" | +/** + * Twigs are placed below heavy branches + */ +"BelowHeavyBranches" | +/** + * Twigs are placed on the side of the trunk + */ +"SideTrunk" | +/** + * Twigs are placed above the trunk + */ +"AboveTrunk" | +/** + * Twigs are placed below the trunk + */ +"BelowTrunk" | +/** + * An unknown twig placement + */ +"Unknown" + diff --git a/jsonlib/src/bindings.rs b/jsonlib/src/bindings.rs new file mode 100644 index 00000000..ef1ad51a --- /dev/null +++ b/jsonlib/src/bindings.rs @@ -0,0 +1,101 @@ +use std::path::Path; + +use specta::TypeCollection; +use specta_typescript::{ExportError, Typescript}; + +/// Generate typescript type defintions for all objects in `dfraw_parser` that might be used when working +/// with JSON objects serialized with serde. +/// +/// # Errors +/// +/// Will error if the Type bindings export fails +pub fn generate_bindings(output_path: &Path) -> Result<(), ExportError> { + let exporter = Typescript::default().bigint(specta_typescript::BigIntExportBehavior::String); + let mut types = TypeCollection::default(); + + types + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::() + .register::(); + + exporter.export_to(output_path, &types) +} diff --git a/jsonlib/src/lib.rs b/jsonlib/src/lib.rs index 2cf2347c..7c8427a6 100644 --- a/jsonlib/src/lib.rs +++ b/jsonlib/src/lib.rs @@ -60,6 +60,7 @@ for the steam workshop if it is a mod downloaded from the steam workshop. pub use dfraw_parser::*; +mod bindings; /// This has utility functions for file operations and directory traversal. /// /// The functions provide functionality for working with directories, files, and paths. @@ -72,3 +73,5 @@ pub use dfraw_parser::*; /// - `write_json_string_vec_to_file`: Writes a vector of strings to a file, with each string on a separate line. /// - `raws_to_string`: Converts a vector of raw objects to a JSON string representation. pub mod util; + +pub use bindings::generate_bindings; diff --git a/jsonlib/tests/bindings.rs b/jsonlib/tests/bindings.rs index 039a6ff6..80926a8b 100644 --- a/jsonlib/tests/bindings.rs +++ b/jsonlib/tests/bindings.rs @@ -1,5 +1,4 @@ -use specta::TypeCollection; -use specta_typescript::{self, Typescript}; +use dfraw_json_parser::generate_bindings; #[test] #[allow(clippy::too_many_lines, clippy::cognitive_complexity)] @@ -12,86 +11,5 @@ fn generate_ts_bindings() { std::fs::create_dir_all(&output_dir).expect("Failed to create output directory"); eprintln!("Output dir: {:?}", &output_dir); - let exporter = Typescript::default().bigint(specta_typescript::BigIntExportBehavior::String); - let mut types = TypeCollection::default(); - types - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::() - .register::(); - - exporter - .export_to(output_dir.join("DFRawParser.d.ts"), &types) - .expect("Failed to export types"); + generate_bindings(&output_dir.join("DFRawParser.d.ts")).expect("Saving bindings failed"); } diff --git a/lib/Cargo.toml b/lib/Cargo.toml index ba2a10cc..f9ec8881 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "dfraw_parser" version = "0.18.0" -edition = "2021" +edition = "2024" readme = "README.md" authors = ["Nicholas Westerhausen "] description = "Library which parses Dwarf Fortress raw files into JSON" @@ -14,53 +14,43 @@ categories = ["parsing"] [lib] name = "dfraw_parser" path = "src/lib.rs" -crate-type = ["rlib", "cdylib"] +crate-type = ["rlib"] [dependencies] +dfraw_parser_proc_macros = {workspace = true} # directories used to find data directory and home directory on linux -directories = "6.0.0" +directories = { workspace = true } # Used to be able to force reading the raw files as Latin1. -encoding_rs = "0.8.34" +encoding_rs = { workspace = true } # Used to be able to force reading the raw files as Latin1 (has a BuffReader we use). -encoding_rs_io = "0.1" -itertools = "0.13.0" +encoding_rs_io = { workspace = true } +itertools = { workspace = true } # Generates static/reusabled regex for parsing the raw text files. -lazy-regex = "3.1.0" +lazy-regex = { workspace = true } # Used for making a shared static ref to the Latin1 encoding. -once_cell = "1.19.0" +once_cell = { workspace = true } # Quick-xml is used to parse legends export files. -quick-xml = "0.35.0" +quick-xml = { workspace = true } # We slugify the names of the raw files to make them easier to work with. -slug = "0.1.5" +slug = { workspace = true } # Create iterators from enums -strum = "0.27" -strum_macros = "0.27" +strum = { workspace = true } +strum_macros = { workspace = true } # Error type creation helper. -thiserror = "1" +thiserror = { workspace = true } # Tracing is used for logging. -tracing = "0.1.40" +tracing = { workspace = true } # Typetag is a helper for serializing traits -typetag = "0.2" +typetag = { workspace = true } # Walkdir used to go through the directories and files recursively. -walkdir = "2.5.0" +walkdir = { workspace = true } +# For serializing and deserializing +serde= { workspace = true, features = ["derive"] } +# For type binding generation +specta= { workspace = true, features = ["derive"] } +# For creating static hashmaps +phf= { workspace = true, features = ["macros"] } + # Windows: Registry access [target.'cfg(windows)'.dependencies] -winreg = "0.52" - -# Serde used to serialize and deserialize our parsed data. -[dependencies.serde] -version = "1.0" -default-features = true -features = ["derive"] - -# Our lookup tables for RAW_TOKENS to RawTokens are generated with phf. -[dependencies.phf] -version = "0.11.2" -default-features = true -features = ["macros"] - -# Specta is used for generating TypeScript bindings. And whatever bindings specta supports in the future. -[dependencies.specta] -version = "2.0.0-rc.22" -default-features = true -features = ["derive"] +winreg = "0.55" diff --git a/lib/src/default_checks.rs b/lib/src/default_checks.rs deleted file mode 100644 index 62b5c0f6..00000000 --- a/lib/src/default_checks.rs +++ /dev/null @@ -1,183 +0,0 @@ -/// This is only used for serialize -pub const fn is_default_frequency(frequency: Option) -> bool { - if let Some(frequency) = frequency { - return frequency == 50; - } - true -} -/// values[0] == 0 && values[1] == -1 -pub const fn is_default_trunk_height_percentage(values: &Option<[i32; 2]>) -> bool { - if let Some(values) = values { - return values[0] == 0 && values[1] == -1; - } - true -} -/// This is only used for serialize -pub const fn is_default_growth_density(density: Option) -> bool { - if let Some(density) = density { - return density == 0; - } - true -} -/// `values[0]` == `0` && `values[1]` == `403_200` -pub const fn is_default_growth_timing(values: &Option<[u32; 2]>) -> bool { - if let Some(values) = values { - return values[0] == 0 && values[1] == 403_200; - } - true -} -/// depth == 4 -pub const fn is_default_sapling_drown_level(depth: Option) -> bool { - if let Some(depth) = depth { - return depth == 4; - } - true -} -/// depth == 7 -pub const fn is_default_tree_drown_level(depth: Option) -> bool { - if let Some(depth) = depth { - return depth == 7; - } - true -} -/// depth == 4 -pub const fn is_default_shrub_drown_level(depth: Option) -> bool { - if let Some(depth) = depth { - return depth == 4; - } - true -} - -/// duration == 300 -pub const fn is_default_grow_duration(duration: Option) -> bool { - if let Some(duration) = duration { - return duration == 300; - } - true -} -/// size == 5 -pub const fn is_default_cluster_size(size: Option) -> bool { - if let Some(size) = size { - return size == 5; - } - true -} - -/// `tile_code` == 34 -pub const fn is_default_dead_shrub_tile(tile_code: Option) -> bool { - if let Some(tile_code) = tile_code { - return tile_code == 34; - } - true -} - -/// `tile_code` == 34 -pub const fn is_default_shrub_tile(tile_code: Option) -> bool { - if let Some(tile_code) = tile_code { - return tile_code == 34; - } - true -} - -/// `tile_code` == 169 -pub const fn is_default_dead_picked_tile(tile_code: Option) -> bool { - if let Some(tile_code) = tile_code { - return tile_code == 169; - } - true -} -/// `tile_code` == 231 -pub const fn is_default_picked_tile(tile_code: Option) -> bool { - if let Some(tile_code) = tile_code { - return tile_code == 231; - } - true -} - -// Checks based on value -/// ``min_max[0]`` == 1 && `min_max[1]` == 1 -pub const fn min_max_is_ones(min_max: &Option<[u32; 2]>) -> bool { - if let Some(min_max) = min_max { - return min_max[0] == 1 && min_max[1] == 1; - } - true -} -/// `min_max[0]` == 0 && `min_max[1]` == 0 -pub const fn min_max_is_zeroes(min_max: &Option<[u32; 2]>) -> bool { - if let Some(min_max) = min_max { - return min_max[0] == 0 && min_max[1] == 0; - } - true -} -/// This is only used for serialize -pub const fn is_zero(num: Option) -> bool { - if let Some(num) = num { - return num == 0; - } - true -} - -/// This is only used for serialize -pub const fn is_zero_i32(value: Option) -> bool { - if let Some(value) = value { - return value == 0; - } - true -} -/// This is only used for serialize -pub fn is_zero_f32(value: Option) -> bool { - if let Some(value) = value { - return value == 0.0; - } - true -} - -/// This is only used for serialize -pub const fn is_500_u32(value: Option) -> bool { - if let Some(value) = value { - return value == 500; - } - true -} -/// This is only used for serialize -pub const fn is_50_u32(value: Option) -> bool { - if let Some(value) = value { - return value == 50; - } - true -} -/// This is only used for serialize -pub const fn is_3_u32(value: Option) -> bool { - if let Some(value) = value { - return value == 3; - } - true -} -/// This is only used for serialize -pub const fn is_zero_u32(value: Option) -> bool { - if let Some(value) = value { - return value == 0; - } - true -} -/// This is only used for serialize -pub const fn is_zero_u8(value: Option) -> bool { - if let Some(value) = value { - return value == 0; - } - true -} -/// This is only used for serialize -pub const fn is_one_u32(value: Option) -> bool { - if let Some(value) = value { - return value == 1; - } - true -} -/// This is only used for serialize -pub const fn is_one_u8(value: Option) -> bool { - if let Some(value) = value { - return value == 1; - } - true -} diff --git a/lib/src/legends_export/reader.rs b/lib/src/legends_export/reader.rs index 3b5511c9..e552abda 100644 --- a/lib/src/legends_export/reader.rs +++ b/lib/src/legends_export/reader.rs @@ -114,7 +114,7 @@ pub fn parse_legends_export>( Ok(Event::Text(e)) => { if parent_tag != Parent::None { tag_txt = e - .unescape() + .decode() .map_or_else(|_| String::new(), std::borrow::Cow::into_owned); } match parent_tag { diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 2e27d15f..3bebb7a0 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,6 +1,5 @@ //! This library provides an API for parsing Dwarf Fortress raw files. -mod default_checks; mod error; mod parsed_definitions; mod parser; @@ -17,14 +16,14 @@ pub mod utilities; pub use error::Parser as ParserError; pub use parsed_definitions::custom_types; pub use parsed_definitions::*; +pub use parser::ParseResult; pub use parser::parse::parse; pub use parser::parse_location; pub use parser::parse_module; pub use parser::parse_module_info_file_in_module; pub use parser::parse_module_info_files; pub use parser::parse_module_info_files_at_location; -pub use parser::ParseResult; -pub use reader::parse_raw_file; pub use reader::FileParseResult; pub use reader::UnprocessedRaw; +pub use reader::parse_raw_file; pub use utilities::build_search_string; diff --git a/lib/src/metadata/location_helper.rs b/lib/src/metadata/location_helper.rs index 609d2870..ac96b4d8 100644 --- a/lib/src/metadata/location_helper.rs +++ b/lib/src/metadata/location_helper.rs @@ -2,10 +2,10 @@ use std::path::PathBuf; use crate::{ + ParserError, constants::DF_STEAM_APPID, metadata::RawModuleLocation, utilities::{find_game_path, find_user_data_path}, - ParserError, }; /// Helper struct for managing locations related to the game directory and user directory. @@ -27,28 +27,34 @@ impl LocationHelper { df_directory: None, user_data_directory: None, }; - helper.init(); + helper.init(true); helper } /// Get the game directory. - pub fn get_df_directory(&self) -> Option<&PathBuf> { - self.df_directory.as_ref() + #[must_use] + pub fn get_df_directory(&self) -> Option { + self.df_directory.clone() } /// Get the user directory. - pub fn get_user_data_directory(&self) -> Option<&PathBuf> { - self.user_data_directory.as_ref() + #[must_use] + pub fn get_user_data_directory(&self) -> Option { + self.user_data_directory.clone() } /// Initialize the game directory and user directory. /// /// This can be called at any time to update the game directory and user directory. - pub fn init(&mut self) { + pub fn init(&mut self, force: bool) { // Get app installation directory - self.df_directory = find_game_path(DF_STEAM_APPID); + if force || self.df_directory.is_none() { + self.df_directory = find_game_path(DF_STEAM_APPID); + } // Get user directory - self.user_data_directory = find_user_data_path(); + if force || self.user_data_directory.is_none() { + self.user_data_directory = find_user_data_path(); + } } /// Set the Dwarf Fortress user data directory explicitly @@ -73,7 +79,7 @@ impl LocationHelper { Err(e) => { return Err(ParserError::InvalidOptions(format!( "Unable to canonicalize Dwarf Fortress user data path!\n{path:?}\n{e:?}" - ))) + ))); } }; @@ -117,7 +123,7 @@ impl LocationHelper { Err(e) => { return Err(ParserError::InvalidOptions(format!( "Unable to canonicalize Dwarf Fortress path!\n{path:?}\n{e:?}" - ))) + ))); } }; @@ -149,7 +155,7 @@ impl LocationHelper { /// - `Option`: The path to the module, or `None` if the location is unknown. pub fn get_path_for_location(&self, location: RawModuleLocation) -> Option { match location { - RawModuleLocation::InstalledMods | RawModuleLocation::Mods => self + RawModuleLocation::InstalledMods | RawModuleLocation::WorkshopMods => self .user_data_directory .as_ref() .map(|dir| dir.join(location.get_path())), diff --git a/lib/src/metadata/mod.rs b/lib/src/metadata/mod.rs index 7373707d..74efec37 100644 --- a/lib/src/metadata/mod.rs +++ b/lib/src/metadata/mod.rs @@ -5,6 +5,7 @@ mod object_type; mod parser_options; mod raw_location; mod raw_metadata; +mod raw_object; mod token_complexity; pub use location_helper::LocationHelper; @@ -15,5 +16,6 @@ pub use raw_location::RawModuleLocation; #[allow(clippy::module_name_repetitions)] /// Metadata about the raw file pub use raw_metadata::Metadata as RawMetadata; +pub use raw_object::RawObject; /// The complexity of a raw object token pub use token_complexity::TagComplexity; diff --git a/lib/src/metadata/object_type.rs b/lib/src/metadata/object_type.rs index 03710359..06d2a955 100644 --- a/lib/src/metadata/object_type.rs +++ b/lib/src/metadata/object_type.rs @@ -2,6 +2,8 @@ use std::fmt::{Debug, Display}; use serde::{Deserialize, Serialize}; +use crate::traits::IsEmpty; + /// A map of the object tokens to their respective object types. pub static OBJECT_TOKEN_MAP: phf::Map<&'static str, ObjectType> = phf::phf_map! { "CREATURE" => ObjectType::Creature, @@ -61,92 +63,92 @@ pub static OBJECT_TOKEN_MAP: phf::Map<&'static str, ObjectType> = phf::phf_map! )] pub enum ObjectType { /// A creature - Creature, + Creature = 1, /// An inorganic material - Inorganic, + Inorganic = 2, /// A plant - Plant, + Plant = 3, /// An item - Item, + Item = 4, /// An item of type ammo - ItemAmmo, + ItemAmmo = 5, /// An item of type armor - ItemArmor, + ItemArmor = 6, /// An item of type food - ItemFood, + ItemFood = 7, /// An item of type gloves - ItemGloves, + ItemGloves = 8, /// An item of type helm - ItemHelm, + ItemHelm = 9, /// An item of type instrument - ItemInstrument, + ItemInstrument = 10, /// An item of type pants - ItemPants, + ItemPants = 11, /// An item of type shield - ItemShield, + ItemShield = 12, /// An item of type shoes - ItemShoes, + ItemShoes = 13, /// An item of type siege ammo - ItemSiegeAmmo, + ItemSiegeAmmo = 14, /// An item of type tool - ItemTool, + ItemTool = 15, /// An item of type toy - ItemToy, + ItemToy = 16, /// An item of type trap component - ItemTrapComponent, + ItemTrapComponent = 17, /// An item of type weapon - ItemWeapon, + ItemWeapon = 18, /// A building - Building, + Building = 19, /// A workshop building - BuildingWorkshop, + BuildingWorkshop = 20, /// A furnace building - BuildingFurnace, + BuildingFurnace = 21, /// A reaction - Reaction, + Reaction = 22, /// Graphics - Graphics, + Graphics = 23, /// A material template - MaterialTemplate, + MaterialTemplate = 24, /// A body detail plan - BodyDetailPlan, + BodyDetailPlan = 25, /// A body - Body, + Body = 26, /// An entity - Entity, + Entity = 27, /// A language - Language, + Language = 28, /// A translation - Translation, + Translation = 29, /// A tissue template - TissueTemplate, + TissueTemplate = 30, /// A creature variation - CreatureVariation, + CreatureVariation = 31, /// A text set - TextSet, + TextSet = 32, /// A tile page - TilePage, + TilePage = 33, /// A descriptor color - DescriptorColor, + DescriptorColor = 34, /// A descriptor pattern - DescriptorPattern, + DescriptorPattern = 35, /// A descriptor shape - DescriptorShape, + DescriptorShape = 36, /// A palette - Palette, + Palette = 37, /// Music - Music, + Music = 38, /// Sound - Sound, + Sound = 39, /// An interaction - Interaction, + Interaction = 40, /// An unknown object type #[default] - Unknown, + Unknown = 99, /// `SelectCreature` tag - SelectCreature, + SelectCreature = 41, /// A creature caste - CreatureCaste, + CreatureCaste = 42, } impl Display for ObjectType { @@ -198,3 +200,9 @@ impl Display for ObjectType { } } } + +impl IsEmpty for ObjectType { + fn is_empty(&self) -> bool { + self == &ObjectType::Unknown + } +} diff --git a/lib/src/metadata/parser_options.rs b/lib/src/metadata/parser_options.rs index ef03d4ce..54e2fa73 100644 --- a/lib/src/metadata/parser_options.rs +++ b/lib/src/metadata/parser_options.rs @@ -1,8 +1,8 @@ use std::path::{Path, PathBuf}; -use crate::metadata::LocationHelper; +use strum::IntoEnumIterator; -use super::{ObjectType, RawModuleLocation}; +use crate::metadata::{LocationHelper, ObjectType, RawModuleLocation}; /// # Parsing Options /// @@ -135,22 +135,14 @@ pub struct ParserOptions { impl Default for ParserOptions { fn default() -> Self { + let all_object_types = ObjectType::iter().collect(); Self { attach_metadata_to_raws: false, skip_apply_copy_tags_from: false, skip_apply_creature_variations: false, include_warnings_for_info_file_format: false, log_summary: false, - object_types_to_parse: vec![ - ObjectType::Creature, - ObjectType::CreatureVariation, - ObjectType::Entity, - ObjectType::Plant, - ObjectType::Inorganic, - ObjectType::MaterialTemplate, - ObjectType::Graphics, - ObjectType::TilePage, - ], + object_types_to_parse: all_object_types, locations_to_parse: vec![], locations: LocationHelper::new(), legends_exports_to_parse: Vec::new(), diff --git a/lib/src/metadata/raw_location.rs b/lib/src/metadata/raw_location.rs index ce9579b0..3542edf1 100644 --- a/lib/src/metadata/raw_location.rs +++ b/lib/src/metadata/raw_location.rs @@ -14,6 +14,8 @@ use std::{ use serde::{Deserialize, Serialize}; use tracing::warn; +use crate::traits::IsEmpty; + /// Raws are part of modules since 50.xx. Raw modules are loaded from 3 common locations: /// `{df_directory}/data/vanilla`, `{df_directory}/mods`, and `{df_directory/data/installed_mods}` #[derive( @@ -21,14 +23,14 @@ use tracing::warn; )] pub enum RawModuleLocation { /// The "installed" mods directory - InstalledMods, + InstalledMods = 3, /// The "downloaded" mods directory - Mods, + WorkshopMods = 2, /// The vanilla data file location - Vanilla, + Vanilla = 1, /// An unknown location #[default] - Unknown, + Unknown = 4, /// Used for handling legends exported files LegendsExport, } @@ -42,7 +44,7 @@ impl RawModuleLocation { #[must_use] pub fn get_path(self) -> PathBuf { match self { - Self::Mods => PathBuf::from("mods"), + Self::WorkshopMods => PathBuf::from("mods"), Self::InstalledMods => ["data", "installed_mods"].iter().collect(), Self::Vanilla => ["data", "vanilla"].iter().collect(), Self::Unknown => PathBuf::from("unknown"), @@ -62,7 +64,7 @@ impl RawModuleLocation { pub fn from_path>(path: &P) -> Self { for component in path.as_ref().iter().rev() { match component.to_string_lossy().as_ref() { - "mods" => return Self::Mods, + "mods" => return Self::WorkshopMods, "installed_mods" => return Self::InstalledMods, "vanilla" => return Self::Vanilla, _ => continue, // Not a match, keep checking the next part @@ -83,3 +85,30 @@ impl Display for RawModuleLocation { Debug::fmt(self, f) } } + +impl From for i32 { + fn from(value: RawModuleLocation) -> Self { + match value { + RawModuleLocation::InstalledMods => 3, + RawModuleLocation::WorkshopMods => 2, + RawModuleLocation::Vanilla => 1, + RawModuleLocation::Unknown | RawModuleLocation::LegendsExport => 4, + } + } +} +impl From<&RawModuleLocation> for i32 { + fn from(value: &RawModuleLocation) -> Self { + match *value { + RawModuleLocation::InstalledMods => 3, + RawModuleLocation::WorkshopMods => 2, + RawModuleLocation::Vanilla => 1, + RawModuleLocation::Unknown | RawModuleLocation::LegendsExport => 4, + } + } +} + +impl IsEmpty for RawModuleLocation { + fn is_empty(&self) -> bool { + self == &RawModuleLocation::Unknown + } +} diff --git a/lib/src/metadata/raw_metadata.rs b/lib/src/metadata/raw_metadata.rs index a4661e8f..e58f17f8 100644 --- a/lib/src/metadata/raw_metadata.rs +++ b/lib/src/metadata/raw_metadata.rs @@ -1,5 +1,6 @@ use std::path::Path; +use dfraw_parser_proc_macros::{Cleanable, IsEmpty}; use serde::{Deserialize, Serialize}; use crate::InfoFile; @@ -29,7 +30,9 @@ use super::{ObjectType, RawModuleLocation}; /// * `hidden`: The `hidden` property is a boolean value that indicates whether the raw metadata should /// be hidden or not when exporting. By default, it is set to `true`, meaning that the raw metadata will /// be hidden unless specified in the `ParsingOptions` struct. -#[derive(Serialize, Deserialize, Clone, Debug, Default, specta::Type)] +#[derive( + Serialize, Deserialize, Clone, Debug, Default, specta::Type, PartialEq, Eq, IsEmpty, Cleanable, +)] #[serde(rename_all = "camelCase")] pub struct Metadata { // The object_id of the raw module @@ -147,8 +150,8 @@ impl Metadata { /// /// * `RawModuleLocation` - The location of the owning raw module #[must_use] - pub const fn get_location(&self) -> &RawModuleLocation { - &self.raw_module_location + pub const fn get_location(&self) -> RawModuleLocation { + self.raw_module_location } /// Get the `object_id` of the owning raw module. /// diff --git a/lib/src/metadata/raw_object.rs b/lib/src/metadata/raw_object.rs new file mode 100644 index 00000000..2627f412 --- /dev/null +++ b/lib/src/metadata/raw_object.rs @@ -0,0 +1,15 @@ +//! A definition purely for consumption in a tauri or JSON app, when dealing +//! with the JSON versions of objects that have the RawObject trait. + +use crate::metadata::RawMetadata; +use serde::{Deserialize, Serialize}; + +/// A struct purely for providing type hinting when working with parsed raws (as JSON) in typescript. +#[derive(Serialize, Deserialize, Clone, Debug, Default, specta::Type)] +#[serde(rename_all = "camelCase")] +pub struct RawObject { + /// The object identifier + identifier: String, + /// The metadata for this raw (includes the `ObjectType`, `RawModuleLocation` and other module info) + metadata: RawMetadata, +} diff --git a/lib/src/parsed_definitions/body_size.rs b/lib/src/parsed_definitions/body_size.rs index 51c890b9..8a23b013 100644 --- a/lib/src/parsed_definitions/body_size.rs +++ b/lib/src/parsed_definitions/body_size.rs @@ -1,7 +1,9 @@ -//! A module containing the `BodySize` struct and its implementation. +//! A module containing the `[BodySize]` struct and its implementation. -/// A struct representing a body size in the format `years:days:size_cm3` -#[allow(clippy::module_name_repetitions)] +/// Represents a creature's body size at a specific age. +/// +/// This structure is used to define growth stages for creatures in Dwarf Fortress raw files. +/// It corresponds to the `[BODY_SIZE:YEARS:DAYS:SIZE_CM3]` tag. #[derive( serde::Serialize, serde::Deserialize, Debug, Clone, Default, PartialEq, Eq, specta::Type, )] @@ -13,15 +15,21 @@ pub struct BodySize { } impl BodySize { - /// Creates a new `BodySize` struct with the given years, days, and `size_cm3` + /// Parses a raw body size value string into a [`BodySize`] struct. /// - /// # Arguments + /// * `value` - A string slice in the format `years:days:size_cm3`. /// - /// * `value` - The value to parse into a `BodySize` struct (e.g. `1:2:3`) + /// Returns a new instance of [`BodySize`]. /// - /// # Returns + /// The string is split by the colon delimiter. If any component fails to parse as a `u32` + /// or if the string does not contain exactly three parts, the respective fields default to 0. /// - /// * The `BodySize` struct + /// # Examples + /// + /// ``` + /// use dfraw_parser::BodySize; + /// let size = BodySize::from_value("1:150:5000"); + /// ``` #[must_use] pub fn from_value(value: &str) -> Self { let split = value.split(':').collect::>(); @@ -50,3 +58,24 @@ impl BodySize { self.size_cm3 } } + +impl std::fmt::Display for BodySize { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let year_ending = if self.years != 1 { "s" } else { "" }; + let day_ending = if self.days != 1 { "s" } else { "" }; + + match (self.years, self.days) { + (_, 0) => write!( + f, + "{}cm³ at {} year{}", + self.size_cm3, self.years, year_ending + ), + (0, _) => write!(f, "{}cm³ at {} day{}", self.size_cm3, self.days, day_ending), + _ => write!( + f, + "{}cm³ at {} year{}, {} day{}", + self.size_cm3, self.years, year_ending, self.days, day_ending + ), + } + } +} diff --git a/lib/src/parsed_definitions/caste.rs b/lib/src/parsed_definitions/caste.rs index a6f5b6aa..eb970a5a 100644 --- a/lib/src/parsed_definitions/caste.rs +++ b/lib/src/parsed_definitions/caste.rs @@ -1,89 +1,128 @@ //! A module for the Caste struct and its implementations. +use dfraw_parser_proc_macros::{Cleanable, IsEmpty}; use tracing::warn; use crate::{ body_size::BodySize, - creature::Creature, - default_checks, gait::Gait, milkable::Milkable, name::Name, raw_definitions::CASTE_TOKENS, tags::CasteTag, tile::Tile, - traits::{searchable::Searchable, RawObjectToken, TagOperations}, + traits::{IsEmpty, Searchable, TagOperations}, }; /// A struct representing a creature caste. +/// +/// Castes are specific subgroups within a creature species, often representing +/// biological sexes, specialized roles, or unique variations specified in the raw files. #[allow(clippy::module_name_repetitions)] -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + Eq, + PartialEq, + IsEmpty, + Cleanable, +)] #[serde(rename_all = "camelCase")] pub struct Caste { + /// The unique name used in raw files for this caste (e.g., "MALE", "FEMALE"). identifier: String, - #[serde(skip_serializing_if = "Option::is_none")] + /// A collection of tags assigned to this caste. + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[cleanable(ignore)] tags: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + /// Flavor text shown in-game when examining a creature of this caste. + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] description: Option, - // String Tokens - #[serde(skip_serializing_if = "Option::is_none")] + /// The specific name for a creature in its baby stage. + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[cleanable(recursive)] baby_name: Option, - #[serde(skip_serializing_if = "Option::is_none")] + /// The name used specifically for this caste. + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[cleanable(recursive)] caste_name: Option, - #[serde(skip_serializing_if = "Option::is_none")] + /// The name for a creature in its child stage. + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[cleanable(recursive)] child_name: Option, - // [min, max] ranges - /// Default \[0,0\] - #[serde(skip_serializing_if = "Option::is_none")] + /// The range of eggs produced per clutch, measured as `[min, max]`. + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] clutch_size: Option<[u32; 2]>, - /// Default \[0,0\] - #[serde(skip_serializing_if = "Option::is_none")] + /// The range of offspring produced per birth, measured as `[min, max]`. + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] litter_size: Option<[u32; 2]>, - /// Default \[0,0\] - #[serde(skip_serializing_if = "Option::is_none")] + /// The range of life expectancy in game ticks, measured as `[min, max]`. + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] max_age: Option<[u32; 2]>, - // Integer tokens - #[serde(skip_serializing_if = "Option::is_none")] + /// The age in game ticks at which a creature ceases to be a baby. + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] baby: Option, - #[serde(skip_serializing_if = "Option::is_none")] + /// The age in game ticks at which a creature ceases to be a child. + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] child: Option, - #[serde(skip_serializing_if = "Option::is_none")] + /// A rating used to determine the challenge level of the creature. + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] difficulty: Option, - #[serde(skip_serializing_if = "Option::is_none")] + /// The size of eggs laid by this caste, measured in cubic centimeters. + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] egg_size: Option, - #[serde(skip_serializing_if = "Option::is_none")] + /// The distance or frequency at which this creature tramples grass. + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] grass_trample: Option, - #[serde(skip_serializing_if = "Option::is_none")] + /// The grazing requirement for the creature to survive. + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] grazer: Option, - #[serde(skip_serializing_if = "Option::is_none")] + /// The level of vision the creature has in dark environments. + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] low_light_vision: Option, - #[serde(skip_serializing_if = "Option::is_none")] + /// The value assigned to the creature when kept as a pet. + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] pet_value: Option, - #[serde(skip_serializing_if = "Option::is_none")] + /// The relative frequency this caste appears in wild populations. + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] pop_ratio: Option, - #[serde(skip_serializing_if = "Option::is_none")] + /// The percentage change applied to the base body size. + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] change_body_size_percentage: Option, - // Arrays - #[serde(skip_serializing_if = "Option::is_none")] + /// The classes or categories this caste belongs to for targeting. + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] creature_class: Option>, - // Special Tokens - #[serde(skip_serializing_if = "Option::is_none")] + /// Growth stages and volume measurements. + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] body_size: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + /// Material and frequency information for milking. + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] milkable: Option, - #[serde(skip_serializing_if = "Option::is_none")] + /// Character and color data for map representation. + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] tile: Option, /// The gaits by which the creature can move. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] gaits: Option>, } impl Caste { - /// Function to create a new Caste. + /// Creates a new [`Caste`] with the specified identifier. /// - /// # Arguments + /// * `identifier` - The unique name used in raw files for this caste (e.g., "MALE", "FEMALE"). /// - /// * `name` - The name of the caste. + /// Returns a default [`Caste`] instance with the provided identifier. + /// + /// # Examples + /// + /// ``` + /// use dfraw_parser::Caste; + /// let caste = Caste::new("MALE"); + /// ``` #[must_use] pub fn new(name: &str) -> Self { Self { @@ -91,6 +130,159 @@ impl Caste { ..Self::default() } } + + /// Returns the age at which creatures of this caste are considered babies. + /// + /// This value is specified in ticks (game time units). + #[must_use] + pub fn get_baby_age(&self) -> Option { + self.baby + } + + /// Returns the name of the creature when it is in its baby stage. + /// + /// This value is specified in the raw file using the `[BABY_NAME]` tag. + #[must_use] + pub fn get_baby_name(&self) -> Option<&Name> { + self.baby_name.as_ref() + } + + /// Returns the body size measurements for this caste at different ages. + /// + /// Measured in cubic centimeters. This list represents the growth stages + /// specified by `[BODY_SIZE]` tags in the raw files. + #[must_use] + pub fn get_body_sizes(&self) -> &[BodySize] { + self.body_size.as_deref().unwrap_or(&[]) + } + + /// Returns the specific name for this caste. + /// + /// This value is specified in the raw file using the `[CASTE_NAME]` tag. + #[must_use] + pub fn get_caste_name(&self) -> Option<&Name> { + self.caste_name.as_ref() + } + + /// Returns the age at which creatures of this caste are considered children. + /// + /// This value is specified in ticks (game time units). + #[must_use] + pub fn get_child_age(&self) -> Option { + self.child + } + + /// Returns the name of the creature when it is in its child stage. + /// + /// This value is specified in the raw file using the `[CHILD_NAME]` tag. + #[must_use] + pub fn get_child_name(&self) -> Option<&Name> { + self.child_name.as_ref() + } + + /// Returns the clutch size range for this caste, if it lays eggs. + /// + /// Returns a tuple of `[min, max]` eggs per clutch. + #[must_use] + pub fn get_clutch_size(&self) -> Option<[u32; 2]> { + self.clutch_size + } + + /// Returns a slice of creature classes this caste belongs to. + /// + /// Creature classes are used for targeting by interactions, syndromes, and other effects. + #[must_use] + pub fn get_creature_classes(&self) -> &[String] { + self.creature_class.as_deref().unwrap_or(&[]) + } + + /// Returns the difficulty rating for this caste. + /// + /// Higher values indicate more challenging creatures in arena mode or similar contexts. + #[must_use] + pub fn get_difficulty(&self) -> Option { + self.difficulty + } + + /// Returns the description of this caste, if available. + /// + /// The description is the flavor text shown in-game when examining a creature of this caste. + #[must_use] + pub fn get_description(&self) -> Option<&str> { + self.description.as_deref() + } + + /// Returns the size of eggs laid by this caste, if applicable. + /// + /// Measured in cubic centimeters (cm³). + #[must_use] + pub fn get_egg_size(&self) -> Option { + self.egg_size + } + + /// Returns a slice of gaits (movement modes) available to this caste. + /// + /// Examples include walking, crawling, flying, and swimming. + #[must_use] + pub fn get_gaits(&self) -> &[Gait] { + self.gaits.as_deref().unwrap_or(&[]) + } + + /// Returns the unique identifier of this caste. + /// + /// The identifier is the unique name used in raw files to distinguish this caste + /// from others within the same creature definition. + #[must_use] + pub fn get_identifier(&self) -> &str { + &self.identifier + } + + /// Returns the litter size range for this caste, if it gives live birth. + /// + /// Returns a tuple of `[min, max]` offspring per litter. + #[must_use] + pub fn get_litter_size(&self) -> Option<[u32; 2]> { + self.litter_size + } + + /// Returns the maximum age range for this caste. + /// + /// Returns a tuple of `[min, max]` age in game ticks. Creatures die of old age + /// within this range. + #[must_use] + pub fn get_max_age(&self) -> Option<[u32; 2]> { + self.max_age + } + + /// Returns the milkable properties of this caste, if applicable. + /// + /// This includes the material produced and the frequency at which the + /// creature can be milked, defined by the `[MILKABLE]` tag. + #[must_use] + pub fn get_milkable(&self) -> Milkable { + self.milkable + .as_ref() + .map_or_else(Milkable::default, std::clone::Clone::clone) + } + + /// Returns the pet value of this caste, if specified. + /// + /// The pet value affects how desirable this creature is as a pet and influences + /// its trade value. + #[must_use] + pub fn get_pet_value(&self) -> Option { + self.pet_value + } + + /// Returns the population ratio for this caste. + /// + /// This determines the relative frequency of this caste in wild populations. + /// For example, a pop_ratio of 50 means this caste appears 50% of the time. + #[must_use] + pub fn get_pop_ratio(&self) -> Option { + self.pop_ratio + } + /// Function to get the tags of the creature caste. /// /// # Returns @@ -100,23 +292,57 @@ impl Caste { pub fn get_tags(&self) -> &[CasteTag] { self.tags.as_ref().map_or(&[], |tags| tags.as_slice()) } - /// Function to get the milkable of the creature caste. + + /// Returns the tiles used to represent this caste in-game. /// - /// # Returns + /// Includes graphical or character-based representations for different display modes. + #[must_use] + pub fn get_tile(&self) -> Option<&Tile> { + self.tile.as_ref() + } + + /// Returns true if the caste has the given tag, ignoring tag values. + /// + /// * `tag` - The [`CasteTag`] to check for. /// - /// * `Milkable` - The milkable of the creature caste. + /// This check uses the variant discriminant to match tags regardless of internal data. #[must_use] - pub fn get_milkable(&self) -> Milkable { - self.milkable - .as_ref() - .map_or_else(Milkable::default, std::clone::Clone::clone) + pub fn has_tag(&self, tag: &CasteTag) -> bool { + if let Some(tags) = &self.tags { + for t in tags { + if std::mem::discriminant(t) == std::mem::discriminant(tag) { + return true; + } + } + } + false + } + + /// Returns true if the caste is an egg layer. + /// + /// Checks for the presence of the `[LAYS_EGGS]` tag via [`CasteTag::LaysEggs`]. + #[must_use] + pub fn is_egg_layer(&self) -> bool { + self.has_tag(&CasteTag::LaysEggs) + } + + /// Returns true if the caste is milkable. + /// + /// Checks for the presence of the `[MILKABLE]` tag via [`CasteTag::Milkable`]. + #[must_use] + pub fn is_milkable(&self) -> bool { + self.has_tag(&CasteTag::Milkable { + material: Vec::new(), + frequency: 0, + }) } - /// Parse a tag and value into the creature caste + + /// Parses a tag key and value and updates the caste state. /// - /// # Arguments + /// * `key` - The key of the tag to parse (e.g., "NAME"). + /// * `value` - The string value associated with the tag. /// - /// * `key` - The key of the tag to parse. - /// * `value` - The value of the tag to parse. + /// This method maps raw file tokens directly to internal struct fields. #[allow(clippy::too_many_lines)] pub fn parse_tag(&mut self, key: &str, value: &str) { let Some(tag) = CasteTag::parse(key, value) else { @@ -213,89 +439,13 @@ impl Caste { _ => {} } } - /// Function to get the identifier of the creature. - /// - /// # Returns - /// - /// * `&str` - The identifier of the creature. - #[must_use] - pub fn get_identifier(&self) -> &str { - &self.identifier - } - #[must_use] - pub fn get_description(&self) -> Option<&str> { - self.description.as_deref() - } - #[must_use] - pub fn get_pet_value(&self) -> Option { - self.pet_value - } - #[must_use] - pub fn get_baby_age(&self) -> Option { - self.baby - } - #[must_use] - pub fn get_child_age(&self) -> Option { - self.child - } - #[must_use] - pub fn get_difficulty(&self) -> Option { - self.difficulty - } - #[must_use] - pub fn get_egg_size(&self) -> Option { - self.egg_size - } - #[must_use] - pub fn get_pop_ratio(&self) -> Option { - self.pop_ratio - } - #[must_use] - pub fn get_clutch_size(&self) -> Option<[u32; 2]> { - self.clutch_size - } - #[must_use] - pub fn get_litter_size(&self) -> Option<[u32; 2]> { - self.litter_size - } - #[must_use] - pub fn get_max_age(&self) -> Option<[u32; 2]> { - self.max_age - } - #[must_use] - pub fn get_baby_name(&self) -> Option<&Name> { - self.baby_name.as_ref() - } - #[must_use] - pub fn get_child_name(&self) -> Option<&Name> { - self.child_name.as_ref() - } - #[must_use] - pub fn get_caste_name(&self) -> Option<&Name> { - self.caste_name.as_ref() - } - #[must_use] - pub fn get_tile(&self) -> Option<&Tile> { - self.tile.as_ref() - } - #[must_use] - pub fn get_body_sizes(&self) -> &[BodySize] { - self.body_size.as_deref().unwrap_or(&[]) - } - #[must_use] - pub fn get_creature_classes(&self) -> &[String] { - self.creature_class.as_deref().unwrap_or(&[]) - } - #[must_use] - pub fn get_gaits(&self) -> &[Gait] { - self.gaits.as_deref().unwrap_or(&[]) - } - /// Function to remove a tag from the creature. - /// - /// # Arguments + + /// Removes a specific tag and its associated value from the caste. /// /// * `key` - The key of the tag to remove. - /// * `value` - The value of the tag to remove. + /// * `value` - The value of the tag to remove (relevant for multi-value tags like `GAIT`). + /// + /// This is used when a creature variation or selection rule negates an existing definition. #[allow(clippy::too_many_lines)] pub fn remove_tag_and_value(&mut self, key: &str, value: &str) { let Some(tag) = CASTE_TOKENS.get(key) else { @@ -356,15 +506,13 @@ impl Caste { tags.retain(|t| t != tag); } } - /// Overwrites the values of self with the values of other. - /// - /// # Arguments - /// - /// * `other` - The other caste to overwrite self with. + + /// Overwrites the properties of this caste with non-default values from another. /// - /// # Notes + /// * `other` - The source [`Caste`] to copy values from. /// - /// This function will overwrite any values in self with the values from other. If a value is default in other, it will not be overwritten. + /// Any field that is considered "default" (e.g., zero or empty) in the `other` + /// caste will not overwrite the current value. #[allow(clippy::cognitive_complexity)] pub fn overwrite_caste(&mut self, other: &Self) { // Include any tags from other that aren't in self @@ -375,262 +523,82 @@ impl Caste { } } } - // For any of the other's values that are not default, overwrite self's values - if let Some(other_description) = &other.description { - if !other_description.is_empty() { - self.description = Some(other_description.clone()); - } + + // For any of the other's values that are not "empty", overwrite self's values. + // Note: !IsEmpty::is_empty(&Option) returns true only if the Option is Some + // AND the inner value is not empty (e.g. not "", not 0, not [0,0]). + + if !other.description.is_empty() { + self.description = other.description.clone(); } - if let Some(other_baby_name) = &other.baby_name { - if !other_baby_name.is_empty() { - self.baby_name = Some(other_baby_name.clone()); - } + if !other.baby_name.is_empty() { + self.baby_name = other.baby_name.clone(); } - if let Some(other_caste_name) = &other.caste_name { - if !other_caste_name.is_empty() { - self.caste_name = Some(other_caste_name.clone()); - } + if !other.caste_name.is_empty() { + self.caste_name = other.caste_name.clone(); } - if let Some(other_child_name) = &other.child_name { - if !other_child_name.is_empty() { - self.child_name = Some(other_child_name.clone()); - } + if !other.child_name.is_empty() { + self.child_name = other.child_name.clone(); } - if !default_checks::min_max_is_zeroes(&other.clutch_size) { + + if !other.clutch_size.is_empty() { self.clutch_size = other.clutch_size; } - if !default_checks::min_max_is_zeroes(&other.litter_size) { + if !other.litter_size.is_empty() { self.litter_size = other.litter_size; } - if !default_checks::min_max_is_zeroes(&other.max_age) { + if !other.max_age.is_empty() { self.max_age = other.max_age; } - if !default_checks::is_zero(other.baby) { + + if !other.baby.is_empty() { self.baby = other.baby; } - if !default_checks::is_zero(other.child) { + if !other.child.is_empty() { self.child = other.child; } - if !default_checks::is_zero(other.difficulty) { + if !other.difficulty.is_empty() { self.difficulty = other.difficulty; } - if !default_checks::is_zero(other.egg_size) { + if !other.egg_size.is_empty() { self.egg_size = other.egg_size; } - if !default_checks::is_zero(other.grass_trample) { + if !other.grass_trample.is_empty() { self.grass_trample = other.grass_trample; } - if !default_checks::is_zero(other.grazer) { + if !other.grazer.is_empty() { self.grazer = other.grazer; } - if !default_checks::is_zero(other.low_light_vision) { + if !other.low_light_vision.is_empty() { self.low_light_vision = other.low_light_vision; } - if !default_checks::is_zero(other.pet_value) { + if !other.pet_value.is_empty() { self.pet_value = other.pet_value; } - if !default_checks::is_zero(other.pop_ratio) { + if !other.pop_ratio.is_empty() { self.pop_ratio = other.pop_ratio; } - if !default_checks::is_zero(other.change_body_size_percentage) { + if !other.change_body_size_percentage.is_empty() { self.change_body_size_percentage = other.change_body_size_percentage; } - if let Some(other_creature_class) = &other.creature_class { - if !other_creature_class.is_empty() { - self.creature_class = Some(other_creature_class.clone()); - } - } - if let Some(other_body_size) = &other.body_size { - if !other_body_size.is_empty() { - self.body_size = Some(other_body_size.clone()); - } - } - if let Some(other_milkable) = &other.milkable { - if !other_milkable.is_default() { - self.milkable = Some(other_milkable.clone()); - } - } - if let Some(other_tile) = &other.tile { - if !other_tile.is_default() { - self.tile = Some(other_tile.clone()); - } - } - } - /// Returns true if the caste is an egg layer. - /// - /// # Returns - /// - /// True if the caste is an egg layer, false otherwise. - #[must_use] - pub fn is_egg_layer(&self) -> bool { - self.has_tag(&CasteTag::LaysEggs) - } - /// Returns true if the caste is milkable. - /// - /// # Returns - /// - /// True if the caste is milkable, false otherwise. - #[must_use] - pub fn is_milkable(&self) -> bool { - self.has_tag(&CasteTag::Milkable { - material: Vec::new(), - frequency: 0, - }) - } - /// Returns true if the caste has the given tag, no values are checked. - /// - /// ## Arguments - /// - /// * `tag` - The tag to check for (note that any values are ignored) - /// - /// ## Returns - /// - /// True if the caste has the given tag, false otherwise. - #[must_use] - pub fn has_tag(&self, tag: &CasteTag) -> bool { - if let Some(tags) = &self.tags { - for t in tags { - if std::mem::discriminant(t) == std::mem::discriminant(tag) { - return true; - } - } - } - false - } - - /// Function to "clean" the creature. This is used to remove any empty list or strings, - /// and to remove any default values. By "removing" it means setting the value to None. - /// - /// This also will remove the metadata if `is_metadata_hidden` is true. - /// - /// Steps: - /// - Set any metadata to None if `is_metadata_hidden` is true. - /// - Set any empty string to None. - /// - Set any empty list to None. - /// - Set any default values to None. - #[must_use] - #[allow(clippy::cognitive_complexity)] - pub fn cleaned(&self) -> Self { - let mut cleaned = self.clone(); - - // Set any empty string to None. - if cleaned.description.is_some() && cleaned.description.as_deref() == Some("") { - cleaned.description = None; - } - - // Set any empty list to None. - if cleaned.creature_class.is_some() && cleaned.creature_class.as_deref() == Some(&[]) { - cleaned.creature_class = None; - } - - // Set any empty list to None. - if cleaned.body_size.is_some() && cleaned.body_size.as_deref() == Some(&[]) { - cleaned.body_size = None; - } - - // Set any default values to None. - if default_checks::is_zero(cleaned.baby) { - cleaned.baby = None; - } - // Set any default values to None. - if default_checks::is_zero(cleaned.child) { - cleaned.child = None; + if !other.creature_class.is_empty() { + self.creature_class = other.creature_class.clone(); } - - // Set any default values to None. - if default_checks::is_zero(cleaned.difficulty) { - cleaned.difficulty = None; - } - - // Set any default values to None. - if default_checks::is_zero(cleaned.egg_size) { - cleaned.egg_size = None; - } - - // Set any default values to None. - if default_checks::is_zero(cleaned.grass_trample) { - cleaned.grass_trample = None; - } - - // Set any default values to None. - if default_checks::is_zero(cleaned.grazer) { - cleaned.grazer = None; - } - - // Set any default values to None. - if default_checks::is_zero(cleaned.low_light_vision) { - cleaned.low_light_vision = None; - } - - // Set any default values to None. - if default_checks::is_zero(cleaned.pet_value) { - cleaned.pet_value = None; - } - - // Set any default values to None. - if default_checks::is_zero(cleaned.pop_ratio) { - cleaned.pop_ratio = None; - } - - // Set any default values to None. - if default_checks::is_zero(cleaned.change_body_size_percentage) { - cleaned.change_body_size_percentage = None; - } - - // Set any default values to None. - if default_checks::min_max_is_zeroes(&cleaned.clutch_size) { - cleaned.clutch_size = None; - } - - // Set any default values to None. - if default_checks::min_max_is_zeroes(&cleaned.litter_size) { - cleaned.litter_size = None; - } - - // Set any default values to None. - if default_checks::min_max_is_zeroes(&cleaned.max_age) { - cleaned.max_age = None; + if !other.body_size.is_empty() { + self.body_size = other.body_size.clone(); } - - // Set any default values to None. - if let Some(baby_name) = cleaned.baby_name.clone() { - if baby_name.is_empty() { - cleaned.baby_name = None; - } + if !other.milkable.is_empty() { + self.milkable = other.milkable.clone(); } - - // Set any default values to None. - if let Some(caste_name) = cleaned.caste_name.clone() { - if caste_name.is_empty() { - cleaned.caste_name = None; - } + if !other.tile.is_empty() { + self.tile = other.tile.clone(); } - - // Set any default values to None. - if let Some(child_name) = cleaned.child_name.clone() { - if child_name.is_empty() { - cleaned.child_name = None; - } - } - - // Set any default values to None. - if let Some(milkable) = cleaned.milkable.clone() { - if milkable.is_default() { - cleaned.milkable = None; - } - } - - // Set any default values to None. - if let Some(tile) = cleaned.tile.clone() { - if tile.is_default() { - cleaned.tile = None; - } - } - - cleaned } + /// Adds a tag to the internal collection if it is not already present. + /// + /// * `tag` - The [`CasteTag`] to add. fn add_tag(&mut self, tag: CasteTag) { if let Some(tags) = self.tags.as_mut() { if !tags.contains(&tag) { @@ -720,15 +688,3 @@ impl Searchable for Caste { vec } } - -#[typetag::serialize] -impl RawObjectToken for CasteTag { - fn is_within(&self, object: &Creature) -> bool { - for caste in object.get_castes() { - if caste.get_tags().contains(self) { - return true; - } - } - false - } -} diff --git a/lib/src/parsed_definitions/color.rs b/lib/src/parsed_definitions/color.rs index 4c054b98..75e85c7e 100644 --- a/lib/src/parsed_definitions/color.rs +++ b/lib/src/parsed_definitions/color.rs @@ -1,26 +1,50 @@ //! A module containing the `Color` struct and its implementations. -/// A struct representing a color in the format "foreground:background:brightness". +use dfraw_parser_proc_macros::{Cleanable, IsEmpty}; + +/// Represents a Dwarf Fortress color triplet. +/// +/// This format is used throughout the game raws to define the foreground, +/// background, and brightness/intensity of tiles and text. #[allow(clippy::module_name_repetitions)] -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + PartialEq, + Eq, + IsEmpty, + Cleanable, + specta::Type, +)] #[serde(rename_all = "camelCase")] pub struct Color { + /// The foreground color index (0-7). foreground: u8, + /// The background color index (0-7). background: u8, + /// The brightness or intensity toggle (0 or 1). brightness: u8, } impl Color { - /// The function `from_value` takes a string value and splits it into three parts to create a - /// `Color` struct, or returns a default `Color` if the string does not have three parts. + /// Parses a color triplet from a string value. + /// + /// * `value` - A string representing a color in the format "foreground:background:brightness". /// - /// # Arguments + /// Returns a new [Color] instance, or [Color::default] if the string format is invalid. /// - /// * `value`: A string representing a color in the format "foreground:background:brightness". + /// This is typically used to parse values from tags like `[COLOR:7:0:1]` found in raw files. /// - /// # Returns + /// # Examples /// - /// * the `Color` struct. + /// ``` + /// use dfraw_parser::Color; + /// let color = Color::from_value("7:0:1"); + /// assert_eq!(color.get_foreground(), 7); + /// ``` #[must_use] pub fn from_value(value: &str) -> Self { let split = value.split(':').collect::>(); @@ -33,13 +57,52 @@ impl Color { } Self::default() } - /// The function `is_default` returns whether the color is the default color. - /// - /// # Returns + + /// Returns true if the color is the default (all components are 0). /// - /// * `true` if the color is the default color, `false` otherwise. + /// This indicates that no specific color was defined or parsing failed. #[must_use] pub const fn is_default(&self) -> bool { self.foreground == 0 && self.background == 0 && self.brightness == 0 } + + /// Returns the foreground color index. + /// + /// This value corresponds to the 0-7 color palette indices used by the game. + #[must_use] + pub fn get_foreground(&self) -> u8 { + self.foreground + } + + /// Returns the background color index. + /// + /// This value corresponds to the 0-7 color palette indices used by the game. + #[must_use] + pub fn get_background(&self) -> u8 { + self.background + } + + /// Returns the brightness value. + /// + /// A value of 1 typically represents a "bright" or "bold" version of the foreground color. + #[must_use] + pub fn get_brightness(&self) -> u8 { + self.brightness + } +} + +impl std::convert::From<(u8, u8, u8)> for Color { + fn from(value: (u8, u8, u8)) -> Self { + Self { + foreground: value.0, + background: value.1, + brightness: value.2, + } + } +} + +impl PartialEq<(u8, u8, u8)> for Color { + fn eq(&self, other: &(u8, u8, u8)) -> bool { + self.foreground == other.0 && self.background == other.1 && self.brightness == other.2 + } } diff --git a/lib/src/parsed_definitions/creature.rs b/lib/src/parsed_definitions/creature.rs index 3ac2bcfc..1be23b7f 100644 --- a/lib/src/parsed_definitions/creature.rs +++ b/lib/src/parsed_definitions/creature.rs @@ -2,11 +2,13 @@ //! that can be set in the raws. Not all the raws are represented here, only the ones that //! are currently supported by the library. +use std::collections::HashSet; + +use dfraw_parser_proc_macros::{Cleanable, IsEmpty}; use tracing::{debug, trace, warn}; use crate::{ caste::Caste, - default_checks, metadata::{ObjectType, RawMetadata}, name::Name, raw_definitions::{BIOME_TOKENS, CASTE_TOKENS, CREATURE_TOKENS}, @@ -14,10 +16,10 @@ use crate::{ tags::{BiomeTag, CasteTag, CreatureTag}, tile::Tile, traits::{ - searchable::clean_search_vec, CreatureVariationRequirements, RawObject, Searchable, + Cleanable, CreatureVariationRequirements, RawObject, RawObjectToken, Searchable, TagOperations, }, - utilities::build_object_id_from_pieces, + utilities::{build_object_id_from_pieces, clean_search_vec}, }; /// The `Creature` struct represents a creature in a Dwarf Fortress, with the properties @@ -30,12 +32,23 @@ use crate::{ /// based on the properties of the creature they are applied to. But right now the application /// of those changes is not applied, in order to preserve the original creature. So instead, /// they are saved and can be applied later (at the consumer's discretion). -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + PartialEq, + Eq, + IsEmpty, + Cleanable, +)] #[serde(rename_all = "camelCase")] pub struct Creature { /// The `metadata` field is of type `RawMetadata` and is used to provide additional information /// about the raws the `Creature` is found in. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] metadata: Option, /// The `identifier` field is a string that represents the identifier of the creature. It is used /// to uniquely identify the creature (however it is not guaranteed to be unique across object types @@ -46,19 +59,19 @@ pub struct Creature { /// its own properties, such as `name`, `description`, `body`, `flags`, etc. /// /// A lot of the properties of the `Creature` object are actually properties of a special `Caste`, `ALL`. - #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] castes: Vec, /// Any tags that are not parsed into their own fields are stored in the `tags` field. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] tags: Option>, /// The biomes that this creature can be found in - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] biomes: Option>, /// Pref strings are things that make dwarves (or others?) like or dislike the creature. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] pref_strings: Option>, /// The tile that represents the creature in the game (classic mode) - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] tile: Option, /// Determines the chances of a creature appearing within its environment, with higher values resulting in more frequent appearance. /// @@ -71,18 +84,21 @@ pub struct Creature { /// Minimum value is 0, maximum value is 100. /// /// Note: not to be confused with `[POP_RATIO]`. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = 50)] frequency: Option, /// The minimum/maximum numbers of how many creatures per spawned cluster. Vermin fish with this token in combination with /// temperate ocean and river biome tokens will perform seasonal migrations. /// /// Defaults to [1,1] if not specified. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = [1,1])] cluster_number: Option<[u32; 2]>, /// The minimum/maximum numbers of how many of these creatures are present in each world map tile of the appropriate region. /// /// Defaults to [1,1] if not specified. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = [1,1])] population_number: Option<[u32; 2]>, /// Depth that the creature appears underground. Numbers can be from 0 to 5. 0 is actually 'above ground' and can be used if the /// creature is to appear both above and below ground. Values from 1-3 are the respective cavern levels, 4 is the magma sea and @@ -93,13 +109,13 @@ pub struct Creature { /// Civilizations that can use underground plants or animals will only export (via the embark screen or caravans) things that are available at depth 1. /// /// Default [0, 0] (aboveground) - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] underground_depth: Option<[u32; 2]>, /// Like `[BABYNAME]`, but applied regardless of caste. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] general_baby_name: Option, /// Like `[CHILDNAME]`, but applied regardless of caste. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] general_child_name: Option, /// The generic name for any creature of this type - will be used when distinctions between caste are unimportant. For names for specific castes, /// use `[CASTE_NAME]` instead. If left undefined, the creature will be labeled as "nothing" by the game. @@ -109,19 +125,19 @@ pub struct Creature { /// which can then be modified. Often used in combination with `[APPLY_CREATURE_VARIATION]` to import standard variations from a file. /// /// The vanilla giant animals and animal peoples are examples of this token combination. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] copy_tags_from: Option, /// Applies the specified creature variation. /// /// These are stored "in the raw", i.e. how they appear in the raws. They are not handled until the end of the parsing process. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] apply_creature_variation: Option>, /// A generated field that is used to uniquely identify this object. It is generated from the `metadata`, `identifier`, and `ObjectType`. /// /// This field is always serialized. object_id: String, /// Various `SELECT_CREATUR` modifications. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] select_creature_variation: Option>, } @@ -231,7 +247,10 @@ impl Creature { if let Some(select_creature_variation) = &mut self.select_creature_variation { select_creature_variation.extend(select_creature_vec); } else { - warn!("Creature::extend_select_creature_variation: ({}) select_creature_variation is None", self.identifier); + warn!( + "Creature::extend_select_creature_variation: ({}) select_creature_variation is None", + self.identifier + ); } } @@ -480,6 +499,56 @@ impl Creature { self.name = name; } + pub fn get_all_names(&self) -> Vec<&str> { + let mut names = HashSet::new(); + + names.insert(self.name.get_singular()); + names.insert(self.name.get_plural()); + names.insert(self.name.get_adjective()); + + if let Some(general_baby_name) = self.general_baby_name.as_ref() { + names.insert(general_baby_name.get_singular()); + names.insert(general_baby_name.get_plural()); + names.insert(general_baby_name.get_adjective()); + } + + if let Some(general_child_name) = self.general_child_name.as_ref() { + names.insert(general_child_name.get_singular()); + names.insert(general_child_name.get_plural()); + names.insert(general_child_name.get_adjective()); + } + + self.castes.iter().for_each(|caste| { + if let Some(caste_name) = caste.get_caste_name() { + names.insert(caste_name.get_singular()); + names.insert(caste_name.get_plural()); + names.insert(caste_name.get_adjective()); + } + if let Some(caste_child_name) = caste.get_child_name() { + names.insert(caste_child_name.get_singular()); + names.insert(caste_child_name.get_plural()); + names.insert(caste_child_name.get_adjective()); + } + if let Some(caste_baby_name) = caste.get_baby_name() { + names.insert(caste_baby_name.get_singular()); + names.insert(caste_baby_name.get_plural()); + names.insert(caste_baby_name.get_adjective()); + } + }); + + names.into_iter().collect() + } + pub fn get_all_descriptions(&self) -> Vec<&str> { + let mut descriptions = HashSet::new(); + + self.castes.iter().for_each(|caste| { + if let Some(description) = caste.get_description() { + descriptions.insert(description); + } + }); + + descriptions.into_iter().collect() + } /// Parse a creature from a set of XML tags from a legends export. /// /// Expects to run on an empty or default creature. Fills in everything it can @@ -599,70 +668,6 @@ impl Creature { } } - /// Function to "clean" the creature. This is used to remove any empty list or strings, - /// and to remove any default values. By "removing" it means setting the value to None. - /// - /// This also will remove the metadata if `is_metadata_hidden` is true. - #[must_use] - pub fn cleaned(&self) -> Self { - let mut cleaned = self.clone(); - - // Set the metadata to None if it is hidden - if let Some(metadata) = &cleaned.metadata { - if metadata.is_hidden() { - cleaned.metadata = None; - } - } - - // Remove any empty lists - if cleaned.tags.is_some() && cleaned.tags.as_deref() == Some(&[]) { - cleaned.tags = None; - } - if cleaned.biomes.is_some() && cleaned.biomes.as_deref() == Some(&[]) { - cleaned.biomes = None; - } - if cleaned.pref_strings.is_some() && cleaned.pref_strings.as_deref() == Some(&[]) { - cleaned.pref_strings = None; - } - if let Some(create_variations) = &cleaned.apply_creature_variation { - if create_variations.is_empty() { - cleaned.apply_creature_variation = None; - } - } - - // Remove any default values - if default_checks::is_default_frequency(cleaned.frequency) { - cleaned.frequency = None; - } - if default_checks::min_max_is_ones(&cleaned.cluster_number) { - cleaned.cluster_number = None; - } - if default_checks::min_max_is_ones(&cleaned.population_number) { - cleaned.population_number = None; - } - if default_checks::min_max_is_zeroes(&cleaned.underground_depth) { - cleaned.underground_depth = None; - } - - if let Some(general_baby_name) = &cleaned.general_baby_name { - if general_baby_name.is_empty() { - cleaned.general_baby_name = None; - } - } - if let Some(general_child_name) = &cleaned.general_child_name { - if general_child_name.is_empty() { - cleaned.general_child_name = None; - } - } - if let Some(tile) = &cleaned.tile { - if tile.is_default() { - cleaned.tile = None; - } - } - - cleaned - } - /// Check whether the creature has the specified creature tag (found in the `tags` field). /// /// # Arguments @@ -744,12 +749,6 @@ impl RawObject for Creature { fn get_identifier(&self) -> &str { &self.identifier } - fn get_name(&self) -> &str { - self.name.get_singular() - } - fn is_empty(&self) -> bool { - self.identifier.is_empty() - } fn get_type(&self) -> &ObjectType { &ObjectType::Creature } @@ -867,8 +866,27 @@ impl RawObject for Creature { fn get_object_id(&self) -> &str { self.object_id.as_str() } - fn clean_self(&mut self) { - *self = self.cleaned(); + fn get_name(&self) -> &str { + self.name.get_singular() + } + fn get_searchable_tokens(&self) -> Vec<&str> { + let mut tokens = HashSet::new(); + + for token in CreatureTag::FLAG_TOKENS { + if self.has_tag(token) { + tokens.insert(CreatureTag::get_key(token).unwrap_or_default()); + } + } + + for caste in &self.castes { + for token in CasteTag::FLAG_TOKENS { + if caste.has_tag(token) { + tokens.insert(CasteTag::get_key(token).unwrap_or_default()); + } + } + } + + tokens.into_iter().collect() } } diff --git a/lib/src/parsed_definitions/creature_effect.rs b/lib/src/parsed_definitions/creature_effect.rs index 3f9d9200..e75b8611 100644 --- a/lib/src/parsed_definitions/creature_effect.rs +++ b/lib/src/parsed_definitions/creature_effect.rs @@ -1,81 +1,39 @@ //! A module containing the `CreatureEffect` struct and its implementations. -use crate::{default_checks, tags::CreatureEffectPropertyTag}; +use crate::tags::CreatureEffectPropertyTag; +use dfraw_parser_proc_macros::{Cleanable, IsEmpty}; /// A creature effect. -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + PartialEq, + Eq, + IsEmpty, + Cleanable, +)] #[serde(rename_all = "camelCase")] pub struct CreatureEffect { severity: u32, probability: u8, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] affected_body_parts_by_category: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] affected_body_parts_by_type: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] affected_body_parts_by_token: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] tags: Option>, start: u32, peak: u32, end: u32, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] dwf_stretch: Option, } - -impl CreatureEffect { - /// Function to "clean" the creature. This is used to remove any empty list or strings, - /// and to remove any default values. By "removing" it means setting the value to None. - /// - /// This also will remove the metadata if `is_metadata_hidden` is true. - /// - /// Steps: - /// - Set any metadata to None if `is_metadata_hidden` is true. - /// - Set any empty string to None. - /// - Set any empty list to None. - /// - Set any default values to None. - #[must_use] - pub fn cleaned(&self) -> Self { - let mut cleaned = self.clone(); - - // Set any empty string to None. - if let Some(affected_body_parts_by_category) = - cleaned.affected_body_parts_by_category.clone() - { - if affected_body_parts_by_category.is_empty() { - cleaned.affected_body_parts_by_category = None; - } - } - - // Set any empty string to None. - if let Some(affected_body_parts_by_type) = cleaned.affected_body_parts_by_type.clone() { - if affected_body_parts_by_type.is_empty() { - cleaned.affected_body_parts_by_type = None; - } - } - - // Set any empty string to None. - if let Some(affected_body_parts_by_token) = cleaned.affected_body_parts_by_token.clone() { - if affected_body_parts_by_token.is_empty() { - cleaned.affected_body_parts_by_token = None; - } - } - - // Set any empty string to None. - if let Some(tags) = cleaned.tags.clone() { - if tags.is_empty() { - cleaned.tags = None; - } - } - - // Set any default values to None. - if default_checks::is_zero_u8(cleaned.dwf_stretch) { - cleaned.dwf_stretch = None; - } - - cleaned - } -} diff --git a/lib/src/parsed_definitions/creature_variation.rs b/lib/src/parsed_definitions/creature_variation.rs index a62b69ad..85c79ec8 100644 --- a/lib/src/parsed_definitions/creature_variation.rs +++ b/lib/src/parsed_definitions/creature_variation.rs @@ -1,5 +1,6 @@ //! A module for the creature variation definition. +use dfraw_parser_proc_macros::{Cleanable, IsEmpty}; use tracing::warn; use crate::{ @@ -12,11 +13,22 @@ use crate::{ /// A creature variation. #[allow(clippy::module_name_repetitions)] -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + PartialEq, + Eq, + IsEmpty, + Cleanable, +)] #[serde(rename_all = "camelCase")] pub struct CreatureVariation { /// Common Raw file Things - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] metadata: Option, identifier: String, object_id: String, @@ -105,38 +117,13 @@ impl CreatureVariation { }) .collect() } - - /// Function to "clean" the creature. This is used to remove any empty list or strings, - /// and to remove any default values. By "removing" it means setting the value to None. - /// - /// This also will remove the metadata if `is_metadata_hidden` is true. - /// - /// Steps for all "Option" fields: - /// - Set any metadata to None if `is_metadata_hidden` is true. - /// - Set any empty string to None. - /// - Set any empty list to None. - /// - Set any default values to None. - /// - /// # Returns - /// - /// A new creature variation with all empty values set to None. - #[must_use] - pub fn cleaned(&self) -> Self { - let mut cleaned = self.clone(); - - // Set metadata to None if it's hidden - if let Some(metadata) = &cleaned.metadata { - if metadata.is_hidden() { - cleaned.metadata = None; - } - } - - cleaned - } } #[typetag::serde] impl RawObject for CreatureVariation { + fn get_searchable_tokens(&self) -> Vec<&str> { + Vec::new() + } fn get_metadata(&self) -> RawMetadata { self.metadata.as_ref().map_or_else( || { @@ -153,18 +140,10 @@ impl RawObject for CreatureVariation { self.identifier.as_str() } - fn is_empty(&self) -> bool { - self.rules.is_empty() && self.identifier.is_empty() - } - fn get_type(&self) -> &ObjectType { &ObjectType::CreatureVariation } - fn clean_self(&mut self) { - *self = self.cleaned(); - } - #[allow(clippy::too_many_lines)] fn parse_tag(&mut self, key: &str, value: &str) { let Some(token) = CREATURE_VARIATION_TOKENS.get(key) else { diff --git a/lib/src/parsed_definitions/custom_graphic_extension.rs b/lib/src/parsed_definitions/custom_graphic_extension.rs index 45f404b8..ab2cbfe1 100644 --- a/lib/src/parsed_definitions/custom_graphic_extension.rs +++ b/lib/src/parsed_definitions/custom_graphic_extension.rs @@ -1,20 +1,31 @@ //! Custom graphic extension definition. +use dfraw_parser_proc_macros::IsEmpty; use tracing::warn; use crate::tags::GraphicTypeTag; /// A custom graphic extension. #[allow(clippy::module_name_repetitions)] -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + PartialEq, + Eq, + IsEmpty, +)] #[serde(rename_all = "camelCase")] pub struct CustomGraphicExtension { extension_type: GraphicTypeTag, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] tile_page_id: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] value_1: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] value_2: Option, } diff --git a/lib/src/parsed_definitions/custom_types/habit_count.rs b/lib/src/parsed_definitions/custom_types/habit_count.rs index 4b337c06..1df76d5f 100644 --- a/lib/src/parsed_definitions/custom_types/habit_count.rs +++ b/lib/src/parsed_definitions/custom_types/habit_count.rs @@ -4,7 +4,9 @@ use std::str::FromStr; /// The 'HABIT_NUM' value which can be a number or "TEST_ALL" #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, specta::Type, Eq)] pub enum HabitCount { + /// Test all possible habit values TestAll, + /// Test a specific number of habits Specific(u32), } diff --git a/lib/src/parsed_definitions/custom_types/tile_char.rs b/lib/src/parsed_definitions/custom_types/tile_char.rs index 5a8a7fd1..acc754c5 100644 --- a/lib/src/parsed_definitions/custom_types/tile_char.rs +++ b/lib/src/parsed_definitions/custom_types/tile_char.rs @@ -10,6 +10,16 @@ pub struct TileCharacter { } impl TileCharacter { + /// Creates a new `TileCharacter` with the default '?' character. + /// + /// # Examples + /// + /// ``` + /// use dfraw_parser::custom_types::TileCharacter; + /// + /// let tile_char = TileCharacter::new(); + /// assert_eq!(tile_char.value, '?'); + /// ``` pub const fn new() -> Self { Self { value: '?' } } @@ -31,10 +41,10 @@ impl FromStr for TileCharacter { if s.len() > 1 { // Sometimes an integer is provided for the ASCII character code. - if let Ok(ascii_code) = s.parse::() { - if let Some(ascii_char) = char::from_u32(ascii_code) { - return Ok(TileCharacter { value: ascii_char }); - } + if let Ok(ascii_code) = s.parse::() + && let Some(ascii_char) = char::from_u32(ascii_code) + { + return Ok(TileCharacter { value: ascii_char }); }; // Othertimes it's a literal quoted character with `'`: `'t'` let stripped = s.trim_matches('\''); diff --git a/lib/src/parsed_definitions/dimensions.rs b/lib/src/parsed_definitions/dimensions.rs index 791bdd33..b3e2b33c 100644 --- a/lib/src/parsed_definitions/dimensions.rs +++ b/lib/src/parsed_definitions/dimensions.rs @@ -1,12 +1,26 @@ //! A module containing the `Dimensions` struct and its implementations. +use dfraw_parser_proc_macros::IsEmpty; use tracing::{error, warn}; /// A struct representing a Dimensions object. -#[derive(serde::Serialize, serde::Deserialize, Copy, Clone, Debug, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Copy, + Clone, + Debug, + Default, + specta::Type, + PartialEq, + Eq, + IsEmpty, +)] pub struct Dimensions { - x: i32, - y: i32, + /// The x coordinate + pub x: u32, + /// The y coordinate + pub y: u32, } #[allow(dead_code)] // Until we add graphics parsing @@ -20,6 +34,14 @@ impl Dimensions { pub const fn zero() -> Self { Self { x: 0, y: 0 } } + /// Returns a new Dimension where each component is the maximum of the two. + #[must_use] + pub fn max_components(self, other: Dimensions) -> Dimensions { + Dimensions { + x: self.x.max(other.x), + y: self.y.max(other.y), + } + } /// Function to create a new Dimensions object with given x and y values. /// /// # Parameters @@ -31,7 +53,7 @@ impl Dimensions { /// /// * `Dimensions` - The new Dimensions object with the given x and y values. #[must_use] - pub const fn from_xy(x: i32, y: i32) -> Self { + pub const fn from_xy(x: u32, y: u32) -> Self { Self { x, y } } /// Function to create a new Dimensions object from a token. @@ -63,17 +85,23 @@ impl Dimensions { return Self { x: 0, y: 0 }; }; - let x: i32 = match dim_x.parse() { + Self::from_two_tokens(dim_x, dim_y) + } + /// Creates a `Dimensions` from two tokens, one for the `xpos` and one for `ypos` + /// + /// If it fails to parse a token, returns `0` for that value. + pub fn from_two_tokens(dim_x: &&str, dim_y: &&str) -> Self { + let x: u32 = match dim_x.parse() { Ok(n) => n, Err(e) => { - warn!("Failed to parse {} as Dimensions:x, {:?}", token, e); + warn!("Failed to parse dim_x: {e}"); 0 } }; - let y: i32 = match dim_y.parse() { + let y: u32 = match dim_y.parse() { Ok(n) => n, Err(e) => { - warn!("Failed to parse {} as Dimensions:y, {:?}", token, e); + warn!("Failed to parse dim_y: {e}"); 0 } }; diff --git a/lib/src/parsed_definitions/entity.rs b/lib/src/parsed_definitions/entity.rs index d9016281..84907692 100644 --- a/lib/src/parsed_definitions/entity.rs +++ b/lib/src/parsed_definitions/entity.rs @@ -1,160 +1,173 @@ //! Contains the Entity struct and implementations. +use dfraw_parser_proc_macros::{Cleanable, IsEmpty}; use tracing::warn; use crate::{ color::Color, - default_checks, metadata::{ObjectType, RawMetadata}, position::Position, raw_definitions::{ENTITY_TOKENS, POSITION_TOKENS}, tags::EntityTag, - traits::{searchable::clean_search_vec, RawObject, Searchable}, - utilities::build_object_id_from_pieces, + traits::{RawObject, Searchable}, + utilities::{build_object_id_from_pieces, clean_search_vec}, }; /// A struct representing an Entity object. -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + PartialEq, + IsEmpty, + Cleanable, +)] #[serde(rename_all = "camelCase")] pub struct Entity { - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] metadata: Option, identifier: String, object_id: String, tags: Vec, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] creature: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] translation: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] exclusive_start_biome: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] biome_support: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] settlement_biome: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] start_biome: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] likes_sites: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] tolerates_sites: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] world_constructions: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = 500)] max_pop_number: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = 50)] max_site_pop_number: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = 3)] max_starting_civ_number: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] permitted_buildings: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] permitted_jobs: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] permitted_reactions: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] currency: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] art_facet_modifier: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] art_image_element_modifier: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] item_improvement_modifier: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] select_symbols: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] subselect_symbols: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] cull_symbols: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] friendly_color: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] religion: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] religion_spheres: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] sphere_alignments: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] positions: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] land_holder_trigger: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] site_variable_positions: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] variable_positions: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] ethics: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] values: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] variable_values: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] active_season: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] banditry: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] progress_trigger_population: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] progress_trigger_production: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] progress_trigger_trade: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] progress_trigger_population_siege: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] progress_trigger_production_siege: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] progress_trigger_trade_siege: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] scholars: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] ammo: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] armors: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] diggers: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] gloves: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] helms: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] instrument: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] pants: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] shields: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] shoes: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] siege_ammo: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] tool: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] toys: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] trap_components: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] weapons: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] gem_shape: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] stone_shape: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] source_hfid: Option, } @@ -198,235 +211,13 @@ impl Entity { ..Default::default() } } - - /// Function to "clean" the creature. This is used to remove any empty list or strings, - /// and to remove any default values. By "removing" it means setting the value to None. - /// - /// This also will remove the metadata if `is_metadata_hidden` is true. - /// - /// Steps: - /// - Set the metadata to None if `is_metadata_hidden` is true. - /// - Set any empty string to None. - /// - Set any empty list to None. - /// - Set any default values to None. - /// - /// # Returns - /// - /// * `Entity` - The cleaned Entity. - #[allow(clippy::too_many_lines, clippy::cognitive_complexity)] - #[must_use] - pub fn cleaned(&self) -> Self { - let mut cleaned = self.clone(); - - // Remove metadata if hidden - if let Some(metadata) = &cleaned.metadata { - if metadata.is_hidden() { - cleaned.metadata = None; - } - } - - // Remove empty strings - if cleaned.creature.as_deref() == Some("") { - cleaned.creature = None; - } - if cleaned.translation.as_deref() == Some("") { - cleaned.translation = None; - } - if cleaned.exclusive_start_biome.as_deref() == Some("") { - cleaned.exclusive_start_biome = None; - } - - if cleaned.biome_support.as_deref() == Some(&[]) { - cleaned.biome_support = None; - } - if cleaned.settlement_biome.as_deref() == Some(&[]) { - cleaned.settlement_biome = None; - } - if cleaned.start_biome.as_deref() == Some(&[]) { - cleaned.start_biome = None; - } - if cleaned.likes_sites.as_deref() == Some(&[]) { - cleaned.likes_sites = None; - } - if cleaned.tolerates_sites.as_deref() == Some(&[]) { - cleaned.tolerates_sites = None; - } - if cleaned.world_constructions.as_deref() == Some(&[]) { - cleaned.world_constructions = None; - } - - if default_checks::is_500_u32(cleaned.max_pop_number) { - cleaned.max_pop_number = None; - } - if default_checks::is_50_u32(cleaned.max_site_pop_number) { - cleaned.max_site_pop_number = None; - } - if default_checks::is_3_u32(cleaned.max_starting_civ_number) { - cleaned.max_starting_civ_number = None; - } - - if cleaned.permitted_buildings.as_deref() == Some(&[]) { - cleaned.permitted_buildings = None; - } - if cleaned.permitted_jobs.as_deref() == Some(&[]) { - cleaned.permitted_jobs = None; - } - if cleaned.permitted_reactions.as_deref() == Some(&[]) { - cleaned.permitted_reactions = None; - } - - if cleaned.currency.as_deref() == Some(&[]) { - cleaned.currency = None; - } - if cleaned.art_facet_modifier.as_deref() == Some(&[]) { - cleaned.art_facet_modifier = None; - } - if cleaned.art_image_element_modifier.as_deref() == Some(&[]) { - cleaned.art_image_element_modifier = None; - } - if cleaned.item_improvement_modifier.as_deref() == Some(&[]) { - cleaned.item_improvement_modifier = None; - } - if cleaned.select_symbols.as_deref() == Some(&[]) { - cleaned.select_symbols = None; - } - if cleaned.subselect_symbols.as_deref() == Some(&[]) { - cleaned.subselect_symbols = None; - } - if cleaned.cull_symbols.as_deref() == Some(&[]) { - cleaned.cull_symbols = None; - } - if let Some(color) = &cleaned.friendly_color { - if color.is_default() { - cleaned.friendly_color = None; - } - } - - if cleaned.religion.as_deref() == Some("") { - cleaned.religion = None; - } - if cleaned.religion_spheres.as_deref() == Some(&[]) { - cleaned.religion_spheres = None; - } - if cleaned.sphere_alignments.as_deref() == Some(&[]) { - cleaned.sphere_alignments = None; - } - - if let Some(positions) = &cleaned.positions { - if positions.is_empty() { - cleaned.positions = None; - } - } - if cleaned.land_holder_trigger.as_deref() == Some("") { - cleaned.land_holder_trigger = None; - } - if cleaned.site_variable_positions.as_deref() == Some(&[]) { - cleaned.site_variable_positions = None; - } - if cleaned.variable_positions.as_deref() == Some(&[]) { - cleaned.variable_positions = None; - } - - if cleaned.ethics.as_deref() == Some(&[]) { - cleaned.ethics = None; - } - if cleaned.values.as_deref() == Some(&[]) { - cleaned.values = None; - } - if cleaned.variable_values.as_deref() == Some(&[]) { - cleaned.variable_values = None; - } - - if cleaned.active_season.as_deref() == Some("") { - cleaned.active_season = None; - } - - if default_checks::is_zero_f32(cleaned.banditry) { - cleaned.banditry = None; - } - - if default_checks::is_zero_u8(cleaned.progress_trigger_population) { - cleaned.progress_trigger_population = None; - } - if default_checks::is_zero_u8(cleaned.progress_trigger_production) { - cleaned.progress_trigger_production = None; - } - if default_checks::is_zero_u8(cleaned.progress_trigger_trade) { - cleaned.progress_trigger_trade = None; - } - if default_checks::is_zero_u8(cleaned.progress_trigger_population_siege) { - cleaned.progress_trigger_population_siege = None; - } - if default_checks::is_zero_u8(cleaned.progress_trigger_production_siege) { - cleaned.progress_trigger_production_siege = None; - } - if default_checks::is_zero_u8(cleaned.progress_trigger_trade_siege) { - cleaned.progress_trigger_trade_siege = None; - } - - if cleaned.scholars.as_deref() == Some(&[]) { - cleaned.scholars = None; - } - if cleaned.ammo.as_deref() == Some(&[]) { - cleaned.ammo = None; - } - if cleaned.armors.as_deref() == Some(&[]) { - cleaned.armors = None; - } - if cleaned.diggers.as_deref() == Some(&[]) { - cleaned.diggers = None; - } - if cleaned.gloves.as_deref() == Some(&[]) { - cleaned.gloves = None; - } - if cleaned.helms.as_deref() == Some(&[]) { - cleaned.helms = None; - } - if cleaned.instrument.as_deref() == Some(&[]) { - cleaned.instrument = None; - } - if cleaned.pants.as_deref() == Some(&[]) { - cleaned.pants = None; - } - if cleaned.shields.as_deref() == Some(&[]) { - cleaned.shields = None; - } - if cleaned.shoes.as_deref() == Some(&[]) { - cleaned.shoes = None; - } - if cleaned.siege_ammo.as_deref() == Some(&[]) { - cleaned.siege_ammo = None; - } - if cleaned.tool.as_deref() == Some(&[]) { - cleaned.tool = None; - } - if cleaned.toys.as_deref() == Some(&[]) { - cleaned.toys = None; - } - if cleaned.trap_components.as_deref() == Some(&[]) { - cleaned.trap_components = None; - } - if cleaned.weapons.as_deref() == Some(&[]) { - cleaned.weapons = None; - } - - if cleaned.gem_shape.as_deref() == Some(&[]) { - cleaned.gem_shape = None; - } - if cleaned.stone_shape.as_deref() == Some(&[]) { - cleaned.stone_shape = None; - } - - if default_checks::is_zero_u32(cleaned.source_hfid) { - cleaned.source_hfid = None; - } - - cleaned - } } #[typetag::serde] impl RawObject for Entity { + fn get_searchable_tokens(&self) -> Vec<&str> { + Vec::new() + } fn get_object_id(&self) -> &str { self.object_id.as_str() } @@ -447,15 +238,9 @@ impl RawObject for Entity { fn get_name(&self) -> &str { &self.identifier } - fn is_empty(&self) -> bool { - self.identifier.is_empty() - } fn get_type(&self) -> &ObjectType { &ObjectType::Entity } - fn clean_self(&mut self) { - *self = self.cleaned(); - } #[allow(clippy::too_many_lines, clippy::cognitive_complexity)] fn parse_tag(&mut self, key: &str, value: &str) { if let Some(position_token) = POSITION_TOKENS.get(key) { diff --git a/lib/src/parsed_definitions/gait.rs b/lib/src/parsed_definitions/gait.rs index 584c6b4e..beb56080 100644 --- a/lib/src/parsed_definitions/gait.rs +++ b/lib/src/parsed_definitions/gait.rs @@ -4,19 +4,21 @@ use tracing::warn; use crate::tags::{GaitModifierTag, GaitTypeTag}; -/// Gaits are a way to describe how a creature moves. Defined in the raws with: +/// A struct describing how a creature moves. /// -/// "GAIT:type:name:full speed:build up time:turning max:start speed:energy use" +/// Gaits define the mechanics of movement modes like walking, swimming, or flying, +/// including speed, acceleration, and energy costs. They are defined in raw files +/// using the `[GAIT:type:name:full_speed:build_up:turning:start_speed:energy_use]` tag. /// -/// * use `NO_BUILD_UP` if you jump immediately to full speed -/// -/// these optional flags go at the end: +/// These optional flags go at the end: /// /// * `LAYERS_SLOW` - fat/muscle layers slow the movement (muscle-slowing counter-acted by strength bonus) /// * `STRENGTH` - strength attribute can speed/slow movement /// * `AGILITY` - agility attribute can speed/slow movement /// * `STEALTH_SLOWS:` - n is percentage slowed -/// * it would be interesting to allow quirky attributes (like mental stats), but they aren't supported yet +/// +/// Instead of specifying a `build_up` you can use `NO_BUILD_UP` to instantly get to speed. +/// /// /// Examples: /// @@ -31,30 +33,38 @@ use crate::tags::{GaitModifierTag, GaitTypeTag}; )] #[serde(rename_all = "camelCase")] pub struct Gait { - /// The type of gait + /// The movement medium (e.g., [`GaitTypeTag::Walk`], [`GaitTypeTag::Swim`]). gait_type: GaitTypeTag, - /// The name of the gait + /// The descriptive name of the movement (e.g., "Sprint", "Jog"). name: String, - /// The maximum speed achievable by a creature using this gait. + /// The maximum speed achievable, where lower values are faster. max_speed: u32, - /// The energy use of the gait + /// The time in game ticks required to reach full speed. + build_up_time: u32, + /// The maximum speed at which the creature can turn effectively. + turning_max: u32, + /// The speed at which the creature begins moving from a standstill. + start_speed: u32, + /// The fatigue or energy cost associated with this movement. energy_use: u32, - /// The gait modifiers - /// - /// These are optional, and may be empty. + /// Optional modifiers affecting speed based on attributes or stealth. modifiers: Vec, } impl Gait { - /// Parse a gait given the raw string (i.e. the string after the `GAIT:` tag) + /// Parses a gait definition from a raw string value. /// - /// ## Parameters + /// * `value` - The colon-separated string from a `GAIT` tag. /// - /// * `raw_gait` - The raw string to parse + /// Returns a [`Gait`] populated with the parsed values. Optional flags like + /// `LAYERS_SLOW` or `STEALTH_SLOWS` are parsed into the `modifiers` list. /// - /// ## Returns + /// # Examples /// - /// The parsed gait + /// ``` + /// use dfraw_parser::Gait; + /// let gait = Gait::from_value("WALK:Sprint:1000:NO_BUILD_UP:3:500:50:STRENGTH"); + /// ``` #[must_use] pub fn from_value(value: &str) -> Self { let mut gait = Self::default(); @@ -96,40 +106,40 @@ impl Gait { if has_build_up { // Next is turning max - if let Some(raw_value) = parts.next() { - if let Ok(value) = raw_value.parse::() { - // Modify the build up modifier to include the turning max - if let Some(GaitModifierTag::BuildUp { + if let Some(raw_value) = parts.next() + && let Ok(value) = raw_value.parse::() + { + // Modify the build up modifier to include the turning max + if let Some(GaitModifierTag::BuildUp { + time, + turning_max: _, + start_speed, + }) = gait.modifiers.pop() + { + gait.modifiers.push(GaitModifierTag::BuildUp { time, - turning_max: _, + turning_max: value, start_speed, - }) = gait.modifiers.pop() - { - gait.modifiers.push(GaitModifierTag::BuildUp { - time, - turning_max: value, - start_speed, - }); - } + }); } } // Next is start speed - if let Some(raw_value) = parts.next() { - if let Ok(value) = raw_value.parse::() { - // Modify the build up modifier to include the start speed - if let Some(GaitModifierTag::BuildUp { + if let Some(raw_value) = parts.next() + && let Ok(value) = raw_value.parse::() + { + // Modify the build up modifier to include the start speed + if let Some(GaitModifierTag::BuildUp { + time, + turning_max, + start_speed: _, + }) = gait.modifiers.pop() + { + gait.modifiers.push(GaitModifierTag::BuildUp { time, turning_max, - start_speed: _, - }) = gait.modifiers.pop() - { - gait.modifiers.push(GaitModifierTag::BuildUp { - time, - turning_max, - start_speed: value, - }); - } + start_speed: value, + }); } } } @@ -158,23 +168,84 @@ impl Gait { gait } - /// Returns true if the gait is empty (i.e. unset/default) - /// - /// ## Returns + /// Returns true if the gait is empty or uninitialized. /// - /// True if the gait is empty, false otherwise. + /// A gait is considered empty if its type is [`GaitTypeTag::Unknown`]. #[must_use] pub fn is_empty(&self) -> bool { self.gait_type == GaitTypeTag::Unknown } - /// Returns the type tag of the gait + + /// Returns the movement medium tag for this gait. + /// + /// This indicates if the movement is walking, swimming, flying, etc. #[must_use] - pub fn get_type(&self) -> &GaitTypeTag { + pub fn get_gait_type(&self) -> &GaitTypeTag { &self.gait_type } - /// Returns the max speed of the gait + + /// Returns the descriptive name of the gait. + /// + /// Examples include "Sprint", "Run", or "Humble Walk". + #[must_use] + pub fn get_name(&self) -> &str { + &self.name + } + + /// Returns the maximum speed value. + /// + /// In Dwarf Fortress, lower values represent higher speeds. + #[must_use] + pub fn get_full_speed(&self) -> u32 { + self.max_speed + } + + /// Returns the maximum speed value. + /// + /// In Dwarf Fortress, lower values represent higher speeds. #[must_use] + #[deprecated = "Use `get_full_speed` instead."] pub fn get_max_speed(&self) -> u32 { self.max_speed } + + /// Returns the time required to reach full speed. + /// + /// Measured in game ticks. A value of 0 usually indicates "NO_BUILD_UP". + #[must_use] + pub fn get_build_up_time(&self) -> u32 { + self.build_up_time + } + + /// Returns the turning speed limit. + /// + /// This limits how fast a creature can change direction while using this gait. + #[must_use] + pub fn get_turning_max(&self) -> u32 { + self.turning_max + } + + /// Returns the initial movement speed. + /// + /// The speed at which movement starts before the build-up phase begins. + #[must_use] + pub fn get_start_speed(&self) -> u32 { + self.start_speed + } + + /// Returns the energy or fatigue cost of this movement. + /// + /// Higher values cause the creature to tire more quickly. + #[must_use] + pub fn get_energy_use(&self) -> u32 { + self.energy_use + } + + /// Returns a slice of active modifiers for this gait. + /// + /// These include attribute scaling (Strength, Agility) and stealth penalties. + #[must_use] + pub fn get_modifiers(&self) -> &[GaitModifierTag] { + self.modifiers.as_slice() + } } diff --git a/lib/src/parsed_definitions/graphic.rs b/lib/src/parsed_definitions/graphic.rs index a74998ca..f95d7d7b 100644 --- a/lib/src/parsed_definitions/graphic.rs +++ b/lib/src/parsed_definitions/graphic.rs @@ -1,49 +1,83 @@ //! Graphic object definition and parsing. +use dfraw_parser_proc_macros::{Cleanable, IsEmpty}; use tracing::warn; use crate::{ custom_graphic_extension::CustomGraphicExtension, + graphic_palette::GraphicPalette, metadata::{ObjectType, RawMetadata}, - raw_definitions::{CUSTOM_GRAPHIC_TOKENS, GROWTH_TOKENS, PLANT_GRAPHIC_TEMPLATE_TOKENS}, + raw_definitions::{ + CONDITION_TOKENS, CUSTOM_GRAPHIC_TOKENS, GROWTH_TOKENS, PLANT_GRAPHIC_TEMPLATE_TOKENS, + }, sprite_graphic::SpriteGraphic, sprite_layer::SpriteLayer, - tags::GraphicTypeTag, - traits::{searchable::clean_search_vec, RawObject, Searchable}, - utilities::build_object_id_from_pieces, + tags::{ConditionTag, GraphicTypeTag}, + traits::{RawObject, Searchable}, + utilities::{build_object_id_from_pieces, clean_search_vec}, }; /// A struct representing a Graphic object. #[allow(clippy::module_name_repetitions)] -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + PartialEq, + Eq, + IsEmpty, + Cleanable, +)] #[serde(rename_all = "camelCase")] pub struct Graphic { - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] metadata: Option, identifier: String, object_id: String, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] caste_identifier: Option, + #[cleanable(ignore)] kind: GraphicTypeTag, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] sprites: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] layers: Option)>>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] growths: Option)>>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] custom_extensions: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] tags: Option>, #[serde(skip)] layer_mode: bool, + + palletes: Vec, } impl Graphic { + /// Get the sprites defined in this graphic + #[must_use] + pub fn get_sprites(&self) -> Vec { + match self.sprites.as_ref() { + Some(sprites) => sprites.clone(), + None => Vec::new(), + } + } + /// Get the sprites defined in this graphic + #[must_use] + pub fn get_layers(&self) -> Vec<(String, Vec)> { + match self.layers.as_ref() { + Some(layers) => layers.clone(), + None => Vec::new(), + } + } /// Function to create a new empty Graphic. /// /// # Returns @@ -104,23 +138,48 @@ impl Graphic { } } } + + #[tracing::instrument(skip(self), fields(self.identifier = &self.identifier))] + fn parse_layer_palette_info(&mut self, key: &str, value: &str) { + if let Some(condition_tag) = CONDITION_TOKENS.get(key) { + let last_pallete = self.palletes.last_mut(); + match condition_tag { + ConditionTag::LayerSetPalette => self.palletes.push(GraphicPalette::new(value)), + ConditionTag::LayerSetPaletteDefault => { + if let Some(palette) = last_pallete { + palette.set_default_row(value.parse().unwrap_or_default()); + } + } + ConditionTag::LayerSetPaletteFile => { + if let Some(palette) = last_pallete { + palette.set_file(value); + } + } + _ => {} + } + } else { + warn!("Expected LS_PALETTE token was invalid") + } + } + + #[tracing::instrument(skip(self), fields(self.identifier = &self.identifier))] fn parse_layer_condition_token(&mut self, key: &str, value: &str) { if let Some(layers) = self.layers.as_mut() { // Conditions get attached to the last layer in the last layer group #[allow(clippy::unwrap_used)] - if let Some(layer) = layers.last_mut().unwrap().1.last_mut() { - layer.parse_condition_token(key, value); + if let Some(layer_entry) = layers.last_mut() { + if layer_entry.1.is_empty() { + warn!("Failed to parse, No SpriteLayer defined yet: {layer_entry:?}") + } else if let Some(layer) = layer_entry.1.last_mut() { + layer.parse_condition_token(key, value); + } else { + warn!("Failed to parse, no mutable SpriteLayer: {layer_entry:?}",); + } } else { - warn!( - "Graphic::parse_condition_token: [{}] Failed to parse {}:{} as LayerCondition", - self.identifier, key, value - ); + warn!("Failed to parse, no layer to append to: {layers:?}"); } } else { - warn!( - "Graphic::parse_condition_token: [{}] Failed to parse {}:{} as LayerCondition (No existing layers)", - self.identifier, key, value - ); + warn!("Failed to parse, (No existing layers)"); } } /// Parse a token from a tag into a `SpriteGraphic` and add it to the current sprite. @@ -132,6 +191,12 @@ impl Graphic { /// * `graphic_type` - The type of graphic. #[allow(clippy::too_many_lines)] pub fn parse_sprite_from_tag(&mut self, key: &str, value: &str, graphic_type: GraphicTypeTag) { + // Check if key is related to setting palette information + if key == "LS_PALETTE" || key == "LS_PALETTE_FILE" || key == "LS_PALETTE_DEFAULT" { + self.parse_layer_palette_info(key, value); + return; + } + // Check if key is LAYER_SET meaning a new layer group is starting if key == "LAYER_SET" { // Parse the value into a SpriteLayer @@ -196,9 +261,7 @@ impl Graphic { } else { warn!( "Graphic::parse_sprite_from_tag:_extension_type [{}] Failed to parse {},{} as CustomGraphicExtension", - self.identifier, - key, - value + self.identifier, key, value ); } return; @@ -207,17 +270,15 @@ impl Graphic { // If the key is a growth token, parse it into a SpriteGraphic and add it to the current growth if let Some(_growth_type) = GROWTH_TOKENS.get(key) { if let Some(sprite_graphic) = SpriteGraphic::from_token(key, value, graphic_type) { - if let Some(growths) = self.growths.as_mut() { - if let Some(growth) = growths.last_mut() { - growth.1.push(sprite_graphic); - }; - } + if let Some(growths) = self.growths.as_mut() + && let Some(growth) = growths.last_mut() + { + growth.1.push(sprite_graphic); + }; } else { warn!( "Graphic::parse_sprite_from_tag:_growth_type [{}] Failed to parse {},{} as SpriteGraphic", - self.identifier, - key, - value + self.identifier, key, value ); } return; @@ -227,17 +288,15 @@ impl Graphic { if let Some(sprite_graphic) = SpriteGraphic::from_token(key, value, GraphicTypeTag::Template) { - if let Some(growths) = self.growths.as_mut() { - if let Some(growth) = growths.last_mut() { - growth.1.push(sprite_graphic); - }; - } + if let Some(growths) = self.growths.as_mut() + && let Some(growth) = growths.last_mut() + { + growth.1.push(sprite_graphic); + }; } else { warn!( "Graphic::parse_sprite_from_tag:_plant_graphic_template [{}] Failed to parse {},{} as SpriteGraphic", - self.identifier, - key, - value + self.identifier, key, value ); } return; @@ -259,10 +318,7 @@ impl Graphic { } else { warn!( "Graphic::parse_sprite_from_tag:_from_token [{}] Failed to parse [{}:{}] as SpriteGraphic::{:?}", - self.identifier, - key, - value, - graphic_type + self.identifier, key, value, graphic_type ); } } @@ -297,80 +353,13 @@ impl Graphic { } vec } - /// Function to "clean" the creature. This is used to remove any empty list or strings, - /// and to remove any default values. By "removing" it means setting the value to None. - /// - /// This also will remove the metadata if `is_metadata_hidden` is true. - /// - /// Steps for all "Option" fields: - /// - Set any metadata to None if `is_metadata_hidden` is true. - /// - Set any empty string to None. - /// - Set any empty list to None. - /// - Set any default values to None. - /// - /// # Returns - /// - /// * `Graphic` - The cleaned Graphic. - #[must_use] - pub fn cleaned(&self) -> Self { - let mut cleaned = self.clone(); - - if let Some(metadata) = &cleaned.metadata { - if metadata.is_hidden() { - cleaned.metadata = None; - } - } - - if let Some(custom_extensions) = &cleaned.custom_extensions { - if custom_extensions.is_empty() { - cleaned.custom_extensions = None; - } - } - - if let Some(tags) = &cleaned.tags { - if tags.is_empty() { - cleaned.tags = None; - } - } - - if let Some(sprites) = &cleaned.sprites { - let mut new_sprites = Vec::new(); - for sprite in sprites { - new_sprites.push(sprite.cleaned()); - } - cleaned.sprites = Some(new_sprites); - } - - if let Some(layers) = &cleaned.layers { - let mut new_layers = Vec::new(); - for (name, sprites) in layers { - let mut new_sprites = Vec::new(); - for sprite in sprites { - new_sprites.push(sprite.cleaned()); - } - new_layers.push((name.clone(), new_sprites)); - } - cleaned.layers = Some(new_layers); - } - - if let Some(growths) = &cleaned.growths { - let mut new_growths = Vec::new(); - for (name, sprites) in growths { - let mut new_sprites = Vec::new(); - for sprite in sprites { - new_sprites.push(sprite.cleaned()); - } - new_growths.push((name.clone(), new_sprites)); - } - cleaned.growths = Some(new_growths); - } - - cleaned - } } #[typetag::serde] impl RawObject for Graphic { + fn get_searchable_tokens(&self) -> Vec<&str> { + Vec::new() + } fn get_metadata(&self) -> RawMetadata { self.metadata.as_ref().map_or_else( || { @@ -388,15 +377,9 @@ impl RawObject for Graphic { fn get_name(&self) -> &str { &self.identifier } - fn is_empty(&self) -> bool { - self.identifier.is_empty() - } fn get_type(&self) -> &ObjectType { &ObjectType::Graphics } - fn clean_self(&mut self) { - *self = self.cleaned(); - } fn parse_tag(&mut self, key: &str, value: &str) { // Any tags should just be able to be handled by the sprite graphic, but it needs to call the right function diff --git a/lib/src/parsed_definitions/graphic_palette.rs b/lib/src/parsed_definitions/graphic_palette.rs new file mode 100644 index 00000000..7b3b9447 --- /dev/null +++ b/lib/src/parsed_definitions/graphic_palette.rs @@ -0,0 +1,50 @@ +//! Graphic palette definition +use dfraw_parser_proc_macros::IsEmpty; + +/// A struct representing a Graphic object. +#[allow(clippy::module_name_repetitions)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + PartialEq, + Eq, + IsEmpty, +)] +#[serde(rename_all = "camelCase")] +pub struct GraphicPalette { + /// Name of the palette + name: String, + /// Relative file path to the palette file + file: String, + /// Default row of the palette + default_row: u32, +} + +impl GraphicPalette { + #[must_use] + pub fn new(new_name: &str) -> Self { + Self { + name: String::from(new_name), + ..Default::default() + } + } + pub fn get_name(&self) -> &str { + self.name.as_str() + } + pub fn set_file(&mut self, file_path: &str) { + self.file = String::from(file_path); + } + pub fn get_file(&self) -> &str { + self.file.as_str() + } + pub fn set_default_row(&mut self, row_num: u32) { + self.default_row = row_num; + } + pub fn get_default_row(&self) -> u32 { + self.default_row + } +} diff --git a/lib/src/parsed_definitions/info_file.rs b/lib/src/parsed_definitions/info_file.rs index 596721f9..b4893b06 100644 --- a/lib/src/parsed_definitions/info_file.rs +++ b/lib/src/parsed_definitions/info_file.rs @@ -5,22 +5,34 @@ use std::{ path::Path, }; +use dfraw_parser_proc_macros::{Cleanable, IsEmpty}; use encoding_rs_io::DecodeReaderBytesBuilder; use slug::slugify; use tracing::{debug, error, trace, warn}; use crate::{ + ParserError, constants::DF_ENCODING, metadata::RawModuleLocation, regex::{NON_DIGIT_RE, RAW_TOKEN_RE}, utilities::{get_parent_dir_name, try_get_file}, - ParserError, }; use super::steam_data::SteamData; /// Represents the `info.txt` file for a raw module -#[derive(serde::Serialize, serde::Deserialize, Default, Clone, Debug, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Default, + Clone, + Debug, + specta::Type, + PartialEq, + Eq, + IsEmpty, + Cleanable, +)] #[serde(rename_all = "camelCase")] pub struct InfoFile { identifier: String, @@ -35,15 +47,15 @@ pub struct InfoFile { name: String, description: String, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] requires_ids: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] conflicts_with_ids: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] requires_ids_before: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] requires_ids_after: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] steam_data: Option, } @@ -196,8 +208,7 @@ impl InfoFile { trace!( "ModuleInfoFile::parse: Key: {} Value: {}", - captured_key, - captured_value + captured_key, captured_value ); match captured_key { @@ -211,10 +222,10 @@ impl InfoFile { Err(_e) => { if warn_on_format_issue { warn!( - "ModuleInfoFile::parse: 'NUMERIC_VERSION' should be integer '{}' in {}", - captured_value, - info_file_path.as_ref().display() - ); + "ModuleInfoFile::parse: 'NUMERIC_VERSION' should be integer '{}' in {}", + captured_value, + info_file_path.as_ref().display() + ); } // match on \D to replace any non-digit characters with empty string let digits_only = @@ -235,10 +246,10 @@ impl InfoFile { Err(_e) => { if warn_on_format_issue { warn!( - "ModuleInfoFile::parse: 'EARLIEST_COMPATIBLE_NUMERIC_VERSION' should be integer '{}' in {:?}", - captured_value, - info_file_path.as_ref().display() - ); + "ModuleInfoFile::parse: 'EARLIEST_COMPATIBLE_NUMERIC_VERSION' should be integer '{}' in {:?}", + captured_value, + info_file_path.as_ref().display() + ); } // match on \D to replace any non-digit characters with empty string let digits_only = @@ -377,10 +388,10 @@ impl InfoFile { Err(_e) => { if warn_on_format_issue { warn!( - "ModuleInfoFile::parse: 'STEAM_FILE_ID' should be integer, was {} in {}", - captured_value, - info_file_path.as_ref().display() - ); + "ModuleInfoFile::parse: 'STEAM_FILE_ID' should be integer, was {} in {}", + captured_value, + info_file_path.as_ref().display() + ); } // match on \D to replace any non-digit characters with empty string let digits_only = @@ -450,11 +461,52 @@ impl InfoFile { pub fn get_version(&self) -> String { String::from(&self.displayed_version) } + /// Returns the numeric version for the `InfoFile` + #[must_use] + pub fn get_numeric_version(&self) -> u32 { + self.numeric_version + } + /// Returns the `earliest_compatible_numeric_version` for the `InfoFile` + #[must_use] + pub fn get_earliest_compatible_numeric_version(&self) -> u32 { + self.earliest_compatible_numeric_version + } + /// Returns the `earliest_compatible_displayed_version` for the `InfoFile` + #[must_use] + pub fn get_earliest_compatible_displayed_version(&self) -> String { + String::from(&self.earliest_compatible_displayed_version) + } + /// Returns the author for the `InfoFile` + #[must_use] + pub fn get_author(&self) -> String { + String::from(&self.author) + } /// Returns the module's object id #[must_use] pub fn get_object_id(&self) -> String { String::from(&self.object_id) } + /// Returns the `SteamData` for the info file if it exists. + #[must_use] + pub fn get_steam_data(&self) -> Option { + self.steam_data.clone() + } + #[must_use] + pub fn get_requires_ids(&self) -> Option> { + self.requires_ids.clone() + } + #[must_use] + pub fn get_requires_ids_before(&self) -> Option> { + self.requires_ids_before.clone() + } + #[must_use] + pub fn get_requires_ids_after(&self) -> Option> { + self.requires_ids_after.clone() + } + #[must_use] + pub fn get_conflicts_with_ids(&self) -> Option> { + self.conflicts_with_ids.clone() + } /// Returns the directory the `InfoFile` was parsed from /// /// # Example diff --git a/lib/src/parsed_definitions/inorganic.rs b/lib/src/parsed_definitions/inorganic.rs index a4e949ee..42ff52e0 100644 --- a/lib/src/parsed_definitions/inorganic.rs +++ b/lib/src/parsed_definitions/inorganic.rs @@ -1,40 +1,51 @@ //! Parsed Inorganic object definition. +use dfraw_parser_proc_macros::{Cleanable, IsEmpty}; use crate::{ - default_checks, material::Material, metadata::{ObjectType, RawMetadata}, raw_definitions::{ENVIRONMENT_CLASS_TOKENS, INCLUSION_TYPE_TOKENS, INORGANIC_TOKENS}, tags::{EnvironmentClassTag, InclusionTypeTag, InorganicTag}, - traits::{searchable::clean_search_vec, RawObject, Searchable}, - utilities::build_object_id_from_pieces, + traits::{RawObject, Searchable}, + utilities::{build_object_id_from_pieces, clean_search_vec}, }; /// The raw representation of an inorganic object. -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + PartialEq, + Eq, + IsEmpty, + Cleanable, +)] #[serde(rename_all = "camelCase")] pub struct Inorganic { identifier: String, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] metadata: Option, object_id: String, material: Material, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] metal_ore_chance: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] thread_metal_chance: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] environment_class: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] environment_inclusion_type: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] environment_inclusion_frequency: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] environment_class_specific: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] tags: Option>, } @@ -78,61 +89,7 @@ impl Inorganic { ..Self::default() } } - /// Function to "clean" the creature. This is used to remove any empty list or strings, - /// and to remove any default values. By "removing" it means setting the value to None. - /// - /// This also will remove the metadata if `is_metadata_hidden` is true. - /// - /// Steps for all "Option" fields: - /// - Set any metadata to None if `is_metadata_hidden` is true. - /// - Set any empty string to None. - /// - Set any empty list to None. - /// - Set any default values to None. - /// - /// # Returns - /// - /// A new Inorganic object with all empty or default values set to None. - #[must_use] - pub fn cleaned(&self) -> Self { - let mut cleaned = self.clone(); - if let Some(metadata) = &cleaned.metadata { - if metadata.is_hidden() { - cleaned.metadata = None; - } - } - - if let Some(metal_ore_chance) = &cleaned.metal_ore_chance { - if metal_ore_chance.is_empty() { - cleaned.metal_ore_chance = None; - } - } - if let Some(thread_metal_chance) = &cleaned.thread_metal_chance { - if thread_metal_chance.is_empty() { - cleaned.thread_metal_chance = None; - } - } - if let Some(environment_class) = &cleaned.environment_class { - if environment_class.is_default() { - cleaned.environment_class = None; - } - } - if let Some(environment_inclusion_type) = &cleaned.environment_inclusion_type { - if environment_inclusion_type.is_default() { - cleaned.environment_inclusion_type = None; - } - } - if default_checks::is_zero(cleaned.environment_inclusion_frequency) { - cleaned.environment_inclusion_frequency = None; - } - if let Some(environment_class_specific) = &cleaned.environment_class_specific { - if environment_class_specific.is_empty() { - cleaned.environment_class_specific = None; - } - } - - cleaned - } /// Add a tag to the inorganic raw. /// /// This handles making sure the tags vector is initialized. @@ -179,6 +136,9 @@ impl Inorganic { #[typetag::serde] impl RawObject for Inorganic { + fn get_searchable_tokens(&self) -> Vec<&str> { + Vec::new() + } fn get_identifier(&self) -> &str { &self.identifier } @@ -196,15 +156,9 @@ impl RawObject for Inorganic { std::clone::Clone::clone, ) } - fn is_empty(&self) -> bool { - self.identifier.is_empty() - } fn get_type(&self) -> &ObjectType { &ObjectType::Inorganic } - fn clean_self(&mut self) { - *self = self.cleaned(); - } fn parse_tag(&mut self, key: &str, value: &str) { if INORGANIC_TOKENS.contains_key(key) { diff --git a/lib/src/parsed_definitions/material.rs b/lib/src/parsed_definitions/material.rs index b3b169b0..60d18e85 100644 --- a/lib/src/parsed_definitions/material.rs +++ b/lib/src/parsed_definitions/material.rs @@ -1,10 +1,10 @@ //! A module to handle the parsing of material definitions from the raws. +use dfraw_parser_proc_macros::IsEmpty; use tracing::warn; use crate::{ color::Color, - default_checks, material_mechanics::MaterialMechanics, raw_definitions::{ CREATURE_EFFECT_TOKENS, FUEL_TYPE_TOKENS, MATERIAL_PROPERTY_TOKENS, MATERIAL_TYPE_TOKENS, @@ -15,93 +15,105 @@ use crate::{ tags::{FuelTypeTag, MaterialPropertyTag, MaterialTypeTag, MaterialUsageTag}, temperatures::Temperatures, tile::Tile, - traits::{searchable::clean_search_vec, Searchable}, + traits::Searchable, + utilities::clean_search_vec, }; /// A struct representing a material #[allow(clippy::module_name_repetitions)] -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + PartialEq, + Eq, + IsEmpty, +)] #[serde(rename_all = "camelCase")] pub struct Material { /// The type of the material is also the trigger to start tracking a material - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] material_type: Option, /// The material might have a name, but its more likely that there is only an identifier to /// refer to another creature/plant/reaction, which are listed elsewhere. /// If there is no name provided, then it is a special hardcoded case, e.g. magma or green glass. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] name: Option, /// For the coal tag, it specifies the type of fuel that can be used. It will never be None. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] fuel_type: Option, /// Linked creature identifier (and then `material_name` might be "skin", like for "`CREATURE_MAT:DWARF:SKIN`") - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] creature_identifier: Option, /// Linked plant identifier (and then `material_name` might be "leaf", like for "`PLANT_MAT:BUSH_QUARRY:LEAF`") - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] plant_identifier: Option, /// If a material is defined within a creature itself, it will use `LOCAL_CREATURE_MAT` tag, which implies /// that the material is only used by that creature. This is also true for plants and `LOCAL_PLANT_MAT`. // skip if false - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] is_local_material: Option, /// Within a reaction, there can be special material definitions. Todo: Figure this out. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] reagent_identifier: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] reaction_product_identifier: Option, /// If material is defined from a template, we need a way to refer to that - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] template_identifier: Option, /// Usage tags - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] usage: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = 1)] value: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] color: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] state_names: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] state_adjectives: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] state_colors: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] temperatures: Option, /// Catch-all for remaining tags we identify but don't do anything with... yet. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] properties: Option>, // Syndromes attached to materials.. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] syndromes: Option>, // Material Mechanical Properties - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] mechanical_properties: Option, // Technically, the material mechanics wouldn't apply to liquid or gaseous forms - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] liquid_density: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] molar_mass: Option, // Colors - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] build_color: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] display_color: Option, // Display - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] tile: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] item_symbol: Option, } @@ -523,17 +535,15 @@ impl Material { } // Materials can have syndromes attached and syndromes have creature effects attached. - if SYNDROME_TOKENS.contains_key(key) + if (SYNDROME_TOKENS.contains_key(key) || CREATURE_EFFECT_TOKENS.contains_key(key) - || key == "CE" - { - if let Some(syndromes) = self.syndromes.as_mut() { + || key == "CE") + && let Some(syndromes) = self.syndromes.as_mut() && // We need to add the tag to the last syndrome added (all syndromes start with SYNDROME key) - if let Some(syndrome) = syndromes.last_mut() { - syndrome.parse_tag(key, value); - return; - } - } + let Some(syndrome) = syndromes.last_mut() + { + syndrome.parse_tag(key, value); + return; } warn!( @@ -541,154 +551,6 @@ impl Material { key ); } - - /// Function to "clean" the raw. This is used to remove any empty list or strings, - /// and to remove any default values. By "removing" it means setting the value to None. - /// - /// This also will remove the metadata if `is_metadata_hidden` is true. - /// - /// Steps for all "Option" fields: - /// - Set any metadata to None if `is_metadata_hidden` is true. - /// - Set any empty string to None. - /// - Set any empty list to None. - /// - Set any default values to None. - /// - /// # Returns - /// - /// A new material with all empty or default values removed. - #[allow(clippy::too_many_lines, clippy::cognitive_complexity)] - #[must_use] - pub fn cleaned(&self) -> Self { - let mut cleaned = self.clone(); - - if let Some(material_type) = &cleaned.material_type { - if material_type.is_default() { - cleaned.material_type = None; - } - } - if let Some(name) = &cleaned.name { - if name.is_empty() { - cleaned.name = None; - } - } - if let Some(fuel_type) = &cleaned.fuel_type { - if fuel_type.is_default() { - cleaned.fuel_type = None; - } - } - if let Some(creature_identifier) = &cleaned.creature_identifier { - if creature_identifier.is_empty() { - cleaned.creature_identifier = None; - } - } - if let Some(plant_identifier) = &cleaned.plant_identifier { - if plant_identifier.is_empty() { - cleaned.plant_identifier = None; - } - } - if let Some(is_local_material) = &cleaned.is_local_material { - if !is_local_material { - cleaned.is_local_material = None; - } - } - if let Some(reagent_identifier) = &cleaned.reagent_identifier { - if reagent_identifier.is_empty() { - cleaned.reagent_identifier = None; - } - } - if let Some(reaction_product_identifier) = &cleaned.reaction_product_identifier { - if reaction_product_identifier.is_empty() { - cleaned.reaction_product_identifier = None; - } - } - if let Some(template_identifier) = &cleaned.template_identifier { - if template_identifier.is_empty() { - cleaned.template_identifier = None; - } - } - if let Some(usage) = &cleaned.usage { - if usage.is_empty() { - cleaned.usage = None; - } - } - if default_checks::is_one_u32(cleaned.value) { - cleaned.value = None; - } - if let Some(color) = &cleaned.color { - if color.is_default() { - cleaned.color = None; - } - } - if let Some(state_names) = &cleaned.state_names { - if state_names.is_empty() { - cleaned.state_names = None; - } - } - if let Some(state_adjectives) = &cleaned.state_adjectives { - if state_adjectives.is_empty() { - cleaned.state_adjectives = None; - } - } - if let Some(state_colors) = &cleaned.state_colors { - if state_colors.is_empty() { - cleaned.state_colors = None; - } - } - if let Some(temperatures) = &cleaned.temperatures { - if temperatures.is_empty() { - cleaned.temperatures = None; - } - } - if let Some(properties) = &cleaned.properties { - if properties.is_empty() { - cleaned.properties = None; - } - } - if let Some(syndromes) = &cleaned.syndromes { - let mut cleaned_syndromes = Vec::new(); - for syndrome in syndromes { - cleaned_syndromes.push(syndrome.cleaned()); - } - if cleaned_syndromes.is_empty() { - cleaned.syndromes = None; - } else { - cleaned.syndromes = Some(cleaned_syndromes); - } - } - if let Some(mechanical_properties) = &cleaned.mechanical_properties { - if mechanical_properties.is_empty() { - cleaned.mechanical_properties = None; - } - } - if default_checks::is_zero_i32(cleaned.liquid_density) { - cleaned.liquid_density = None; - } - if default_checks::is_zero_i32(cleaned.molar_mass) { - cleaned.molar_mass = None; - } - if let Some(build_color) = &cleaned.build_color { - if build_color.is_default() { - cleaned.build_color = None; - } - } - if let Some(display_color) = &cleaned.display_color { - if display_color.is_default() { - cleaned.display_color = None; - } - } - if let Some(tile) = &cleaned.tile { - if tile.is_default() { - cleaned.tile = None; - } - } - if let Some(item_symbol) = &cleaned.item_symbol { - if item_symbol.is_empty() { - cleaned.item_symbol = None; - } - } - - cleaned - } } impl Searchable for Material { diff --git a/lib/src/parsed_definitions/material_mechanics.rs b/lib/src/parsed_definitions/material_mechanics.rs index 8b0bb2b4..ca341a96 100644 --- a/lib/src/parsed_definitions/material_mechanics.rs +++ b/lib/src/parsed_definitions/material_mechanics.rs @@ -1,34 +1,43 @@ //! Contains the `MaterialMechanics` struct and associated functions. +use dfraw_parser_proc_macros::IsEmpty; use tracing::warn; -use crate::{ - default_checks, mechanical_properties::MechanicalProperties, tags::MaterialPropertyTag, -}; +use crate::{mechanical_properties::MechanicalProperties, tags::MaterialPropertyTag}; /// Represents the specific yield, fracture, and elasticity of a material for the various /// types of mechanical stress. #[allow(clippy::module_name_repetitions)] -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + PartialEq, + Eq, + IsEmpty, +)] #[serde(rename_all = "camelCase")] pub struct MaterialMechanics { - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] impact: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] compressive: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] tensile: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] torsion: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] shear: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] bending: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] max_edge: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] solid_density: Option, } @@ -222,58 +231,4 @@ impl MaterialMechanics { } } } - /// Function to "clean" the raw. This is used to remove any empty list or strings, - /// and to remove any default values. By "removing" it means setting the value to None. - /// - /// This also will remove the metadata if `is_metadata_hidden` is true. - /// - /// Steps for all "Option" fields: - /// - Set any metadata to None if `is_metadata_hidden` is true. - /// - Set any empty string to None. - /// - Set any empty list to None. - /// - Set any default values to None. - #[must_use] - pub fn cleaned(&self) -> Self { - let mut cleaned = self.clone(); - - if let Some(impact) = &cleaned.impact { - if impact.is_empty() { - cleaned.impact = None; - } - } - if let Some(compressive) = &cleaned.compressive { - if compressive.is_empty() { - cleaned.compressive = None; - } - } - if let Some(tensile) = &cleaned.tensile { - if tensile.is_empty() { - cleaned.tensile = None; - } - } - if let Some(torsion) = &cleaned.torsion { - if torsion.is_empty() { - cleaned.torsion = None; - } - } - if let Some(shear) = &cleaned.shear { - if shear.is_empty() { - cleaned.shear = None; - } - } - if let Some(bending) = &cleaned.bending { - if bending.is_empty() { - cleaned.bending = None; - } - } - - if default_checks::is_zero_i32(cleaned.max_edge) { - cleaned.max_edge = None; - } - if default_checks::is_zero_i32(cleaned.solid_density) { - cleaned.solid_density = None; - } - - cleaned - } } diff --git a/lib/src/parsed_definitions/material_template.rs b/lib/src/parsed_definitions/material_template.rs index 4056f443..7f868a98 100644 --- a/lib/src/parsed_definitions/material_template.rs +++ b/lib/src/parsed_definitions/material_template.rs @@ -1,18 +1,31 @@ //! Material template definition +use dfraw_parser_proc_macros::{Cleanable, IsEmpty}; + use crate::{ material::Material, metadata::{ObjectType, RawMetadata}, - traits::{searchable::clean_search_vec, RawObject, Searchable}, - utilities::build_object_id_from_pieces, + traits::{RawObject, Searchable}, + utilities::{build_object_id_from_pieces, clean_search_vec}, }; /// A struct representing a material template -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + PartialEq, + Eq, + IsEmpty, + Cleanable, +)] #[serde(rename_all = "camelCase")] pub struct MaterialTemplate { identifier: String, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] metadata: Option, object_id: String, material: Material, @@ -58,39 +71,12 @@ impl MaterialTemplate { ..Self::default() } } - - /// Function to "clean" the raw. This is used to remove any empty list or strings, - /// and to remove any default values. By "removing" it means setting the value to None. - /// - /// This also will remove the metadata if `is_metadata_hidden` is true. - /// - /// Steps for all "Option" fields: - /// - Set any metadata to None if `is_metadata_hidden` is true. - /// - Set any empty string to None. - /// - Set any empty list to None. - /// - Set any default values to None. - /// - /// # Returns - /// - /// A new material template with all empty or default values removed. - #[must_use] - pub fn cleaned(&self) -> Self { - let mut cleaned = self.clone(); - - if let Some(metadata) = &cleaned.metadata { - if metadata.is_hidden() { - cleaned.metadata = None; - } - } - - cleaned - } } #[typetag::serde] impl RawObject for MaterialTemplate { - fn is_empty(&self) -> bool { - self.identifier.is_empty() + fn get_searchable_tokens(&self) -> Vec<&str> { + Vec::new() } fn get_identifier(&self) -> &str { &self.identifier @@ -124,9 +110,6 @@ impl RawObject for MaterialTemplate { fn get_type(&self) -> &ObjectType { &ObjectType::MaterialTemplate } - fn clean_self(&mut self) { - *self = self.cleaned(); - } } impl Searchable for MaterialTemplate { diff --git a/lib/src/parsed_definitions/mechanical_properties.rs b/lib/src/parsed_definitions/mechanical_properties.rs index fce40c26..ceb1b1ee 100644 --- a/lib/src/parsed_definitions/mechanical_properties.rs +++ b/lib/src/parsed_definitions/mechanical_properties.rs @@ -1,8 +1,19 @@ //! Contains the `MechanicalProperties` struct and its implementation +use dfraw_parser_proc_macros::IsEmpty; /// Represents the mechanical properties of a material via the yield, fracture, and elasticity #[allow(clippy::module_name_repetitions)] -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + PartialEq, + Eq, + IsEmpty, +)] #[serde(rename_all = "camelCase")] pub struct MechanicalProperties { #[serde(rename = "yield")] diff --git a/lib/src/parsed_definitions/milkable.rs b/lib/src/parsed_definitions/milkable.rs index 48c507ac..02f8c9fe 100644 --- a/lib/src/parsed_definitions/milkable.rs +++ b/lib/src/parsed_definitions/milkable.rs @@ -1,7 +1,20 @@ //! Milkable struct and implementation +use dfraw_parser_proc_macros::{Cleanable, IsEmpty}; + #[allow(clippy::module_name_repetitions)] -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + PartialEq, + Eq, + specta::Type, + IsEmpty, + Cleanable, +)] #[serde(rename_all = "camelCase")] /// How often a creature can be milked and what material it produces pub struct Milkable { diff --git a/lib/src/parsed_definitions/mod.rs b/lib/src/parsed_definitions/mod.rs index 68f298ca..3ac4a3f8 100644 --- a/lib/src/parsed_definitions/mod.rs +++ b/lib/src/parsed_definitions/mod.rs @@ -14,6 +14,7 @@ pub mod dimensions; pub mod entity; pub mod gait; pub mod graphic; +pub mod graphic_palette; pub mod info_file; pub mod inorganic; pub mod material; diff --git a/lib/src/parsed_definitions/name.rs b/lib/src/parsed_definitions/name.rs index 2fb25069..20e0ffba 100644 --- a/lib/src/parsed_definitions/name.rs +++ b/lib/src/parsed_definitions/name.rs @@ -1,7 +1,20 @@ //! Name struct for singular, plural, and adjective names (or any combination thereof) +use dfraw_parser_proc_macros::{Cleanable, IsEmpty}; + /// A name with a singular, plural, and adjective form -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + PartialEq, + Eq, + specta::Type, + IsEmpty, + Cleanable, +)] #[serde(rename_all = "camelCase")] pub struct Name { singular: String, diff --git a/lib/src/parsed_definitions/plant.rs b/lib/src/parsed_definitions/plant.rs index 25f9d532..a6c13394 100644 --- a/lib/src/parsed_definitions/plant.rs +++ b/lib/src/parsed_definitions/plant.rs @@ -1,9 +1,11 @@ //! Plant definition and parsing +use std::collections::HashSet; + +use dfraw_parser_proc_macros::{Cleanable, IsEmpty}; use tracing::{debug, warn}; use crate::{ - default_checks, material::Material, metadata::{ObjectType, RawMetadata}, name::Name, @@ -14,51 +16,62 @@ use crate::{ }, shrub::Shrub, tags::{BiomeTag, PlantGrowthTag, PlantGrowthTypeTag, PlantTag}, - traits::{searchable::clean_search_vec, RawObject, Searchable}, + traits::{RawObject, Searchable}, tree::Tree, - utilities::{build_object_id_from_pieces, parse_min_max_range}, + utilities::{build_object_id_from_pieces, clean_search_vec, parse_min_max_range}, }; /// A struct representing a plant #[allow(clippy::module_name_repetitions)] -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + PartialEq, + Eq, + IsEmpty, + Cleanable, +)] #[serde(rename_all = "camelCase")] pub struct Plant { /// Common Raw file Things - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] metadata: Option, identifier: String, object_id: String, // Basic Tokens name: Name, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] pref_strings: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] tags: Option>, // Environment Tokens /// Default [0, 0] (aboveground) - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] underground_depth: Option<[u32; 2]>, /// Default frequency is 50 - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] frequency: Option, /// List of biomes this plant can grow in - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] biomes: Option>, /// Growth Tokens define the growths of the plant (leaves, fruit, etc.) - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] growths: Option>, /// If plant is a tree, it will have details about the tree. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] tree_details: Option, /// If plant is a shrub, it will have details about the shrub. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] shrub_details: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] materials: Option>, } @@ -112,77 +125,30 @@ impl Plant { .map_or_else(Vec::new, std::clone::Clone::clone) } - /// Function to "clean" the raw. This is used to remove any empty list or strings, - /// and to remove any default values. By "removing" it means setting the value to None. - /// - /// This also will remove the metadata if `is_metadata_hidden` is true. - /// - /// Steps for all "Option" fields: - /// - Set any metadata to None if `is_metadata_hidden` is true. - /// - Set any empty string to None. - /// - Set any empty list to None. - /// - Set any default values to None. - /// - /// # Returns - /// - /// A new plant with all empty or default values removed. - #[must_use] - pub fn cleaned(&self) -> Self { - let mut cleaned = self.clone(); - - if let Some(metadata) = &cleaned.metadata { - if metadata.is_hidden() { - cleaned.metadata = None; - } - } - - if let Some(pref_strings) = &cleaned.pref_strings { - if pref_strings.is_empty() { - cleaned.pref_strings = None; - } - } - - if let Some(tags) = &cleaned.tags { - if tags.is_empty() { - cleaned.tags = None; - } - } + pub fn get_all_names(&self) -> Vec<&str> { + let mut names = HashSet::new(); - if default_checks::min_max_is_zeroes(&cleaned.underground_depth) { - cleaned.underground_depth = None; - } - - if default_checks::is_default_frequency(cleaned.frequency) { - cleaned.frequency = None; - } - - if let Some(biomes) = &cleaned.biomes { - if biomes.is_empty() { - cleaned.biomes = None; - } - } + names.insert(self.name.get_singular()); + names.insert(self.name.get_plural()); + names.insert(self.name.get_adjective()); - if let Some(growths) = &cleaned.growths { - let mut cleaned_growths = Vec::new(); + if let Some(growths) = &self.growths { for growth in growths { - cleaned_growths.push(growth.cleaned()); - } - cleaned.growths = Some(cleaned_growths); - } - - if let Some(materials) = &cleaned.materials { - let mut cleaned_materials = Vec::new(); - for material in materials { - cleaned_materials.push(material.cleaned()); - } - if cleaned_materials.is_empty() { - cleaned.materials = None; + names.insert(growth.name.get_singular()); + names.insert(growth.name.get_plural()); + names.insert(growth.name.get_adjective()); } - cleaned.materials = Some(cleaned_materials); } - cleaned + names.into_iter().collect() } + pub fn get_pref_strings(&self) -> Vec<&str> { + if let Some(prefs) = self.pref_strings.as_ref() { + return prefs.iter().map(String::as_str).collect(); + }; + Vec::new() + } + /// Add a tag to the plant. /// /// This handles making sure the tags vector is initialized. @@ -249,6 +215,27 @@ impl Plant { #[typetag::serde] impl RawObject for Plant { + fn get_searchable_tokens(&self) -> Vec<&str> { + let mut tokens = HashSet::new(); + + for token in PlantTag::FLAG_TOKENS { + if self.has_tag(token) { + tokens.insert(PlantTag::get_key(token).unwrap_or_default()); + } + } + + if let Some(growths) = &self.growths { + for growth in growths { + if PlantGrowthTypeTag::FLAG_TOKENS.contains(&growth.get_growth_type()) { + tokens.insert( + PlantGrowthTypeTag::get_key(growth.get_growth_type()).unwrap_or_default(), + ); + } + } + } + + tokens.into_iter().collect() + } fn get_metadata(&self) -> RawMetadata { self.metadata.as_ref().map_or_else( || { @@ -269,13 +256,7 @@ impl RawObject for Plant { fn get_name(&self) -> &str { self.name.get_singular() } - fn is_empty(&self) -> bool { - self.identifier.is_empty() - } - fn clean_self(&mut self) { - *self = self.cleaned(); - } fn get_type(&self) -> &ObjectType { &ObjectType::Plant } diff --git a/lib/src/parsed_definitions/plant_growth.rs b/lib/src/parsed_definitions/plant_growth.rs index 3ebcf42b..903fda7d 100644 --- a/lib/src/parsed_definitions/plant_growth.rs +++ b/lib/src/parsed_definitions/plant_growth.rs @@ -1,32 +1,43 @@ //! Contains the struct for plant growths and its implementation. +use dfraw_parser_proc_macros::{Cleanable, IsEmpty}; use tracing::{error, warn}; use crate::{ - default_checks, name::Name, raw_definitions::{PLANT_GROWTH_TOKENS, PLANT_PART_TOKENS}, tags::{PlantGrowthTag, PlantGrowthTypeTag, PlantPartTag}, - traits::{searchable::clean_search_vec, Searchable}, + traits::Searchable, + utilities::clean_search_vec, }; /// A struct representing a plant growth -#[allow(clippy::module_name_repetitions)] -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + PartialEq, + Eq, + IsEmpty, + Cleanable, +)] #[serde(rename_all = "camelCase")] pub struct PlantGrowth { /// Plant growths are not given an identifier, since they are just supporting /// data for the plant definition. They are defined instead by the type of growth. growth_type: PlantGrowthTypeTag, /// The name of the growth. This is actually defined with `GROWTH_NAME` key in the raws. - name: Name, + pub name: Name, /// The item grown by this growth. This is actually defined with `GROWTH_ITEM` key in the raws. /// This is a string until we make a proper item structure. Technically there are 2 arguments: /// 1. item token, 2: material token. Generally the item type should be `PLANT_GROWTH:NONE`. item: String, /// Specifies on which part of the plant this growth grows. This is defined with `GROWTH_HOST_TILE` key. /// This can be unused, like in the case of crops where the plant is the growth (I think?). - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] host_tiles: Option>, /// Controls the height on the trunk above which the growth begins to appear. /// The first value is the percent of the trunk height where the growth begins appearing: @@ -34,21 +45,22 @@ pub struct PlantGrowth { /// at the topmost trunk tile. Can be larger than 100 to cause it to appear above the trunk. /// The second value must be -1, but might be intended to control whether it starts height counting /// from the bottom or top. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] trunk_height_percentage: Option<[i32; 2]>, /// Currently has no effect. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] density: Option, /// Specifies the appearance of the growth. This is defined with `GROWTH_PRINT` key. /// This is a string until we make a proper print structure. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] print: Option, /// Specifies at which part of the year the growth appears. Default is all year round. /// Minimum: 0, Maximum: `402_200`. This is defined with `GROWTH_TIMING` key. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = [0, 402_200])] timing: Option<[u32; 2]>, /// Where we gather some of the growth's tags. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] tags: Option>, } @@ -69,6 +81,21 @@ impl PlantGrowth { ..Self::default() } } + /// Returns the type of growth this is + pub fn get_growth_type(&self) -> &PlantGrowthTypeTag { + &self.growth_type + } + /// Returns true if tag exists on this plant growth + pub fn has_tag(&self, tag: &PlantGrowthTag) -> bool { + if let Some(tags) = &self.tags { + for t in tags { + if std::mem::discriminant(t) == std::mem::discriminant(tag) { + return true; + } + } + } + false + } /// Parses a tag and value into the plant growth /// /// # Arguments @@ -185,55 +212,6 @@ impl PlantGrowth { } } } - - /// Function to "clean" the raw. This is used to remove any empty list or strings, - /// and to remove any default values. By "removing" it means setting the value to None. - /// - /// This also will remove the metadata if `is_metadata_hidden` is true. - /// - /// Steps for all "Option" fields: - /// - Set any metadata to None if `is_metadata_hidden` is true. - /// - Set any empty string to None. - /// - Set any empty list to None. - /// - Set any default values to None. - /// - /// # Returns - /// - /// A new plant growth with all empty or default values removed. - #[must_use] - pub fn cleaned(&self) -> Self { - let mut cleaned = self.clone(); - - if let Some(host_tiles) = &cleaned.host_tiles { - if host_tiles.is_empty() { - cleaned.host_tiles = None; - } - } - - if let Some(tags) = &cleaned.tags { - if tags.is_empty() { - cleaned.tags = None; - } - } - - if default_checks::is_default_trunk_height_percentage(&cleaned.trunk_height_percentage) { - cleaned.trunk_height_percentage = None; - } - if default_checks::is_default_growth_density(cleaned.density) { - cleaned.density = None; - } - if default_checks::is_default_growth_timing(&cleaned.timing) { - cleaned.timing = None; - } - - if let Some(print) = &cleaned.print { - if print.is_empty() { - cleaned.print = None; - } - } - - cleaned - } } impl Searchable for PlantGrowth { diff --git a/lib/src/parsed_definitions/position.rs b/lib/src/parsed_definitions/position.rs index 05b29354..c4e4ab69 100644 --- a/lib/src/parsed_definitions/position.rs +++ b/lib/src/parsed_definitions/position.rs @@ -1,86 +1,98 @@ //! Contains the Position struct and implementation (for government positions) +use dfraw_parser_proc_macros::{Cleanable, IsEmpty}; -use crate::{color::Color, default_checks, name::Name, tags::PositionTag}; +use crate::{color::Color, name::Name, tags::PositionTag}; /// Represents a position in the government of an entity -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + PartialEq, + Eq, + IsEmpty, + Cleanable, +)] #[serde(rename_all = "camelCase")] pub struct Position { identifier: String, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] allowed_classes: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] allowed_creatures: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] appointed_by: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] color: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] commander: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] demand_max: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] execution_skill: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] gender: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] land_holder: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] land_name: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] mandate_max: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] name: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] name_male: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] name_female: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] number: Option, //set -1 for AS_NEEDED - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] precedence: Option, //set -1 for NONE - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] rejected_classes: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] rejected_creatures: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] replaced_by: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] required_bedroom: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] required_boxes: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] required_cabinets: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] required_dining: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] required_office: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] required_racks: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] required_stands: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] required_tomb: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] requires_population: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] responsibilities: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] spouse: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] spouse_female: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] spouse_male: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] squad: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] succession: Option, - #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] tags: Vec, } @@ -97,168 +109,6 @@ impl Position { ..Default::default() } } - - /// Function to "clean" the raw. This is used to remove any empty list or strings, - /// and to remove any default values. By "removing" it means setting the value to None. - /// - /// This also will remove the metadata if `is_metadata_hidden` is true. - /// - /// Steps for all "Option" fields: - /// - Set any metadata to None if `is_metadata_hidden` is true. - /// - Set any empty string to None. - /// - Set any empty list to None. - /// - Set any default values to None. - #[allow(clippy::too_many_lines, clippy::cognitive_complexity)] - #[must_use] - pub fn cleaned(&self) -> Self { - let mut cleaned = self.clone(); - - if let Some(allowed_classes) = &cleaned.allowed_classes { - if allowed_classes.is_empty() { - cleaned.allowed_classes = None; - } - } - - if let Some(allowed_creatures) = &cleaned.allowed_creatures { - if allowed_creatures.is_empty() { - cleaned.allowed_creatures = None; - } - } - if let Some(rejected_classes) = &cleaned.rejected_classes { - if rejected_classes.is_empty() { - cleaned.rejected_classes = None; - } - } - if let Some(appointed_by) = &cleaned.appointed_by { - if appointed_by.is_empty() { - cleaned.appointed_by = None; - } - } - if let Some(color) = &cleaned.color { - if color.is_default() { - cleaned.color = None; - } - } - if let Some(commander) = &cleaned.commander { - if commander.is_empty() { - cleaned.commander = None; - } - } - if default_checks::is_zero_u32(cleaned.demand_max) { - cleaned.demand_max = None; - } - if let Some(execution_skill) = &cleaned.execution_skill { - if execution_skill.is_empty() { - cleaned.execution_skill = None; - } - } - if let Some(gender) = &cleaned.gender { - if gender.is_empty() { - cleaned.gender = None; - } - } - if default_checks::is_zero_u32(cleaned.land_holder) { - cleaned.land_holder = None; - } - if let Some(land_name) = &cleaned.land_name { - if land_name.is_empty() { - cleaned.land_name = None; - } - } - if default_checks::is_zero_u32(cleaned.mandate_max) { - cleaned.mandate_max = None; - } - if let Some(name) = &cleaned.name { - if name.is_empty() { - cleaned.name = None; - } - } - if let Some(name_male) = &cleaned.name_male { - if name_male.is_empty() { - cleaned.name_male = None; - } - } - if let Some(name_female) = &cleaned.name_female { - if name_female.is_empty() { - cleaned.name_female = None; - } - } - if default_checks::is_zero_i32(cleaned.number) { - cleaned.number = None; - } - if default_checks::is_zero_i32(cleaned.precedence) { - cleaned.precedence = None; - } - if let Some(rejected_creatures) = &cleaned.rejected_creatures { - if rejected_creatures.is_empty() { - cleaned.rejected_creatures = None; - } - } - if let Some(replaced_by) = &cleaned.replaced_by { - if replaced_by.is_empty() { - cleaned.replaced_by = None; - } - } - if default_checks::is_zero_u32(cleaned.required_bedroom) { - cleaned.required_bedroom = None; - } - if default_checks::is_zero_u32(cleaned.required_boxes) { - cleaned.required_boxes = None; - } - if default_checks::is_zero_u32(cleaned.required_cabinets) { - cleaned.required_cabinets = None; - } - if default_checks::is_zero_u32(cleaned.required_dining) { - cleaned.required_dining = None; - } - if default_checks::is_zero_u32(cleaned.required_office) { - cleaned.required_office = None; - } - if default_checks::is_zero_u32(cleaned.required_racks) { - cleaned.required_racks = None; - } - if default_checks::is_zero_u32(cleaned.required_stands) { - cleaned.required_stands = None; - } - if default_checks::is_zero_u32(cleaned.required_tomb) { - cleaned.required_tomb = None; - } - if default_checks::is_zero_u32(cleaned.requires_population) { - cleaned.requires_population = None; - } - if let Some(responsibilities) = &cleaned.responsibilities { - if responsibilities.is_empty() { - cleaned.responsibilities = None; - } - } - if let Some(spouse) = &cleaned.spouse { - if spouse.is_empty() { - cleaned.spouse = None; - } - } - if let Some(spouse_female) = &cleaned.spouse_female { - if spouse_female.is_empty() { - cleaned.spouse_female = None; - } - } - if let Some(spouse_male) = &cleaned.spouse_male { - if spouse_male.is_empty() { - cleaned.spouse_male = None; - } - } - if let Some(squad) = &cleaned.squad { - if squad.is_empty() { - cleaned.squad = None; - } - } - if let Some(succession) = &cleaned.succession { - if succession.is_empty() { - cleaned.succession = None; - } - } - - cleaned - } /// Parses a tag and value into the position /// /// # Arguments diff --git a/lib/src/parsed_definitions/seed_material.rs b/lib/src/parsed_definitions/seed_material.rs index b17e2fec..79fd8285 100644 --- a/lib/src/parsed_definitions/seed_material.rs +++ b/lib/src/parsed_definitions/seed_material.rs @@ -1,12 +1,23 @@ //! Seed material definition +use dfraw_parser_proc_macros::IsEmpty; use tracing::warn; use crate::{color::Color, name::Name}; /// A struct representing a seed material #[allow(clippy::module_name_repetitions)] -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + PartialEq, + Eq, + IsEmpty, +)] #[serde(rename_all = "camelCase")] pub struct SeedMaterial { name: Name, diff --git a/lib/src/parsed_definitions/select_creature.rs b/lib/src/parsed_definitions/select_creature.rs index 4356d640..214509a6 100644 --- a/lib/src/parsed_definitions/select_creature.rs +++ b/lib/src/parsed_definitions/select_creature.rs @@ -1,21 +1,33 @@ //! Parsed `SelectCreature` definition +use dfraw_parser_proc_macros::{Cleanable, IsEmpty}; use crate::{ metadata::{ObjectType, RawMetadata}, - traits::{searchable::clean_search_vec, RawObject, Searchable}, - utilities::build_object_id_from_pieces, + traits::{RawObject, Searchable}, + utilities::{build_object_id_from_pieces, clean_search_vec}, }; /// A struct representing a creature selection -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + PartialEq, + Eq, + IsEmpty, + Cleanable, +)] #[serde(rename_all = "camelCase")] pub struct SelectCreature { - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] metadata: Option, identifier: String, object_id: String, - #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] tags: Vec, } impl SelectCreature { @@ -58,37 +70,13 @@ impl SelectCreature { ..Self::default() } } - - /// Function to "clean" the raw. This is used to remove any empty list or strings, - /// and to remove any default values. By "removing" it means setting the value to None. - /// - /// This also will remove the metadata if `is_metadata_hidden` is true. - /// - /// Steps for all "Option" fields: - /// - Set any metadata to None if `is_metadata_hidden` is true. - /// - Set any empty string to None. - /// - Set any empty list to None. - /// - Set any default values to None. - /// - /// # Returns - /// - /// A cleaned `SelectCreature` - #[must_use] - pub fn cleaned(&self) -> Self { - let mut cleaned = self.clone(); - - if let Some(metadata) = &cleaned.metadata { - if metadata.is_hidden() { - cleaned.metadata = None; - } - } - - cleaned - } } #[typetag::serde] impl RawObject for SelectCreature { + fn get_searchable_tokens(&self) -> Vec<&str> { + Vec::new() + } fn get_metadata(&self) -> RawMetadata { self.metadata.as_ref().map_or_else( || { @@ -103,18 +91,12 @@ impl RawObject for SelectCreature { std::clone::Clone::clone, ) } - fn clean_self(&mut self) { - *self = self.cleaned(); - } fn get_identifier(&self) -> &str { &self.identifier } fn get_name(&self) -> &str { &self.identifier } - fn is_empty(&self) -> bool { - false - } fn get_type(&self) -> &ObjectType { &ObjectType::SelectCreature } diff --git a/lib/src/parsed_definitions/shrub.rs b/lib/src/parsed_definitions/shrub.rs index eb759184..39eea5d0 100644 --- a/lib/src/parsed_definitions/shrub.rs +++ b/lib/src/parsed_definitions/shrub.rs @@ -1,17 +1,27 @@ //! Shrub definition and parsing. +use dfraw_parser_proc_macros::IsEmpty; use tracing::{error, warn}; use crate::{ color::Color, - default_checks, raw_definitions::SHRUB_TOKENS, seed_material::SeedMaterial, tags::{SeasonTag, ShrubTag}, }; /// A shrub in the raws. -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + PartialEq, + Eq, + IsEmpty, +)] #[serde(rename_all = "camelCase")] pub struct Shrub { /// Allows the plant to grow in farm plots during the given season. @@ -19,78 +29,89 @@ pub struct Shrub { /// this token will disappear at the beginning of the season. Underground plants grow wild in all seasons, regardless /// of their season tokens. /// Default: empty (plant will not grow in farm plots) - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] growing_season: Option>, /// How long the plant takes to grow to harvest in a farm plot. Unit hundreds of ticks. /// There are 1008 GROWDUR units in a season. Defaults to 300. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = 300)] grow_duration: Option, /// Has no known effect. Previously set the value of the harvested plant. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] value: Option, /// The tile used when the plant is harvested whole, or is ready to be picked from a farm plot. May either be a cp437 /// tile number, or a character between single quotes. See character table. Defaults to 231 (τ). - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = 231)] picked_tile: Option, /// The tile used when a plant harvested whole has wilted. Defaults to 169 (⌐). - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = 169)] dead_picked_tile: Option, /// The tile used to represent this plant when it is wild, alive, and has no growths. Defaults to 34 ("). - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = 34)] shrub_tile: Option, /// The tile used to represent this plant when it is dead in the wild. Defaults to 34 ("). - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = 34)] dead_shrub_tile: Option, /// The maximum stack size collected when gathered via herbalism (possibly also from farm plots?). Defaults to 5. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = 5)] cluster_size: Option, /// The color of the plant when it has been picked whole, or when it is ready for harvest in a farm plot. Defaults to 2:0:0 (dark green). - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = (2,0,0))] picked_color: Option, /// The color of the plant when it has been picked whole, but has wilted. Defaults to 0:0:1 (dark gray). - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = (0,0,1))] dead_picked_color: Option, /// The color of the plant when it is alive, wild, and has no growths. Defaults to 2:0:0 (dark green). - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = (2,0,0))] shrub_color: Option, /// The color of the plant when it is dead in the wild. Defaults to 6:0:0 (brown). - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = (6,0,0))] dead_shrub_color: Option, /// The shrub will drown once the water on its tile reaches this level. Defaults to 4. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = 4)] shrub_drown_level: Option, // Todo: fix these with actual values (materials and seed) /// Names a drink made from the plant, allowing it to be used in entity resources. /// Previously also permitted brewing the plant into alcohol made of this material. /// Now, a `MATERIAL_REACTION_PRODUCT` of type `DRINK_MAT` should be used on the proper plant material. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] drink: Option, /// Permits milling the plant at a quern or millstone into a powder made of this material and allows its use in entity resources. /// Said material should have `[POWDER_MISC_PLANT]` to permit proper stockpiling. This token makes the whole plant harvestable regardless /// of which material is designated for milling. /// For plants with millable growths, use only `MATERIAL_REACTION_PRODUCT` or `ITEM_REACTION_PRODUCT` tokens to define the milling products. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] mill: Option, /// Permits processing the plant at a farmer's workshop to yield threads made of this material and allows its use in entity resources. /// Said material should have `[THREAD_PLANT]` to permit proper stockpiling. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] thread: Option, /// Causes the plant to yield plantable seeds made of this material and having these properties. /// Said material should have `[SEED_MAT]` to permit proper stockpiling. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] seed: Option, /// Permits processing the plant into a vial at a still to yield extract made of this material. /// Said material should have `[EXTRACT_STORAGE:FLASK]`. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] extract_still_vial: Option, /// Permits processing the plant into a vial at a farmer's workshop to yield extract made of this material. /// Said material should have `[EXTRACT_STORAGE:VIAL]`. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] extract_vial: Option, /// Permits processing the plant into a barrel at a farmer's workshop to yield extract made of this material. /// Said material should have `[EXTRACT_STORAGE:BARREL]`. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] extract_barrel: Option, } @@ -113,113 +134,6 @@ impl Shrub { ..Self::default() } } - - /// Function to "clean" the raw. This is used to remove any empty list or strings, - /// and to remove any default values. By "removing" it means setting the value to None. - /// - /// This also will remove the metadata if `is_metadata_hidden` is true. - /// - /// Steps for all "Option" fields: - /// - Set any metadata to None if `is_metadata_hidden` is true. - /// - Set any empty string to None. - /// - Set any empty list to None. - /// - Set any default values to None. - /// - /// # Returns - /// - /// * `Shrub` - The cleaned Shrub - #[must_use] - #[allow(clippy::cognitive_complexity)] - pub fn cleaned(&self) -> Self { - let mut cleaned = self.clone(); - - if let Some(growing_season) = &cleaned.growing_season { - if growing_season.is_empty() { - cleaned.growing_season = None; - } - } - if default_checks::is_default_grow_duration(cleaned.grow_duration) { - cleaned.grow_duration = None; - } - if default_checks::is_zero(cleaned.value) { - cleaned.value = None; - } - if default_checks::is_default_picked_tile(cleaned.picked_tile) { - cleaned.picked_tile = None; - } - if default_checks::is_default_dead_picked_tile(cleaned.dead_picked_tile) { - cleaned.dead_picked_tile = None; - } - if default_checks::is_default_shrub_tile(cleaned.shrub_tile) { - cleaned.shrub_tile = None; - } - if default_checks::is_default_dead_shrub_tile(cleaned.dead_shrub_tile) { - cleaned.dead_shrub_tile = None; - } - if default_checks::is_default_cluster_size(cleaned.cluster_size) { - cleaned.cluster_size = None; - } - if let Some(picked_color) = &cleaned.picked_color { - if picked_color.is_default() { - cleaned.picked_color = None; - } - } - if let Some(dead_picked_color) = &cleaned.dead_picked_color { - if dead_picked_color.is_default() { - cleaned.dead_picked_color = None; - } - } - if let Some(shrub_color) = &cleaned.shrub_color { - if shrub_color.is_default() { - cleaned.shrub_color = None; - } - } - if let Some(dead_shrub_color) = &cleaned.dead_shrub_color { - if dead_shrub_color.is_default() { - cleaned.dead_shrub_color = None; - } - } - if default_checks::is_default_shrub_drown_level(cleaned.shrub_drown_level) { - cleaned.shrub_drown_level = None; - } - if let Some(drink) = &cleaned.drink { - if drink.is_empty() { - cleaned.drink = None; - } - } - if let Some(mill) = &cleaned.mill { - if mill.is_empty() { - cleaned.mill = None; - } - } - if let Some(thread) = &cleaned.thread { - if thread.is_empty() { - cleaned.thread = None; - } - } - if let Some(seed) = &cleaned.seed { - if seed.is_empty() { - cleaned.seed = None; - } - } - if let Some(extract_still_vial) = &cleaned.extract_still_vial { - if extract_still_vial.is_empty() { - cleaned.extract_still_vial = None; - } - } - if let Some(extract_vial) = &cleaned.extract_vial { - if extract_vial.is_empty() { - cleaned.extract_vial = None; - } - } - if let Some(extract_barrel) = &cleaned.extract_barrel { - if extract_barrel.is_empty() { - cleaned.extract_barrel = None; - } - } - - cleaned - } /// Parses a tag and sets the appropriate field. /// /// # Arguments diff --git a/lib/src/parsed_definitions/sprite_graphic.rs b/lib/src/parsed_definitions/sprite_graphic.rs index 95f429cb..87d92c7b 100644 --- a/lib/src/parsed_definitions/sprite_graphic.rs +++ b/lib/src/parsed_definitions/sprite_graphic.rs @@ -1,10 +1,10 @@ //! A module that contains the `SpriteGraphic` struct and its implementation. +use dfraw_parser_proc_macros::IsEmpty; use itertools::Itertools; use tracing::warn; use crate::{ - default_checks, dimensions::Dimensions, raw_definitions::{CONDITION_TOKENS, GRAPHIC_TYPE_TOKENS}, tags::{ColorModificationTag, ConditionTag, GraphicTypeTag}, @@ -12,29 +12,67 @@ use crate::{ /// A struct representing a sprite graphic. #[allow(clippy::module_name_repetitions)] -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + PartialEq, + Eq, + IsEmpty, +)] #[serde(rename_all = "camelCase")] pub struct SpriteGraphic { primary_condition: ConditionTag, tile_page_id: String, offset: Dimensions, #[serde(skip_serializing_if = "Option::is_none")] + #[is_empty(only_if_none)] color: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] large_image: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] offset2: Option, #[serde(skip_serializing_if = "Option::is_none")] + #[is_empty(only_if_none)] secondary_condition: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] color_pallet_swap: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] target_identifier: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] extra_descriptor: Option, } impl SpriteGraphic { + #[must_use] + pub fn get_offset(&self) -> Dimensions { + self.offset + } + #[must_use] + pub fn get_offset2(&self) -> Option { + self.offset2 + } + #[must_use] + pub fn get_primary_condition(&self) -> ConditionTag { + self.primary_condition + } + #[must_use] + pub fn get_secondary_condition(&self) -> ConditionTag { + match self.secondary_condition { + Some(condition) => condition, + None => ConditionTag::None, + } + } + #[must_use] + pub fn get_target_identifier(&self) -> &str { + match self.target_identifier.as_ref() { + Some(target) => target.as_str(), + None => "", + } + } /// Get the tile page ID. /// /// # Returns @@ -65,9 +103,13 @@ impl SpriteGraphic { .unwrap_or(graphic_type); match specific_graphic_type { - GraphicTypeTag::Creature | GraphicTypeTag::CreatureCaste => { + GraphicTypeTag::Creature + | GraphicTypeTag::CreatureCaste + | GraphicTypeTag::StatueCreature + | GraphicTypeTag::StatueCreatureCaste + | GraphicTypeTag::StatuesSurfaceGiant => { // parse creature - Self::parse_creature_from_token(&token) + Self::parse_creature_sprite_from_token(&token) } GraphicTypeTag::Plant => { // parse plant @@ -87,9 +129,6 @@ impl SpriteGraphic { | GraphicTypeTag::ShapeSmallGem => { Self::parse_tile_with_extra_descriptor_from_value(value) } - GraphicTypeTag::StatueCreature - | GraphicTypeTag::StatueCreatureCaste - | GraphicTypeTag::StatuesSurfaceGiant => Self::parse_creature_statue_from_token(&token), GraphicTypeTag::Template | GraphicTypeTag::CustomWorkshop | GraphicTypeTag::AddTool @@ -147,7 +186,7 @@ impl SpriteGraphic { } }; - let offset_x: i32 = match tile_offset_x.parse() { + let offset_x: u32 = match tile_offset_x.parse() { Ok(n) => n, Err(_e) => { warn!( @@ -158,7 +197,7 @@ impl SpriteGraphic { } }; - let offset_y: i32 = match tile_offset_y.parse() { + let offset_y: u32 = match tile_offset_y.parse() { Ok(n) => n, Err(_e) => { warn!( @@ -176,6 +215,7 @@ impl SpriteGraphic { ..Self::default() }) } + #[tracing::instrument] fn parse_tile_with_color_pallet_from_value(value: &str) -> Option { // .[TOOL_GRAPHICS_WOOD: 1: ITEM_BOOKCASE: 0: 0] // ( key color_id tile_page_id offset_x offset_y) @@ -185,12 +225,12 @@ impl SpriteGraphic { Some(v) => match v.parse() { Ok(n) => n, Err(_e) => { - warn!( - "parse_tile_with_color_pallet_from_value: Failed to parse {} as color_id {}", - v, - value - ); - return None; + if v == "ALL" { + 0 + } else { + warn!("Failed to parse {v}"); + return None; + } } }, _ => { @@ -217,7 +257,7 @@ impl SpriteGraphic { } }; - let offset_x: i32 = match tile_offset_x.parse() { + let offset_x: u32 = match tile_offset_x.parse() { Ok(n) => n, Err(_e) => { warn!( @@ -228,7 +268,7 @@ impl SpriteGraphic { } }; - let offset_y: i32 = match tile_offset_y.parse() { + let offset_y: u32 = match tile_offset_y.parse() { Ok(n) => n, Err(_e) => { warn!( @@ -280,7 +320,7 @@ impl SpriteGraphic { // Target identifier is optional let target_identifier = split.join(":"); - let offset_x: i32 = match tile_offset_x.parse() { + let offset_x: u32 = match tile_offset_x.parse() { Ok(n) => n, Err(_e) => { warn!( @@ -291,7 +331,7 @@ impl SpriteGraphic { } }; - let offset_y: i32 = match tile_offset_y.parse() { + let offset_y: u32 = match tile_offset_y.parse() { Ok(n) => n, Err(_e) => { warn!( @@ -347,25 +387,23 @@ impl SpriteGraphic { // Target identifier is optional let target_identifier = split.join(":"); - let offset_x: i32 = match tile_offset_x.parse() { + let offset_x: u32 = match tile_offset_x.parse() { Ok(n) => n, Err(_e) => { warn!( "parse_tile_with_extra_descriptor_from_value: Failed to parse {} as offset_x {}", - tile_offset_x, - value + tile_offset_x, value ); return None; } }; - let offset_y: i32 = match tile_offset_y.parse() { + let offset_y: u32 = match tile_offset_y.parse() { Ok(n) => n, Err(_e) => { warn!( "parse_tile_with_extra_descriptor_from_value: Failed to parse {} as offset_y {}", - tile_offset_y, - value + tile_offset_y, value ); return None; } @@ -379,374 +417,233 @@ impl SpriteGraphic { ..Self::default() }) } - fn parse_creature_statue_from_token(token: &str) -> Option { - // [DEFAULT: STATUES_LAYERED: 0: 0: 0: 1] - // [DEFAULT: STATUES_SURFACE_LARGE: 1: 0: 1: 1] - // condition tile_page_id x1 y1 x2 y2 - let mut split = token.split(':'); - - let condition = match split.next() { - Some(v) => String::from(v), - _ => { - return None; - } - }; - - let x1: i32 = match split.next() { - Some(v) => match v.parse() { - Ok(n) => n, - Err(_e) => { - warn!( - "parse_creature_statue_from_token: Failed to parse {} as x1 {}", - v, token - ); - return None; - } - }, - _ => { - return None; - } - }; - let y1: i32 = match split.next() { - Some(v) => match v.parse() { - Ok(n) => n, - Err(_e) => { - warn!( - "parse_creature_statue_from_token: Failed to parse {} as y1 {}", - v, token - ); - return None; - } - }, - _ => { - return None; - } - }; - let x2: i32 = match split.next() { - Some(v) => match v.parse() { - Ok(n) => n, - Err(_e) => { - warn!( - "parse_creature_statue_from_token: Failed to parse {} as x2 {}", - v, token - ); - return None; - } - }, - _ => { - return None; - } - }; - let y2: i32 = match split.next() { - Some(v) => match v.parse() { - Ok(n) => n, - Err(_e) => { - warn!( - "parse_creature_statue_from_token: Failed to parse {} as y2 {}", - v, token - ); - return None; - } - }, - _ => { - return None; - } - }; - - let primary_condition = ConditionTag::from_token(condition.as_str()).unwrap_or_else(|| { - warn!( - "parse_creature_statue_from_token: Failed to parse {} as primary_condition in {}", - condition, token - ); - ConditionTag::None - }); - - Some(Self { - primary_condition, - tile_page_id: String::from("STATUES"), - offset: Dimensions::from_xy(x1, y1), - offset2: Some(Dimensions::from_xy(x2, y2)), - ..Self::default() - }) - } - fn parse_creature_from_token(token: &str) -> Option { - // [:::::] - // 0 1 2 3 4 5 - // [::LARGE_IMAGE::::::] - // 0 1 2 3 4 5 6 7 8 - - // Based on the length of the split, we can determine if this is a large image or not - let mut split = token.split(':'); - - let condition = match split.next() { - Some(v) => String::from(v), - _ => { - return None; - } - }; - - let tile_page_id = match split.next() { - Some(v) => String::from(v), - _ => { - return None; - } - }; - - let fourth_position_token = match split.next() { - Some(v) => String::from(v), - _ => { - return None; - } - }; - - let large_image = matches!(fourth_position_token.as_str(), "LARGE_IMAGE"); - - if large_image { - return Self::parse_large_creature_with_split( - condition.as_str(), - tile_page_id.as_str(), - split.collect::>().as_slice(), - ); - } - - // x1 actually is parsed from fourth_position_token - let x1: i32 = match fourth_position_token.parse() { - Ok(n) => n, - Err(_e) => { - warn!( - "parse_creature_from_token: Failed to parse {} as x1 {}", - fourth_position_token, token - ); - return None; - } - }; - let y1: i32 = match split.next() { - Some(v) => match v.parse() { - Ok(n) => n, - Err(_e) => { - warn!( - "parse_creature_from_token: Failed to parse {} as y1 {}", - v, token - ); - return None; - } - }, - _ => { - return None; - } - }; - - let color = split.next().map_or(ColorModificationTag::AsIs, |v| { - ColorModificationTag::from_token(v) - }); - - let primary_condition = ConditionTag::from_token(condition.as_str()).unwrap_or_else(|| { - warn!( - "Failed to parse {} as primary_condition in {}", - condition, token - ); - ConditionTag::None - }); - - let secondary_condition = split.next().map_or(ConditionTag::None, |v| { - ConditionTag::from_token(v).unwrap_or_else(|| { - warn!("Failed to parse {} as secondary_condition in {}", v, token); - ConditionTag::None - }) - }); - - if primary_condition == ConditionTag::None { - warn!( - "Failed to parse {} as primary_condition large_animal_sprite {}", - condition, tile_page_id - ); + /// Parses a sprite graphic for anything about a creature. + /// + /// Takes the key condition and uses it to confirm we know how to parse it. + #[tracing::instrument] + fn parse_creature_sprite_from_token(token: &str) -> Option { + let mut tokens: Vec<&str> = token.split(':').rev().collect(); + // reversed token list, so pop will remove the first token + let first_token = tokens.pop().unwrap_or_default(); + let Some(key_condition) = CONDITION_TOKENS.get(first_token) else { + warn!("no condition token found '{first_token}'"); return None; - } - - Some(Self { - primary_condition, - tile_page_id, - offset: Dimensions::from_xy(x1, y1), - color: Some(color), - secondary_condition: Some(secondary_condition), - ..Self::default() - }) - } - #[allow(clippy::too_many_lines)] - fn parse_large_creature_with_split( - condition: &str, - tile_page_id: &str, - split: &[&str], - ) -> Option { - // [::LARGE_IMAGE::::::] - // 0 1 2 3 4 5 6 7 8 - let x1: i32 = match split.first() { - Some(v) => match v.parse() { - Ok(n) => n, - Err(_e) => { - warn!( - "parse_large_creature_with_split: Failed to parse {} as offset_x1 {:?}", - v, split - ); - return None; - } - }, - _ => { - return None; - } - }; - - let y1: i32 = match split.get(1) { - Some(v) => match v.parse() { - Ok(n) => n, - Err(_e) => { - warn!( - "parse_large_creature_with_split: Failed to parse {} as offset_y1 {:?}", - v, split - ); - return None; - } - }, - _ => { - return None; - } - }; - - let x2: i32 = match split.get(2) { - Some(v) => match v.parse() { - Ok(n) => n, - Err(_e) => { - warn!( - "parse_large_creature_with_split: Failed to parse {} as offset_x2 {:?}", - v, split - ); - return None; - } - }, - _ => { - return None; - } - }; - - let y2: i32 = match split.get(3) { - Some(v) => match v.parse() { - Ok(n) => n, - Err(_e) => { - warn!( - "parse_large_creature_with_split: Failed to parse {} as offset_y2 {:?}", - v, split - ); - return None; - } - }, - _ => { - return None; - } }; + tokens.reverse(); + let token_str = tokens.as_slice().join(":"); - let color = split.get(4).map_or(ColorModificationTag::AsIs, |v| { - ColorModificationTag::from_token(v) - }); - - let primary_condition = ConditionTag::from_token(condition).unwrap_or_else(|| { - warn!( - "Failed to parse {} as primary_condition in {}", - condition, - split.join(":") - ); - ConditionTag::None - }); - - let secondary_condition = split.get(5).map_or(ConditionTag::None, |v| { - ConditionTag::from_token(v).unwrap_or_else(|| { - warn!( - "Failed to parse {} as secondary_condition in {}", - v, - split.join(":") - ); - ConditionTag::None - }) - }); - - if primary_condition == ConditionTag::None { - warn!( - "Failed to parse {} as primary_condition large_animal_sprite {}", - condition, tile_page_id - ); - return None; - } - - Some(Self { - primary_condition, - tile_page_id: String::from(tile_page_id), - offset: Dimensions::from_xy(x1, y1), - color: Some(color), - large_image: Some(true), - offset2: Some(Dimensions::from_xy(x2, y2)), - secondary_condition: Some(secondary_condition), - ..Self::default() - }) + Self::parse_creature_sprite_from_keyed_token(key_condition, &token_str) } - /// Function to "clean" the creature. This is used to remove any empty list or strings, - /// and to remove any default values. By "removing" it means setting the value to None. + /// Parses a sprite graphic that has one of the 6 accepted basic condition tags: + /// `DEFAULT`,`CHILD`,`ANIMATED`,`CORPSE`,`LIST_ICON`, or `CDI_LIST_ICON` /// - /// This also will remove the metadata if `is_metadata_hidden` is true. + /// ## Default Sprite Example: /// - /// Steps: - /// - Set any metadata to None if `is_metadata_hidden` is true. - /// - Set any empty string to None. - /// - Set any empty list to None. - /// - Set any default values to None. + /// ```txt + /// [CREATURE_GRAPHICS:GORLAK] + /// [DEFAULT:CREATURES_UNDERGROUND:0:11:AS_IS] + /// [key-----0---------------------1-2--3----] len: 4 + /// [DEFAULT:CREATURES_UNDERGROUND:0:11:AS_IS:CORPSE] + /// [key-----0---------------------1-2--3-----4-----] len: 5 + /// ``` /// - /// # Returns + /// ## Large Sprite Example: /// - /// A new sprite graphic with the cleaned values. - #[must_use] - pub fn cleaned(&self) -> Self { - let mut cleaned = self.clone(); + /// ```txt + /// [CREATURE_GRAPHICS:CAVE_DRAGON] + /// [DEFAULT:CREATURES_UNDERGROUND_LARGE:LARGE_IMAGE:0:0:2:1:AS_IS] + /// [key-----0---------------------------1-----------2-3-4-5-6----] len: 7 + /// [DEFAULT:CREATURES_UNDERGROUND_LARGE:LARGE_IMAGE:0:0:2:1:AS_IS:CHILD] + /// [key-----0---------------------------1-----------2-3-4-5-6-----7----] len: 8 + /// ``` + /// + /// Alternatively, statues are tall and so define it as a larger size without the indicator + /// flag `LARGE_IMAGE`. + /// + /// ```txt + /// [STATUE_CREATURE_GRAPHICS:SERPENT_MAN] + /// [DEFAULT:STATUES_UNDERGROUND_CIV:0:8:0:9] + /// [key-----0-----------------------1-2-3-4] len: 5 + /// ``` + /// + /// ## List Icon Example: + /// + /// Typically only exists if it is a giant/oddly sized normal sprite that wouldn't fit. + /// + /// ```txt + /// [LIST_ICON:CREATURES_SURFACE_GIANT:10:54] + /// [LIST_ICON:CREATURES_ANIMAL_PEOPLE_TALL:2:76] + /// [key-------0----------------------------1-2-] len: 3 + /// ``` + /// + /// ## CDI List Icon Example: + /// + /// An interaction list icon. Supercedes any list icon for the interaction list. Also replaces the normal + /// sprite if a CDI_LIST_ICON exists for specific interaction. + /// + /// ```txt + /// [CREATURE_GRAPHICS:ELEMENTMAN_FIRE] + /// [CDI_LIST_ICON:HURL_FIREBALL:CREATURE_ABILITY_LIST_ICONS:20:0:4:3] + /// [CDI_LIST_ICON:SPRAY_FIRE_JET:CREATURE_ABILITY_LIST_ICONS:24:0:4:3] + /// [key-----------0--------------1---------------------------2--3-4-5] len: 6 + /// ``` + #[tracing::instrument] + fn parse_creature_sprite_from_keyed_token( + key_condition: &ConditionTag, + token: &str, + ) -> Option { + let primary_condition = *key_condition; + let tokens: Vec<&str> = token.split(':').collect(); + let num_args = tokens.len(); + + match num_args { + // List Icon or Creature without color + 3 => { + if key_condition == &ConditionTag::ListIcon { + return Some(Self { + primary_condition, + // pos 1 (idx 0) + tile_page_id: String::from(*tokens.first().unwrap_or(&"UNKNOWN")), + // pos 2-3 (idx 1-2) + offset: Dimensions::from_two_tokens( + tokens.get(1).unwrap_or(&"0"), + tokens.get(2).unwrap_or(&"0"), + ), + ..Default::default() + }); + } - // Set any empty string to None. - if let Some(extra_descriptor) = cleaned.extra_descriptor.as_ref() { - if extra_descriptor.is_empty() { - cleaned.extra_descriptor = None; + Some(Self { + primary_condition, + // pos 1 (idx 0) + tile_page_id: String::from(*tokens.first().unwrap_or(&"UNKNOWN")), + // pos 2-3 (idx 1-2) + offset: Dimensions::from_two_tokens( + tokens.get(1).unwrap_or(&"0"), + tokens.get(2).unwrap_or(&"0"), + ), + ..Default::default() + }) } - } - - // Set any empty string to None. - if let Some(target_identifier) = cleaned.target_identifier.as_ref() { - if target_identifier.is_empty() { - cleaned.target_identifier = None; + // Only creature + 4 => { + Some(Self { + primary_condition, + // pos 1 (idx 0) + tile_page_id: String::from(*tokens.first().unwrap_or(&"UNKNOWN")), + // pos 2-3 (idx 1-2) + offset: Dimensions::from_two_tokens( + tokens.get(1).unwrap_or(&"0"), + tokens.get(2).unwrap_or(&"0"), + ), + // pos 4 (idx 3) + // always is AS_IS (as of df53.08) + color: Some(ColorModificationTag::AsIs), + ..Default::default() + }) } - } + // Can be creature sprite + 2nd condition or statue + 5 => { + // If the final token is not `u32` then we have a secondary condition. + if tokens.get(4).unwrap_or(&"!").parse::().is_err() { + // pos 5 (idx 4) (only sometimes present) + let secondary_condition: Option = + CONDITION_TOKENS.get(tokens.get(4).unwrap_or(&"")).copied(); + return Some(Self { + primary_condition, + // pos 1 (idx 0) + tile_page_id: String::from(*tokens.first().unwrap_or(&"UNKNOWN")), + // pos 2-3 (idx 1-2) + offset: Dimensions::from_two_tokens( + tokens.get(1).unwrap_or(&"0"), + tokens.get(2).unwrap_or(&"0"), + ), + // pos 4 (idx 3) + // always is AS_IS (as of df53.08) + color: Some(ColorModificationTag::AsIs), + // pos 5 (idx 4) (only sometimes present) + secondary_condition, + ..Default::default() + }); + } - // Set any default values to None. - if let Some(color) = cleaned.color.as_ref() { - if color.is_default() { - cleaned.color = None; + // Tall/wide Sprite + // Used for statues (tall) + Some(Self { + primary_condition, + // pos 1 (idx 0) + tile_page_id: String::from(*tokens.first().unwrap_or(&"UNKNOWN")), + // pos 2-3 (idx 1-2) + offset: Dimensions::from_two_tokens( + tokens.get(1).unwrap_or(&"0"), + tokens.get(2).unwrap_or(&"0"), + ), + // pos 4 (idx 3) + // always is AS_IS (as of df53.08) + color: Some(ColorModificationTag::AsIs), + ..Default::default() + }) } - } - - // Set any default values to None. - if let Some(offset2) = cleaned.offset2.as_ref() { - if offset2.is_empty() { - cleaned.offset2 = None; + // Only cdi list icon + 6 => { + Some(Self { + primary_condition, + // pos 1 (idx 0) + extra_descriptor: Some(String::from(*tokens.first().unwrap_or(&"UNKNOWN"))), + // pos 2 (idx 1) + tile_page_id: String::from(*tokens.get(1).unwrap_or(&"UNKNOWN")), + // pos 3-4 (idx 2-3) + offset: Dimensions::from_two_tokens( + tokens.get(2).unwrap_or(&"0"), + tokens.get(3).unwrap_or(&"0"), + ), + // pos 5-6 (idx 4-5) + offset2: Some(Dimensions::from_two_tokens( + tokens.get(4).unwrap_or(&"0"), + tokens.get(5).unwrap_or(&"0"), + )), + ..Default::default() + }) } - } - - // Set any default values to None. - if let Some(secondary_condition) = cleaned.secondary_condition.as_ref() { - if secondary_condition.is_none() { - cleaned.secondary_condition = None; + // Only large creature (and lrg + 2nd cond) + 7 | 8 => { + // pos 8 (idx 7) (only sometimes present) + let secondary_condition: Option = if num_args == 8 { + CONDITION_TOKENS.get(tokens.get(7).unwrap_or(&"")).copied() + } else { + None + }; + // Large Sprite + Some(Self { + primary_condition, + // pos 1 (idx 0) + tile_page_id: String::from(*tokens.first().unwrap_or(&"UNKNOWN")), + // pos 2 (idx 1) + // literally: LARGE_IMAGE (discarded) + large_image: Some(true), + // pos 3-4 (idx 2-3) + offset: Dimensions::from_two_tokens( + tokens.get(2).unwrap_or(&"0"), + tokens.get(3).unwrap_or(&"0"), + ), + // pos 5-6 (idx 4-5) + offset2: Some(Dimensions::from_two_tokens( + tokens.get(4).unwrap_or(&"0"), + tokens.get(5).unwrap_or(&"0"), + )), + // pos 7 (idx 6) + // always is AS_IS (as of df53.08) + color: Some(ColorModificationTag::AsIs), + secondary_condition, + ..Default::default() + }) + } + _ => { + warn!("Fell through to none"); + None } } - - // Set any default values to None. - if default_checks::is_zero(cleaned.color_pallet_swap) { - cleaned.color_pallet_swap = None; - } - - cleaned } } diff --git a/lib/src/parsed_definitions/sprite_layer.rs b/lib/src/parsed_definitions/sprite_layer.rs index e1d93d0d..e896873a 100644 --- a/lib/src/parsed_definitions/sprite_layer.rs +++ b/lib/src/parsed_definitions/sprite_layer.rs @@ -1,26 +1,49 @@ //! Contains the `SpriteLayer` struct and associated functions. +use dfraw_parser_proc_macros::IsEmpty; use tracing::warn; use crate::{dimensions::Dimensions, raw_definitions::CONDITION_TOKENS, tags::ConditionTag}; /// A struct representing a `SpriteLayer` object. #[allow(clippy::module_name_repetitions)] -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + PartialEq, + Eq, + IsEmpty, +)] #[serde(rename_all = "camelCase")] pub struct SpriteLayer { layer_name: String, tile_page_id: String, offset: Dimensions, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] offset_2: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] large_image: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] conditions: Option>, } impl SpriteLayer { + #[must_use] + pub fn get_offset(&self) -> Dimensions { + self.offset + } + #[must_use] + pub fn get_offset2(&self) -> Option { + self.offset_2 + } + #[must_use] + pub fn get_name(&self) -> String { + self.layer_name.clone() + } /// Returns the `tile_page_id` of the `SpriteLayer`. /// /// # Returns @@ -48,10 +71,6 @@ impl SpriteLayer { conditions.push((*condition, String::from(value))); } } else { - // Manually avoid ISSUE_MIN_LENGTH which is a typo in one of the mods.. This hack should be removed once the mod is fixed. - if key == "ISSUE_MIN_LENGTH" { - return; - } warn!( "Failed to parse {} as LayerCondition, unknown key {}", value, key @@ -109,7 +128,7 @@ impl SpriteLayer { } }; - let offset_x: i32 = match fourth_position_token.parse() { + let offset_x: u32 = match fourth_position_token.parse() { Ok(n) => n, Err(_e) => { warn!( @@ -120,7 +139,7 @@ impl SpriteLayer { } }; - let offset_y: i32 = match tile_offset_y.parse() { + let offset_y: u32 = match tile_offset_y.parse() { Ok(n) => n, Err(_e) => { warn!( @@ -155,7 +174,7 @@ impl SpriteLayer { tile_page_id: &str, split: &[&str], ) -> Option { - let x1: i32 = match split.first() { + let x1: u32 = match split.first() { Some(v) => match v.parse() { Ok(n) => n, Err(_e) => { @@ -171,7 +190,7 @@ impl SpriteLayer { } }; - let y1: i32 = match split.get(1) { + let y1: u32 = match split.get(1) { Some(v) => match v.parse() { Ok(n) => n, Err(_e) => { @@ -187,7 +206,7 @@ impl SpriteLayer { } }; - let x2: i32 = match split.get(2) { + let x2: u32 = match split.get(2) { Some(v) => match v.parse() { Ok(n) => n, Err(_e) => { @@ -203,7 +222,7 @@ impl SpriteLayer { } }; - let y2: i32 = match split.get(3) { + let y2: u32 = match split.get(3) { Some(v) => match v.parse() { Ok(n) => n, Err(_e) => { @@ -228,30 +247,4 @@ impl SpriteLayer { ..Self::default() }) } - /// Function to "clean" the creature. This is used to remove any empty list or strings, - /// and to remove any default values. By "removing" it means setting the value to None. - /// - /// This also will remove the metadata if `is_metadata_hidden` is true. - /// - /// Steps for all "Option" fields: - /// - Set any metadata to None if `is_metadata_hidden` is true. - /// - Set any empty string to None. - /// - Set any empty list to None. - /// - Set any default values to None. - /// - /// # Returns - /// - /// * `SpriteLayer` - The cleaned `SpriteLayer`. - #[must_use] - pub fn cleaned(&self) -> Self { - let mut cleaned = self.clone(); - - if let Some(conditions) = &cleaned.conditions { - if conditions.is_empty() { - cleaned.conditions = None; - } - } - - cleaned - } } diff --git a/lib/src/parsed_definitions/state_names.rs b/lib/src/parsed_definitions/state_names.rs index 3f76aaab..46b35b41 100644 --- a/lib/src/parsed_definitions/state_names.rs +++ b/lib/src/parsed_definitions/state_names.rs @@ -1,7 +1,19 @@ //! State names for materials +use dfraw_parser_proc_macros::{Cleanable, IsEmpty}; /// Represents the name of a materials 3 states (solid, liquid, gas) -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + PartialEq, + Eq, + IsEmpty, + Cleanable, +)] #[serde(rename_all = "camelCase", rename = "StateName")] pub struct StateNames { solid: String, diff --git a/lib/src/parsed_definitions/steam_data.rs b/lib/src/parsed_definitions/steam_data.rs index b4bb300a..49d13678 100644 --- a/lib/src/parsed_definitions/steam_data.rs +++ b/lib/src/parsed_definitions/steam_data.rs @@ -1,43 +1,28 @@ //! Additional data specific to the steam workshop that may be included in the `info.txt` file for a raw module. +use dfraw_parser_proc_macros::IsEmpty; use serde::{Deserialize, Serialize}; /// The additional data specific to the steam workshop -#[derive(Serialize, Deserialize, Default, Clone, Debug, specta::Type)] +#[derive(Serialize, Deserialize, Default, Clone, Debug, specta::Type, PartialEq, Eq, IsEmpty)] #[serde(rename_all = "camelCase")] pub struct SteamData { - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] title: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] description: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] tags: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] key_value_tags: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] metadata: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] changelog: Option, file_id: u64, } impl SteamData { - /// Returns whether the steam data is empty - /// - /// # Returns - /// - /// * `true` if the steam data is empty, `false` otherwise. - #[allow(dead_code)] - #[must_use] - pub const fn is_empty(&self) -> bool { - self.title.is_none() - && self.description.is_none() - && self.tags.is_none() - && self.key_value_tags.is_none() - && self.metadata.is_none() - && self.changelog.is_none() - && self.file_id == 0 - } /// Sets the title of the steam data /// /// # Arguments @@ -46,6 +31,9 @@ impl SteamData { pub fn set_title(&mut self, title: &str) { self.title = Some(String::from(title)); } + pub fn get_title(&self) -> Option { + self.title.clone() + } /// Sets the description of the steam data /// /// # Arguments @@ -54,6 +42,9 @@ impl SteamData { pub fn set_description(&mut self, description: &str) { self.description = Some(String::from(description)); } + pub fn get_description(&self) -> Option { + self.description.clone() + } /// Sets the changelog of the steam data /// /// # Arguments @@ -62,6 +53,9 @@ impl SteamData { pub fn set_changelog(&mut self, changelog: &str) { self.changelog = Some(String::from(changelog)); } + pub fn get_changelog(&self) -> Option { + self.changelog.clone() + } /// Sets the file id of the steam data /// /// # Arguments @@ -70,6 +64,9 @@ impl SteamData { pub fn set_file_id(&mut self, file_id: u64) { self.file_id = file_id; } + pub fn get_file_id(&self) -> u64 { + self.file_id + } /// Adds a tag to the steam data /// /// # Arguments @@ -84,6 +81,9 @@ impl SteamData { tags.push(String::from(tag)); } } + pub fn get_tags(&self) -> Option> { + self.tags.clone() + } /// Adds a key value tag to the steam data /// /// # Arguments diff --git a/lib/src/parsed_definitions/syndrome.rs b/lib/src/parsed_definitions/syndrome.rs index f7efe9db..6a3136ae 100644 --- a/lib/src/parsed_definitions/syndrome.rs +++ b/lib/src/parsed_definitions/syndrome.rs @@ -1,44 +1,55 @@ //! Syndrome struct and implementation +use dfraw_parser_proc_macros::IsEmpty; use tracing::{debug, warn}; use crate::{ - default_checks, raw_definitions::{CREATURE_EFFECT_TOKENS, SYNDROME_TOKENS}, tags::SyndromeTag, - traits::{searchable::clean_search_vec, Searchable}, + traits::Searchable, + utilities::clean_search_vec, }; /// A struct representing a syndrome -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + PartialEq, + Eq, + IsEmpty, +)] #[serde(rename_all = "camelCase")] pub struct Syndrome { /// Seen the `[SYN_IDENTIFIER:INEBRIATION]` tag in `material_templates.txt` - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] identifier: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] name: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] affected_classes: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] immune_classes: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] affected_creatures: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] immune_creatures: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] classes: Option>, /// Seen the `[SYN_CONCENTRATION_ADDED:100:1000]` tag in `material_templates.txt` /// default is 0:0 - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] concentration_added: Option<[u32; 2]>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] tags: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] conditions: Option>, } @@ -68,72 +79,6 @@ impl Syndrome { ..Self::default() } } - - /// Function to "clean" the raw. This is used to remove any empty list or strings, - /// and to remove any default values. By "removing" it means setting the value to None. - /// - /// This also will remove the metadata if `is_metadata_hidden` is true. - /// - /// Steps for all "Option" fields: - /// - Set any metadata to None if `is_metadata_hidden` is true. - /// - Set any empty string to None. - /// - Set any empty list to None. - /// - Set any default values to None. - #[must_use] - pub fn cleaned(&self) -> Self { - let mut cleaned = self.clone(); - - if let Some(identifier) = &cleaned.identifier { - if identifier.is_empty() { - cleaned.identifier = None; - } - } - if let Some(name) = &cleaned.name { - if name.is_empty() { - cleaned.name = None; - } - } - if let Some(affected_classes) = &cleaned.affected_classes { - if affected_classes.is_empty() { - cleaned.affected_classes = None; - } - } - if let Some(immune_classes) = &cleaned.immune_classes { - if immune_classes.is_empty() { - cleaned.immune_classes = None; - } - } - if let Some(affected_creatures) = &cleaned.affected_creatures { - if affected_creatures.is_empty() { - cleaned.affected_creatures = None; - } - } - if let Some(immune_creatures) = &cleaned.immune_creatures { - if immune_creatures.is_empty() { - cleaned.immune_creatures = None; - } - } - if let Some(classes) = &cleaned.classes { - if classes.is_empty() { - cleaned.classes = None; - } - } - if default_checks::min_max_is_zeroes(&cleaned.concentration_added) { - cleaned.concentration_added = None; - } - if let Some(tags) = &cleaned.tags { - if tags.is_empty() { - cleaned.tags = None; - } - } - if let Some(conditions) = &cleaned.conditions { - if conditions.is_empty() { - cleaned.conditions = None; - } - } - - cleaned - } /// Parses a tag into the Syndrome struct /// /// # Arguments diff --git a/lib/src/parsed_definitions/tags/biome.rs b/lib/src/parsed_definitions/tags/biome.rs index dffb506c..293760c5 100644 --- a/lib/src/parsed_definitions/tags/biome.rs +++ b/lib/src/parsed_definitions/tags/biome.rs @@ -186,89 +186,108 @@ pub enum BiomeTag { impl std::fmt::Display for BiomeTag { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Mountain => write!(f, "Mountain"), - Self::Mountains => write!(f, "Mountains"), - Self::Glacier => write!(f, "Glacier"), - Self::Tundra => write!(f, "Tundra"), - Self::SwampTemperateFreshwater => write!(f, "Temperate Freshwater Swamp"), - Self::SwampTemperateSaltwater => write!(f, "Temperate Saltwater Swamp"), - Self::MarshTemperateFreshwater => write!(f, "Temperate Freshwater Marsh"), - Self::MarshTemperateSaltwater => write!(f, "Temperate Saltwater Marsh"), - Self::SwampTropicalFreshwater => write!(f, "Tropical Freshwater Swamp"), - Self::SwampTropicalSaltwater => write!(f, "Tropical Saltwater Swamp"), - Self::SwampMangrove => write!(f, "Mangrove Swamp"), - Self::MarshTropicalFreshwater => write!(f, "Tropical Freshwater Marsh"), - Self::MarshTropicalSaltwater => write!(f, "Tropical Saltwater Marsh"), - Self::ForestTaiga => write!(f, "Taiga Forest"), - Self::Taiga => write!(f, "Taiga"), - Self::ForestTemperateConifer => write!(f, "Temperate Coniferous Forest"), - Self::ForestTemperateBroadleaf => write!(f, "Temperate Broadleaf Forest"), - Self::ForestTropicalConifer => write!(f, "Tropical Coniferous Forest"), - Self::ForestTropicalDryBroadleaf => write!(f, "Tropical Dry Broadleaf Forest"), - Self::ForestTropicalMoistBroadleaf => write!(f, "Tropical Moist Broadleaf Forest"), - Self::GrasslandTemperate => write!(f, "Temperate Grassland"), - Self::SavannaTemperate => write!(f, "Temperate Savanna"), - Self::ShrublandTemperate => write!(f, "Temperate Shrubland"), - Self::GrasslandTropical => write!(f, "Tropical Grassland"), - Self::SavannaTropical => write!(f, "Tropical Savanna"), - Self::ShrublandTropical => write!(f, "Tropical Shrubland"), - Self::DesertBadland => write!(f, "Badlands"), - Self::DesertRock => write!(f, "Rocky Wasteland"), - Self::DesertSand => write!(f, "Sand Desert"), - Self::OceanTropical => write!(f, "Tropical Ocean"), - Self::OceanTemperate => write!(f, "Temperate Ocean"), - Self::OceanArctic => write!(f, "Arctic Ocean"), - Self::PoolTemperateFreshwater => write!(f, "Temperate Freshwater Pool"), - Self::PoolTemperateBrackishwater => write!(f, "Temperate Brackish Pool"), - Self::PoolTemperateSaltwater => write!(f, "Temperate Saltwater Pool"), - Self::PoolTropicalFreshwater => write!(f, "Tropical Freshwater Pool"), - Self::PoolTropicalBrackishwater => write!(f, "Tropical Brackish Pool"), - Self::PoolTropicalSaltwater => write!(f, "Tropical Saltwater Pool"), - Self::LakeTemperateFreshwater => write!(f, "Temperate Freshwater Lake"), - Self::LakeTemperateBrackishwater => write!(f, "Temperate Brackish Lake"), - Self::LakeTemperateSaltwater => write!(f, "Temperate Saltwater Lake"), - Self::LakeTropicalFreshwater => write!(f, "Tropical Freshwater Lake"), - Self::LakeTropicalBrackishwater => write!(f, "Tropical Brackish Lake"), - Self::LakeTropicalSaltwater => write!(f, "Tropical Saltwater Lake"), - Self::RiverTemperateFreshwater => write!(f, "Temperate Freshwater River"), - Self::RiverTemperateBrackishwater => write!(f, "Temperate Brackish River"), - Self::RiverTemperateSaltwater => write!(f, "Temperate Saltwater River"), - Self::RiverTropicalFreshwater => write!(f, "Tropical Freshwater River"), - Self::RiverTropicalBrackishwater => write!(f, "Tropical Brackish River"), - Self::RiverTropicalSaltwater => write!(f, "Tropical Saltwater River"), - Self::SubterraneanWater => write!(f, "Underground caverns (in water)"), - Self::SubterraneanChasm => write!(f, "Underground caverns (out of water)"), - Self::SubterraneanLava => write!(f, "Magma sea"), - Self::AllMain => write!(f, "All biomes excluding pools, rivers, and underground features"), - Self::AnyLand => write!(f, "All main biomes excluding oceans and lakes"), - Self::AnyOcean => write!(f, "All ocean biomes"), - Self::AnyLake => write!(f, "All lake biomes"), - Self::AnyTemperateLake => write!(f, "All temperate lake biomes"), - Self::AnyTropicalLake => write!(f, "All tropical lake biomes"), - Self::AnyRiver => write!(f, "All river biomes"), - Self::AnyTemperateRiver => write!(f, "All temperate river biomes"), - Self::AnyTropicalRiver => write!(f, "All tropical river biomes"), - Self::AnyPool => write!(f, "All pool biomes"), - Self::NotFreezing => write!(f, "All land biomes excluding Mountain, Glacier, and Tundra"), - Self::AnyTemperate => write!(f, "All Temperate land biomes - marshes, swamps, forests, grassland, savanna, and shrubland"), - Self::AnyTropical => write!(f, "All Tropical land biomes - marshes, swamps (including Mangrove), forests, grassland, savanna, and shrubland"), - Self::AnyForest => write!(f, "All Forest biomes (excluding Taiga)"), - Self::AnyShrubland => write!(f, "Temperate and Tropical Shrubland"), - Self::AnyGrassland => write!(f, "Temperate and Tropical Grassland"), - Self::AnySavanna => write!(f, "Temperate and Tropical Savanna"), - Self::AnyTemperateForest => write!(f, "Temperate Coniferous and Broadleaf Forests"), - Self::AnyTropicalForest => write!(f, "Tropical Coniferous and Dry/Moist Broadleaf Forests"), - Self::AnyTemperateBroadleaf => write!(f, "Temperate Broadleaf Forest, Grassland/Savanna/Shrubland, Swamps, and Marshes"), - Self::AnyTropicalBroadleaf => write!(f, "Tropical Dry/Moist Broadleaf Forest, Grassland/Savanna/Shrubland, Swamps (including Mangrove), and Marshes"), - Self::AnyWetland => write!(f, "All swamps and marshes"), - Self::AnyTemperateWetland => write!(f, "All temperate swamps and marshes"), - Self::AnyTropicalWetland => write!(f, "All tropical swamps and marshes"), - Self::AnyTropicalMarsh => write!(f, "All tropical marshes"), - Self::AnyTemperateMarsh => write!(f, "All temperate marshes"), - Self::AnyTropicalSwamp => write!(f, "All tropical swamps (including Mangrove)"), - Self::AnyTemperateSwamp => write!(f, "All temperate swamps"), - Self::AnyDesert => write!(f, "Badlands, Rocky Wasteland, and Sand Desert"), - Self::Unknown => write!(f, "Unknown"), + Self::Mountain => write!(f, "Mountain"), + Self::Mountains => write!(f, "Mountains"), + Self::Glacier => write!(f, "Glacier"), + Self::Tundra => write!(f, "Tundra"), + Self::SwampTemperateFreshwater => write!(f, "Temperate Freshwater Swamp"), + Self::SwampTemperateSaltwater => write!(f, "Temperate Saltwater Swamp"), + Self::MarshTemperateFreshwater => write!(f, "Temperate Freshwater Marsh"), + Self::MarshTemperateSaltwater => write!(f, "Temperate Saltwater Marsh"), + Self::SwampTropicalFreshwater => write!(f, "Tropical Freshwater Swamp"), + Self::SwampTropicalSaltwater => write!(f, "Tropical Saltwater Swamp"), + Self::SwampMangrove => write!(f, "Mangrove Swamp"), + Self::MarshTropicalFreshwater => write!(f, "Tropical Freshwater Marsh"), + Self::MarshTropicalSaltwater => write!(f, "Tropical Saltwater Marsh"), + Self::ForestTaiga => write!(f, "Taiga Forest"), + Self::Taiga => write!(f, "Taiga"), + Self::ForestTemperateConifer => write!(f, "Temperate Coniferous Forest"), + Self::ForestTemperateBroadleaf => write!(f, "Temperate Broadleaf Forest"), + Self::ForestTropicalConifer => write!(f, "Tropical Coniferous Forest"), + Self::ForestTropicalDryBroadleaf => write!(f, "Tropical Dry Broadleaf Forest"), + Self::ForestTropicalMoistBroadleaf => write!(f, "Tropical Moist Broadleaf Forest"), + Self::GrasslandTemperate => write!(f, "Temperate Grassland"), + Self::SavannaTemperate => write!(f, "Temperate Savanna"), + Self::ShrublandTemperate => write!(f, "Temperate Shrubland"), + Self::GrasslandTropical => write!(f, "Tropical Grassland"), + Self::SavannaTropical => write!(f, "Tropical Savanna"), + Self::ShrublandTropical => write!(f, "Tropical Shrubland"), + Self::DesertBadland => write!(f, "Badlands"), + Self::DesertRock => write!(f, "Rocky Wasteland"), + Self::DesertSand => write!(f, "Sand Desert"), + Self::OceanTropical => write!(f, "Tropical Ocean"), + Self::OceanTemperate => write!(f, "Temperate Ocean"), + Self::OceanArctic => write!(f, "Arctic Ocean"), + Self::PoolTemperateFreshwater => write!(f, "Temperate Freshwater Pool"), + Self::PoolTemperateBrackishwater => write!(f, "Temperate Brackish Pool"), + Self::PoolTemperateSaltwater => write!(f, "Temperate Saltwater Pool"), + Self::PoolTropicalFreshwater => write!(f, "Tropical Freshwater Pool"), + Self::PoolTropicalBrackishwater => write!(f, "Tropical Brackish Pool"), + Self::PoolTropicalSaltwater => write!(f, "Tropical Saltwater Pool"), + Self::LakeTemperateFreshwater => write!(f, "Temperate Freshwater Lake"), + Self::LakeTemperateBrackishwater => write!(f, "Temperate Brackish Lake"), + Self::LakeTemperateSaltwater => write!(f, "Temperate Saltwater Lake"), + Self::LakeTropicalFreshwater => write!(f, "Tropical Freshwater Lake"), + Self::LakeTropicalBrackishwater => write!(f, "Tropical Brackish Lake"), + Self::LakeTropicalSaltwater => write!(f, "Tropical Saltwater Lake"), + Self::RiverTemperateFreshwater => write!(f, "Temperate Freshwater River"), + Self::RiverTemperateBrackishwater => write!(f, "Temperate Brackish River"), + Self::RiverTemperateSaltwater => write!(f, "Temperate Saltwater River"), + Self::RiverTropicalFreshwater => write!(f, "Tropical Freshwater River"), + Self::RiverTropicalBrackishwater => write!(f, "Tropical Brackish River"), + Self::RiverTropicalSaltwater => write!(f, "Tropical Saltwater River"), + Self::SubterraneanWater => write!(f, "Underground caverns (in water)"), + Self::SubterraneanChasm => write!(f, "Underground caverns (out of water)"), + Self::SubterraneanLava => write!(f, "Magma sea"), + Self::AllMain => write!( + f, + "All biomes excluding pools, rivers, and underground features" + ), + Self::AnyLand => write!(f, "All main biomes excluding oceans and lakes"), + Self::AnyOcean => write!(f, "All ocean biomes"), + Self::AnyLake => write!(f, "All lake biomes"), + Self::AnyTemperateLake => write!(f, "All temperate lake biomes"), + Self::AnyTropicalLake => write!(f, "All tropical lake biomes"), + Self::AnyRiver => write!(f, "All river biomes"), + Self::AnyTemperateRiver => write!(f, "All temperate river biomes"), + Self::AnyTropicalRiver => write!(f, "All tropical river biomes"), + Self::AnyPool => write!(f, "All pool biomes"), + Self::NotFreezing => { + write!(f, "All land biomes excluding Mountain, Glacier, and Tundra") + } + Self::AnyTemperate => write!( + f, + "All Temperate land biomes - marshes, swamps, forests, grassland, savanna, and shrubland" + ), + Self::AnyTropical => write!( + f, + "All Tropical land biomes - marshes, swamps (including Mangrove), forests, grassland, savanna, and shrubland" + ), + Self::AnyForest => write!(f, "All Forest biomes (excluding Taiga)"), + Self::AnyShrubland => write!(f, "Temperate and Tropical Shrubland"), + Self::AnyGrassland => write!(f, "Temperate and Tropical Grassland"), + Self::AnySavanna => write!(f, "Temperate and Tropical Savanna"), + Self::AnyTemperateForest => write!(f, "Temperate Coniferous and Broadleaf Forests"), + Self::AnyTropicalForest => { + write!(f, "Tropical Coniferous and Dry/Moist Broadleaf Forests") + } + Self::AnyTemperateBroadleaf => write!( + f, + "Temperate Broadleaf Forest, Grassland/Savanna/Shrubland, Swamps, and Marshes" + ), + Self::AnyTropicalBroadleaf => write!( + f, + "Tropical Dry/Moist Broadleaf Forest, Grassland/Savanna/Shrubland, Swamps (including Mangrove), and Marshes" + ), + Self::AnyWetland => write!(f, "All swamps and marshes"), + Self::AnyTemperateWetland => write!(f, "All temperate swamps and marshes"), + Self::AnyTropicalWetland => write!(f, "All tropical swamps and marshes"), + Self::AnyTropicalMarsh => write!(f, "All tropical marshes"), + Self::AnyTemperateMarsh => write!(f, "All temperate marshes"), + Self::AnyTropicalSwamp => write!(f, "All tropical swamps (including Mangrove)"), + Self::AnyTemperateSwamp => write!(f, "All temperate swamps"), + Self::AnyDesert => write!(f, "Badlands, Rocky Wasteland, and Sand Desert"), + Self::Unknown => write!(f, "Unknown"), } } } diff --git a/lib/src/parsed_definitions/tags/caste.rs b/lib/src/parsed_definitions/tags/caste.rs index b4ced702..58962ac6 100644 --- a/lib/src/parsed_definitions/tags/caste.rs +++ b/lib/src/parsed_definitions/tags/caste.rs @@ -2196,3 +2196,9 @@ pub enum CasteTag { /// Is a flying curious beast CannotBreatheAir, } + +impl std::fmt::Display for CasteTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} diff --git a/lib/src/parsed_definitions/tags/color_modification.rs b/lib/src/parsed_definitions/tags/color_modification.rs index fd7164e8..0f830ee4 100644 --- a/lib/src/parsed_definitions/tags/color_modification.rs +++ b/lib/src/parsed_definitions/tags/color_modification.rs @@ -51,3 +51,9 @@ impl ColorModificationTag { matches!(self, Self::AsIs) } } + +impl std::fmt::Display for ColorModificationTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} diff --git a/lib/src/parsed_definitions/tags/condition.rs b/lib/src/parsed_definitions/tags/condition.rs index 553e57ad..c572b02c 100644 --- a/lib/src/parsed_definitions/tags/condition.rs +++ b/lib/src/parsed_definitions/tags/condition.rs @@ -1,6 +1,6 @@ //! Tags for conditions that can be applied to a tile/entity (graphics) -use crate::raw_definitions::CONDITION_TOKENS; +use crate::{raw_definitions::CONDITION_TOKENS, traits::IsEmpty}; /// A condition that can be applied to a tile/entity #[derive( @@ -17,27 +17,54 @@ use crate::raw_definitions::CONDITION_TOKENS; )] #[serde(rename_all = "camelCase")] pub enum ConditionTag { + #[default] /// No condition None, + /// A portrait of the creature, used when interacting with them + Portrait, /// The start of a condition Condition, - /// Default condition - #[default] + /// Used when defining a default sprite image + /// + /// `[DEFAULT:...]` Default, - /// A condition of "being animated" + /// Used when defining a child sprite image + /// + /// `[CHILD:...]` + ChildPrime, + /// Used when defining a baby sprite image + /// + /// `[BABY:...]` + BabyPrime, + /// Displayed if the creature is raised from the dead, although it is not + /// known how this is decided. Raised status is not related to having a + /// syndrome with the class from `[CONDITION_SYN_CLASS]` or from having + /// `[NOT_LIVING]`/`[OPPOSED_TO_LIFE]`. + /// + /// Used when defining a sprite image. + /// + /// `[ANIMATED:...]` Animated, - /// Condition of being a corpse + /// Displayed as soon as the creature dies. + /// + /// `[CORPSE:...]` Corpse, - /// Condition of being a child - Child, - /// Condition of being a baby - Baby, + /// Displayed in menus. Useful for large images that would extend beyond the + /// menu boxes otherwise. + /// + /// `[LIST_ICON]` + ListIcon, + /// Displayed in interaction menus in Adventure Mode, overrides `LIST_ICON` when + /// specified in a creature `CAN_DO_INTERACTION` using `CDI:TOKEN:token_name`. + /// + /// Might accept referenced token_name before standard secondaries. + /// + /// `[CDI_LIST_ICON]` + CdiListIcon, /// Condition of being trained for hunting TrainedHunter, /// Condition of being trained for war TrainedWar, - /// Condition of being a list icont - ListIcon, /// Condition of being a skeleton Skeleton, /// Condition of being a skeleton with a skull @@ -50,6 +77,8 @@ pub enum ConditionTag { Male, /// Condition of being female Female, + /// `[CONDITION_BABY]` + Baby, /// Condition of being a vampire VampireCursed, /// Condition of being a ghoul @@ -58,26 +87,88 @@ pub enum ConditionTag { DisturbedDead, /// Condition of being remains Remains, - /// Condition of being a vermin + /// Displayed if the unit escorts a tax collector (unused). + /// + /// `[TAX_ESCORT]` + TaxEscort, + /// Displayed if the unit is law enforcement. + /// + /// `[LAW_ENFORCE]` + LawEnforcement, + /// Displayed if the creature is an adventurer. + /// + /// `[ADVENTURER]` + Adventurer, + /// The creature is in the dark. Graphical replacement for `[GLOWTILE]`. + /// + /// `[GLOW]` + Glow, + /// As `[GLOW]`, but with their left eye missing. If the sprite is facing forwards, then the + /// visually leftmost eye should remain. + /// + /// `[GLOW_LEFT_GONE]` + GlowLeftGone, + /// As `[GLOW]`, but with their left eye missing. If the sprite is facing forwards, then the + /// visually leftmost eye should remain. + /// + /// `[GLOW_RIGHT_GONE]` + GlowRightGone, + /// A child creature is in darkness. Does not have wound states. + /// + /// `[GLOW_CHILD]` + GlowChild, + /// The sprite for a clutch of eggs. + /// + /// `[EGG]` + Egg, + /// The default graphic for this vermin. + /// + /// `[VERMIN]` Vermin, - /// Condition of being a light vermin - LightVermin, - /// Condition of being a hive - Hive, - /// Condition of being a small swarm + /// The alternating graphic for this vermin. Image cycles every 1 second. + /// + /// `[VERMIN_ALT]` + VerminAlt, + /// For swarming vermin like flies and fairies in small groups. + /// + /// `[SWARM_SMALL]` SwarmSmall, - /// Condition of being a medium swarm + /// For swarming vermin like flies and fairies in medium-sized groups. + /// + /// `[SWARM_MEDIUM]` SwarmMedium, - /// Condition of being a large swarm + /// For swarming vermin like flies and fairies in large groups. + /// + /// `[SWARM_LARGE]` SwarmLarge, + /// Light-producing vermin, for fireflies etc. Does not replace `[VERMIN]`. + /// + /// `[LIGHT_VERMIN]` + LightVermin, + /// The alternating graphic for this light-producing vermin. Image cycles every 1 second. + /// + /// `[LIGHT_VERMIN_ALT]` + LightVerminAlt, + /// For swarming vermin like flies and fairies in small groups. + /// + /// `[LIGHT_SWARM_SMALL]` + LightSwarmSmall, + /// For swarming vermin like flies and fairies in medium-sized groups. + /// + /// `[LIGHT_SWARM_MEDIUM]` + LightSwarmMedium, + /// For swarming vermin like flies and fairies in large groups. + /// + /// `[LIGHT_SWARM_LARGE]` + LightSwarmLarge, + /// Vermin hives. + /// + /// `[HIVE]` + Hive, /// Condition of being not an artifact NotArtifact, /// Condition of being a crafted artifact CraftedArtifact, - /// Condition of being dyed - Dye, - /// Condition of not being dyed - NotDyed, /// Condition of being a crop Crop, /// Condition of being a seed @@ -98,63 +189,260 @@ pub enum ConditionTag { CropR, /// Condition of being a dead shrub ShrubDead, - /// Condition of not being a child + /// Checks if the creature is a child or baby. + /// + /// `[CONDITION_CHILD]` + Child, + /// Checks if the creature is an adult. + /// + /// `[CONDITION_NOT_CHILD]` NotChild, - /// Condition of being at least so many hauled + /// Counts how many items the creature is hauling. Used for `[PACK_ANIMAL]`s in vanilla. + /// + /// `[CONDITION_HAUL_COUNT_MIN:count]` HaulCountMin, - /// Condition of being at most so many hauled + /// Counts how many items the creature is hauling. Used for `[PACK_ANIMAL]`s in vanilla. + /// + /// `[CONDITION_HAUL_COUNT_MAX:count]` HaulCountMax, - /// Condition of being a worn item - ItemWorn, - /// Condition of having a profession - ProfessionCategory, /// Condition of being a class Class, - /// Condition of being a syndrome class + /// Defines a body part graphic using standard body token selection criteria. + /// + /// Selection is done with `BY_TYPE`, `BY_CATEGORY`, or `BY_TOKEN` + /// + /// `[CONDITION_BP:selection:category, type, or token]` + BodyPart, + /// Checks if current `[CONDITION_BP]`'s `[BP_APPEARANCE_MODIFIER]` falls within the chosen range. + /// + /// `[BP_APPEARANCE_MODIFIER_RANGE]` + BodyPartAppearanceModifierRange, + /// Checks if the current `[CONDITION_BP]` is present and not destroyed, pulped, or severed. Can also be applied to + /// `[LG_CONDITION_BP]`. + /// + /// `[BP_PRESENT]` + BodyPartPresent, + /// Checks if the current `[CONDITION_BP]` is scarred. Seems to also require `[BP_PRESENT]` to avoid illogical results. + /// + /// `[BP_SCARRED]` + BodyPartScarred, + /// True if creature size is greater than defined size. + /// + /// `[CONDITION_BODY_SIZE_MIN:size]` + BodySizeMin, + /// True if creature size is less than defined size. + /// + /// `[CONDITION_BODY_SIZE_MAX:size]` + BodySizeMax, + /// Changes graphics based on any syndromes the creature is affected by. Vanilla values include: + /// - `ZOMBIE` + /// - `NECROMANCER` + /// - `VAMPCURSE` + /// - `RAISED_UNDEAD` + /// - `DISTURBED_DEAD` + /// - `GHOUL` + /// + /// `[CONDITION_SYN_CLASS:class]` SyndromeClass, - /// Condition of being a caste - Caste, - /// Condition of being a tissue layer + /// Selects a tissue layer to use for checking other conditions. + /// + /// `[CONDITION_TISSUE_LAYER:BY_CATEGORY:ALL:SKIN]` + /// + /// `[CONDITION_TISSUE_LAYER:BY_CATEGORY:bp category or 'ALL':tissue layer or 'ALL']` TissueLayer, - /// Condition of being a material flag - MaterialFlag, - /// Condition of being a material type - MaterialType, - /// Condition of being off if an item is present - ShutOffIfItemPresent, - /// Condition of being a random part index + /// Chooses a random layer among layers with a `CONDITION_RANDOM_PART_INDEX` with the same identifier. Index + /// is which option this condition is, out of Range number of options. + /// + /// `[CONDITION_RANDOM_PART_INDEX:HEAD:3:4]` is the third possible random head out of four total options. + /// One of these random conditions each will be put into a set of four different sprites to add some random + /// variation in the appearance of the creature's head. + /// + /// `[CONDITION_RANDOM_PART_INDEX:identifier:index:range]` RandomPartIndex, - /// Condition of being a ghost + /// Checks if the creature is a ghost. + /// + /// `[CONDITION_GHOST]` Ghost, - /// Condition of being a tissue that may have color + /// Checks the selected tissue's color. Accepts multiple color tokens, and is true if the any of the colors + /// is present in the selected tissues. + /// + /// `[TISSUE_MAY_HAVE_COLOR:color token:more color tokens]` TissueMayHaveColor, - /// Condition of being a tissue that is at least so long + /// Checks the current `[CONDITION_TISSUE_LAYER]`'s `LENGTH` appearance modifier. Is true if the `LENGTH` is + /// greater than the integer input. + /// + /// `[TISSUE_MIN_LENGTH:length]` TissueMinLength, - /// Condition of being a tissue that is at most so long + /// Checks the current `[CONDITION_TISSUE_LAYER]`'s `LENGTH` appearance modifier. Is true if the `LENGTH` is + /// less than the integer input. + /// + /// `[TISSUE_MAX_LENGTH:length]` TissueMaxLength, + /// Checks the current `[CONDITION_TISSUE_LAYER]`'s `DENSITY` appearance modifier. Is true if the `DENSITY` is + /// greater than the integer input. + /// + /// `[TISSUE_MIN_DENSITY:desnsity]` + TissueMinDensity, + /// Checks the current `[CONDITION_TISSUE_LAYER]`'s `DENSITY` appearance modifier. Is true if the `DENSITY` is + /// less than the integer input. + /// + /// `[TISSUE_MAX_DENSITY:desnsity]` + TissueMaxDensity, /// Condition of being a tissue at least so curly TissueMinCurly, /// Condition of being a tissue at most so curly TissueMaxCurly, - /// Condition of being a tissue that may have a shape + /// Checks the current `[CONDITION_TISSUE_LAYER]`'s shaping (hairstyle). Valid tokens are + /// - `NEATLY_COMBED` + /// - `BRAIDED` + /// - `DOUBLE_BRAIDS` + /// - `PONY_TAILS` + /// - `CLEAN_SHAVEN ` + /// - `STANDARD_HAIR/BEARD/MOUSTACHE/SIDEBURNS_SHAPINGS` + /// + /// `[TISSUE_MAY_HAVE_SHAPING:styling token]` TissueMayHaveShaping, - /// Condition of being a tissue that is not shaped + /// Checks the current `[CONDITION_TISSUE_LAYER]`'s color. Accepts multiple color tokens, and is true if the + /// any of the colors is present in the selected tissues. + /// + /// `[TISSUE_NOT_SHAPED]` TissueNotShaped, - /// Condition of being a swapped tissue + /// Checks if a tissue is sufficiently curly, and if so swaps to display a different image. The new image + /// is defined by the tile page ID, x position, and y position. + /// + /// This condition should be within a `[LAYER:... ]` that has a similar graphic to the on in the `TISSUE_SWAP`. + /// The current `[CONDITION_TISSUE_LAYER]` group must also include a `[TISSUE_MIN_LENGTH]`. + /// + /// `[TISSUE_SWAP:IF_MIN_CURLY:curl amount:tile page id:x pos:y pos]` TissueSwap, /// Condition of being a specific layer (start layer definition) Layer, - /// Condition of being a specific layer set of layers - LayerSet, - /// Condition of being a specific layer group - LayerGroup, - /// Condition of being a specific layer group set of layers - EndLayerGroup, /// Condition of being the upper body BodyUpper, /// Condition of being a copy of a template CopyOfTemplate, + /// Checks the current [CONDITION_ITEM_WORN]'s quality. 0 is base quality, 5 is masterwork. + /// See `[CONDITION_MATERIAL_FLAG:NOT_ARTIFACT]` for non-artifact-quality items. + /// + /// `[ITEM_QUALITY:quality id]` + ItemQuality, + /// Begins a layer group. Only the first-matching layer in a group will be rendered, so list more + /// specific items at the beginning of the layer group and more general items towards the end. + /// + /// `[LAYER_GROUP]` + LayerGroup, + /// Condition of being a specific layer group set of layers + /// Begins defining a layer set for a creature's graphics. + /// + /// `[LAYER_SET:condition]` + LayerSet, + /// Begins defining a palette for the layer set. Its name can then be referenced by `[USE_PALETTE]`. + /// Unlike the palettes used to render all descriptor color tokens, it can be of arbitrary length. + /// + /// `[LS_PALETTE:name]` + LayerSetPalette, + /// The file name of the 8bit RGBA (sometimes called 32bit) in the /graphics/images folder of the mod, + /// such as `images/portraits/dwarf_portrait_body_palette.png`. + /// + /// `[LS_PALETTE_FILE:file path]` + LayerSetPaletteFile, + /// Defines the default row of a layer set palette, conventionally 0. The exact color values on this row + /// will be replaced on layer images with the colors in the same column, based on what row is passed as + /// an argument to `[USE_PALETTE]`. + /// + /// `[LS_PALETTE_DEFAULT:integer]` + LayerSetPaletteDefault, + /// Allows the entire layer group (rather than an individual layer) to be switched on and off depending on the + /// conditions of a body part. Should accept the same tokens `[CONDITION_BP]` does. + /// + /// Selection is done with `BY_TYPE`, `BY_CATEGORY`, or `BY_TOKEN` + /// + /// `[LG_CONDITION_BP:selection:cateogry, type, or token]` + LayerGroupBodyPart, + /// Explicitly marks the end of a layer group, which allows layers after to not belong to any layer group. + /// + /// `[END_LAYER_GROUP]` + EndLayerGroup, + /// Defines a clothing or armor graphic by the specific part it is equipped to, the type of armor it is, and the + /// internal ID of that item. Additional arguments can be supplied to check for additional subtypes. Valid if any + /// matching items are worn. + /// + /// For example, a condition representing a right handed mitten or glove would be defined as: + /// + /// `[CONDITION_ITEM_WORN:BY_TOKEN:RH:GLOVES:ITEM_GLOVES_MITTENS]` Also accepts the input `ANY_HELD` or `WIELD` + /// (e.g. `WIELD:WEAPON:ANY`), though `ANY_HELD` has been bugged since v50.14. + /// + /// Selection is done with `BY_CATEGORY` or `BY_TOKEN` + /// + /// `[CONDITION_ITEM_WORN:selection:cateogry or token:armor type:item id]` + ItemWorn, + /// Causes the current layer to not be rendered if the creature has one of the items worn or equipped. Also accepts + /// the input `ANY_HELD` or `WIELD` (e.g. `WIELD:WEAPON:ANY`). Note that `ANY_HELD` has been bugged since v50.14. + /// + /// Selection is done with `BY_CATEGORY` or `BY_TOKEN` + /// + /// `[SHUT_OFF_IF_ITEM_PRESENT:selection:cateogry or token:armor type:item id]` + ShutOffIfItemPresent, + /// Displays this layer if the creature is this caste. Only one caste is accepted for each condition, but multiple + /// caste conditions can be used in one layer and the layer will be displayed if any of them match. + /// + /// `[CONDITION_CASTE:caste name]` + Caste, + /// Represents which color the clothing is dyed. Partially-working.v50.15 + /// + /// Takes a descriptor color. Vanilla dye options: + /// + /// - MIDNIGHT_BLUE - (Dimple cup) + /// - EMERALD - (Blade weed) + /// - RED - (Hide root) + /// - BLACK - (Sliver barb) + /// + /// `[CONDITION_DYE:cye color]` + Dye, + /// Checks if the clothing is dyed.v50.15 + /// + /// `[CONDITION_NOT_DYED]` + NotDyed, + /// Changes graphics based on the material an equipped item is made of. Specifying multiple of this condition for a + /// layer uses the "AND" instead of "OR" logical operator, whether placed in the same line or on separate lines. Valid + /// material flags are similar to reactant conditions including: + /// + /// - `WOVEN_ITEM` + /// - `ANY_X_MATERIAL` with X being: + /// - `PLANT`, `SILK`, `YARN`, `LEATHER`, `WOOD`, `SHELL`, `BONE`, `STONE`, `GEM`, `TOOTH`, `HORN`, `PEARL` + /// - `IS_DIVINE_MATERIAL` + /// - `NOT_ARTIFACT` + /// - `IS_CRAFTED_ARTIFACT` (Note that this token might not have ever worked.) + /// - `METAL_ITEM_MATERIAL` + /// - `GLASS_MATERIAL` + /// - `FIRE_BUILD_SAFE` + /// - `MAGMA_BUILD_SAFE` + /// - `GROWN_NOT_CRAFTED` + /// + /// `[CONDITION_MATERIAL_FLAG:flag]` + MaterialFlag, + /// Changes graphics based on the material an equipped item is made of. Valid material types are `INORGANIC` or `METAL:IRON` + /// where "iron" can be replaced with any weapons-grade metal. General material tokens are not functional. + /// + /// `[CONDITION_MATERIAL_FLAG]` is a better option for any material condition other than metal. + /// + /// `[CONDITION_MATERIAL_TYPE:material token]` + MaterialType, + /// Colors the layer using that row of either the layer-set-specific `[LS_PALETTE]` or a predefined palette such as `DEFAULT`. + /// + /// `[USE_PALETTE:layer set palette:row] + UsePalette, + /// Uses the default palette to render the layer based on the color of the current `[CONDITION_ITEM_WORN]`. + /// + /// `[USE_STANDARD_PALETTE_FROM_ITEM]` + UseStandardPaletteFromItem, + /// Note: This condition is bugged and doesn't work since DFv50.14. + /// + /// Checks the profession category of the creature to act as a condition. Multiple profession category tokens can be supplied as additional arguments, and will be valid for any of them. You can also use multiple of these tokens instead of listing them all in a single line, but this is functionally identical. Valid Profession tokens which are not categories will be ignored; values that do not match any existing Profession will be treated as NONE and thus apply to doctors, military, etc.. + /// + /// `[CONDITION_PROFESSION_CATEGORY:prefession tokens (one ore more)] + ProfessionCategory, // Professions (somewhat of a hack.. but some mods don't use profession category and instead call direct) /// Hammerman profession Hammerman, @@ -281,3 +569,15 @@ impl ConditionTag { self.is_default() } } + +impl std::fmt::Display for ConditionTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} + +impl IsEmpty for ConditionTag { + fn is_empty(&self) -> bool { + self == &Self::None + } +} diff --git a/lib/src/parsed_definitions/tags/creature.rs b/lib/src/parsed_definitions/tags/creature.rs index aac83abf..08f05e76 100644 --- a/lib/src/parsed_definitions/tags/creature.rs +++ b/lib/src/parsed_definitions/tags/creature.rs @@ -1,10 +1,9 @@ //! Contains the `CreatureTag` enum and associated implementations. use crate::{ - creature::Creature, custom_types::TileCharacter, raw_definitions::CREATURE_TOKENS, - traits::{RawObjectToken, TagOperations, TokenParser}, + traits::{TagOperations, TokenParser}, }; /// An enum representing a creature tag. @@ -568,15 +567,8 @@ pub enum CreatureTag { } impl std::fmt::Display for CreatureTag { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - format!("{self:?}").fmt(f) - } -} - -#[typetag::serialize] -impl RawObjectToken for CreatureTag { - fn is_within(&self, object: &Creature) -> bool { - object.get_tags().contains(self) + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) } } @@ -712,11 +704,15 @@ impl TagOperations for CreatureTag { } let Ok(number) = (*values.first().unwrap_or(&"")).parse::() else { - tracing::warn!("parse_complex_token: HarvestProduct failed to parse number value in position 0: {values:?}"); + tracing::warn!( + "parse_complex_token: HarvestProduct failed to parse number value in position 0: {values:?}" + ); return None; }; let Ok(time) = (*values.get(1).unwrap_or(&"")).parse::() else { - tracing::warn!("parse_complex_token: HarvestProduct failed to parse time value in position 1: {values:?}"); + tracing::warn!( + "parse_complex_token: HarvestProduct failed to parse time value in position 1: {values:?}" + ); return None; }; diff --git a/lib/src/parsed_definitions/tags/creature_effect.rs b/lib/src/parsed_definitions/tags/creature_effect.rs index 79dc1e3f..5d29995c 100644 --- a/lib/src/parsed_definitions/tags/creature_effect.rs +++ b/lib/src/parsed_definitions/tags/creature_effect.rs @@ -130,3 +130,9 @@ pub enum CreatureEffectTag { } //Todo: add triggers + +impl std::fmt::Display for CreatureEffectTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} diff --git a/lib/src/parsed_definitions/tags/creature_effect_property.rs b/lib/src/parsed_definitions/tags/creature_effect_property.rs index 996a599b..181f5574 100644 --- a/lib/src/parsed_definitions/tags/creature_effect_property.rs +++ b/lib/src/parsed_definitions/tags/creature_effect_property.rs @@ -1,5 +1,7 @@ //! An enum representing a creature effect property tag. +use crate::traits::IsEmpty; + /// An enum representing a creature effect property tag. #[derive( serde::Serialize, @@ -68,3 +70,15 @@ pub enum CreatureEffectPropertyTag { #[default] Unknown, } + +impl std::fmt::Display for CreatureEffectPropertyTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} + +impl IsEmpty for CreatureEffectPropertyTag { + fn is_empty(&self) -> bool { + self == &Self::Unknown + } +} diff --git a/lib/src/parsed_definitions/tags/creature_variation.rs b/lib/src/parsed_definitions/tags/creature_variation.rs index 1aa4677e..68f4f20e 100644 --- a/lib/src/parsed_definitions/tags/creature_variation.rs +++ b/lib/src/parsed_definitions/tags/creature_variation.rs @@ -64,3 +64,9 @@ impl CreatureVariationTag { *tag } } + +impl std::fmt::Display for CreatureVariationTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} diff --git a/lib/src/parsed_definitions/tags/creature_variation_rule.rs b/lib/src/parsed_definitions/tags/creature_variation_rule.rs index 2b3b8e44..c2e1123e 100644 --- a/lib/src/parsed_definitions/tags/creature_variation_rule.rs +++ b/lib/src/parsed_definitions/tags/creature_variation_rule.rs @@ -286,10 +286,10 @@ impl CreatureVariationRuleTag { return; } // Check if the argument matches the requirement. - if let Some(argument_value) = args.get(argument_index - 1) { - if argument_value == &argument_requirement { - apply_new_tag(creature, &tag, value.as_deref()); - } + if let Some(argument_value) = args.get(argument_index - 1) + && argument_value == &argument_requirement + { + apply_new_tag(creature, &tag, value.as_deref()); } } Self::ConditionalRemoveTag { @@ -308,10 +308,10 @@ impl CreatureVariationRuleTag { return; } // Check if the argument matches the requirement. - if let Some(argument_value) = args.get(argument_index - 1) { - if argument_value == &argument_requirement { - remove_tag(creature, &tag); - } + if let Some(argument_value) = args.get(argument_index - 1) + && argument_value == &argument_requirement + { + remove_tag(creature, &tag); } } Self::ConditionalConvertTag { @@ -331,13 +331,19 @@ impl CreatureVariationRuleTag { return; } // Check if the argument matches the requirement. - if let Some(argument_value) = args.get(argument_index - 1) { - if argument_value == &argument_requirement { - convert_tag(creature, &tag, target.as_deref(), replacement.as_deref()); - } + if let Some(argument_value) = args.get(argument_index - 1) + && argument_value == &argument_requirement + { + convert_tag(creature, &tag, target.as_deref(), replacement.as_deref()); } } Self::Unknown => {} } } } + +impl std::fmt::Display for CreatureVariationRuleTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} diff --git a/lib/src/parsed_definitions/tags/entity.rs b/lib/src/parsed_definitions/tags/entity.rs index 997a7e06..affed4da 100644 --- a/lib/src/parsed_definitions/tags/entity.rs +++ b/lib/src/parsed_definitions/tags/entity.rs @@ -799,3 +799,9 @@ pub enum EntityTag { /// Select an entity to modify SelectEntity, } + +impl std::fmt::Display for EntityTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} diff --git a/lib/src/parsed_definitions/tags/environment_class.rs b/lib/src/parsed_definitions/tags/environment_class.rs index 59fb14ea..199ed0ad 100644 --- a/lib/src/parsed_definitions/tags/environment_class.rs +++ b/lib/src/parsed_definitions/tags/environment_class.rs @@ -1,5 +1,7 @@ //! The class of environment that the stone appears in. +use crate::traits::IsEmpty; + /// The class of environment that the stone appears in. #[derive( serde::Serialize, @@ -54,13 +56,13 @@ impl EnvironmentClassTag { impl std::fmt::Display for EnvironmentClassTag { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { - Self::AllStone => write!(f, "AllStone"), - Self::IgneousAll => write!(f, "IgneousAll"), - Self::IgneousExtrusive => write!(f, "IgneousExtrusive"), - Self::IgneousIntrusive => write!(f, "IgneousIntrusive"), + Self::AllStone => write!(f, "All Stone"), + Self::IgneousAll => write!(f, "Igneous (All)"), + Self::IgneousExtrusive => write!(f, "Igneous Extrusive"), + Self::IgneousIntrusive => write!(f, "Igneous Intrusive"), Self::Soil => write!(f, "Soil"), - Self::SoilSand => write!(f, "SoilSand"), - Self::SoilOcean => write!(f, "SoilOcean"), + Self::SoilSand => write!(f, "Sand"), + Self::SoilOcean => write!(f, "Ocean Floor"), Self::Sedimentary => write!(f, "Sedimentary"), Self::Metamorphic => write!(f, "Metamorphic"), Self::Alluvial => write!(f, "Alluvial"), @@ -68,3 +70,9 @@ impl std::fmt::Display for EnvironmentClassTag { } } } + +impl IsEmpty for EnvironmentClassTag { + fn is_empty(&self) -> bool { + self == &Self::None + } +} diff --git a/lib/src/parsed_definitions/tags/fuel_type.rs b/lib/src/parsed_definitions/tags/fuel_type.rs index e54e4cd0..63954c30 100644 --- a/lib/src/parsed_definitions/tags/fuel_type.rs +++ b/lib/src/parsed_definitions/tags/fuel_type.rs @@ -1,5 +1,7 @@ //! A material fuel type that can be set in a material definition. +use crate::traits::IsEmpty; + /// A material fuel type that can be set in a material definition. #[derive( serde::Serialize, @@ -36,3 +38,15 @@ impl FuelTypeTag { matches!(self, Self::None) } } + +impl std::fmt::Display for FuelTypeTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} + +impl IsEmpty for FuelTypeTag { + fn is_empty(&self) -> bool { + self == &FuelTypeTag::None + } +} diff --git a/lib/src/parsed_definitions/tags/gait_modifier.rs b/lib/src/parsed_definitions/tags/gait_modifier.rs index 5351a417..ae4f9426 100644 --- a/lib/src/parsed_definitions/tags/gait_modifier.rs +++ b/lib/src/parsed_definitions/tags/gait_modifier.rs @@ -40,3 +40,9 @@ pub enum GaitModifierTag { start_speed: u32, }, } + +impl std::fmt::Display for GaitModifierTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} diff --git a/lib/src/parsed_definitions/tags/gait_type.rs b/lib/src/parsed_definitions/tags/gait_type.rs index d7421aea..8d38bd5c 100644 --- a/lib/src/parsed_definitions/tags/gait_type.rs +++ b/lib/src/parsed_definitions/tags/gait_type.rs @@ -34,3 +34,9 @@ pub enum GaitTypeTag { /// Unknown gait type (unset) Unknown, } + +impl std::fmt::Display for GaitTypeTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} diff --git a/lib/src/parsed_definitions/tags/graphic_type.rs b/lib/src/parsed_definitions/tags/graphic_type.rs index ce80d509..bba1cc3f 100644 --- a/lib/src/parsed_definitions/tags/graphic_type.rs +++ b/lib/src/parsed_definitions/tags/graphic_type.rs @@ -1,5 +1,7 @@ //! Graphic type tags for the tileset +use crate::traits::IsEmpty; + /// The graphic type of the tile #[derive( serde::Serialize, @@ -257,3 +259,15 @@ pub enum GraphicTypeTag { /// The graphic is of a weapon upright 10-B WeaponUpright10B, } + +impl std::fmt::Display for GraphicTypeTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} + +impl IsEmpty for GraphicTypeTag { + fn is_empty(&self) -> bool { + self == &Self::Unknown + } +} diff --git a/lib/src/parsed_definitions/tags/growth.rs b/lib/src/parsed_definitions/tags/growth.rs index fe73f306..55d3d2c0 100644 --- a/lib/src/parsed_definitions/tags/growth.rs +++ b/lib/src/parsed_definitions/tags/growth.rs @@ -29,3 +29,9 @@ pub enum GrowthTag { #[default] AsIs, } + +impl std::fmt::Display for GrowthTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} diff --git a/lib/src/parsed_definitions/tags/inclusion_type.rs b/lib/src/parsed_definitions/tags/inclusion_type.rs index 35c2802b..c52e83eb 100644 --- a/lib/src/parsed_definitions/tags/inclusion_type.rs +++ b/lib/src/parsed_definitions/tags/inclusion_type.rs @@ -1,5 +1,7 @@ //! Inclusion type tag. +use crate::traits::IsEmpty; + /// The type of inclusion that the stone has. #[derive( serde::Serialize, @@ -43,10 +45,16 @@ impl std::fmt::Display for InclusionTypeTag { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Self::Cluster => write!(f, "Cluster"), - Self::ClusterSmall => write!(f, "ClusterSmall"), - Self::ClusterOne => write!(f, "ClusterOne"), + Self::ClusterSmall => write!(f, "Small Cluster"), + Self::ClusterOne => write!(f, "Singular Cluster"), Self::Vein => write!(f, "Vein"), Self::None => write!(f, "None"), } } } + +impl IsEmpty for InclusionTypeTag { + fn is_empty(&self) -> bool { + self == &Self::None + } +} diff --git a/lib/src/parsed_definitions/tags/material_property.rs b/lib/src/parsed_definitions/tags/material_property.rs index fbd804b7..5523c7b5 100644 --- a/lib/src/parsed_definitions/tags/material_property.rs +++ b/lib/src/parsed_definitions/tags/material_property.rs @@ -215,3 +215,9 @@ pub enum MaterialPropertyTag { #[default] Unknown, } + +impl std::fmt::Display for MaterialPropertyTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} diff --git a/lib/src/parsed_definitions/tags/material_state.rs b/lib/src/parsed_definitions/tags/material_state.rs index db9148fe..855d7a77 100644 --- a/lib/src/parsed_definitions/tags/material_state.rs +++ b/lib/src/parsed_definitions/tags/material_state.rs @@ -34,3 +34,9 @@ pub enum MaterialStateTag { /// Denotes '`Solid`', '`Powder`', '`Paste`', and '`Pressed`' AllSolid, } + +impl std::fmt::Display for MaterialStateTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} diff --git a/lib/src/parsed_definitions/tags/material_type.rs b/lib/src/parsed_definitions/tags/material_type.rs index 9d0c7414..4634775f 100644 --- a/lib/src/parsed_definitions/tags/material_type.rs +++ b/lib/src/parsed_definitions/tags/material_type.rs @@ -1,5 +1,7 @@ //! Material type tags +use crate::traits::IsEmpty; + /// A material template #[derive( serde::Serialize, @@ -111,11 +113,17 @@ impl std::fmt::Display for MaterialTypeTag { Self::Mud => write!(f, "Mud"), Self::Vomit => write!(f, "Vomit"), Self::Salt => write!(f, "Salt"), - Self::FilthB => write!(f, "FilthB"), - Self::FilthY => write!(f, "FilthY"), + Self::FilthB => write!(f, "Brown Filth"), + Self::FilthY => write!(f, "Yellow Filth"), Self::UnknownSubstance => write!(f, "Unknown Substance"), Self::Grime => write!(f, "Grime"), Self::Unknown => write!(f, "Unknown"), } } } + +impl IsEmpty for MaterialTypeTag { + fn is_empty(&self) -> bool { + self == &MaterialTypeTag::Unknown + } +} diff --git a/lib/src/parsed_definitions/tags/modification.rs b/lib/src/parsed_definitions/tags/modification.rs index 57bcc90d..8da72b73 100644 --- a/lib/src/parsed_definitions/tags/modification.rs +++ b/lib/src/parsed_definitions/tags/modification.rs @@ -81,3 +81,9 @@ impl ModificationTag { } } } + +impl std::fmt::Display for ModificationTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} diff --git a/lib/src/parsed_definitions/tags/plant_graphic_template.rs b/lib/src/parsed_definitions/tags/plant_graphic_template.rs index 5a03519c..dab41a63 100644 --- a/lib/src/parsed_definitions/tags/plant_graphic_template.rs +++ b/lib/src/parsed_definitions/tags/plant_graphic_template.rs @@ -33,3 +33,9 @@ pub enum PlantGraphicTemplateTag { /// The standard flowers 4 StandardFlowers4, } + +impl std::fmt::Display for PlantGraphicTemplateTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} diff --git a/lib/src/parsed_definitions/tags/plant_growth.rs b/lib/src/parsed_definitions/tags/plant_growth.rs index 6eda1f3b..e0f7eb6e 100644 --- a/lib/src/parsed_definitions/tags/plant_growth.rs +++ b/lib/src/parsed_definitions/tags/plant_growth.rs @@ -40,3 +40,9 @@ pub enum PlantGrowthTag { #[default] Unknown, } + +impl std::fmt::Display for PlantGrowthTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} diff --git a/lib/src/parsed_definitions/tags/plant_growth_type.rs b/lib/src/parsed_definitions/tags/plant_growth_type.rs index 02dd2c90..009f4e57 100644 --- a/lib/src/parsed_definitions/tags/plant_growth_type.rs +++ b/lib/src/parsed_definitions/tags/plant_growth_type.rs @@ -1,5 +1,7 @@ //! The types of growths +use crate::traits::IsEmpty; + /// The types of growths #[derive( serde::Serialize, @@ -44,3 +46,15 @@ pub enum PlantGrowthTypeTag { #[default] None, } + +impl std::fmt::Display for PlantGrowthTypeTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} + +impl IsEmpty for PlantGrowthTypeTag { + fn is_empty(&self) -> bool { + self == &PlantGrowthTypeTag::None + } +} diff --git a/lib/src/parsed_definitions/tags/plant_part.rs b/lib/src/parsed_definitions/tags/plant_part.rs index e701c0ac..3a9d7be7 100644 --- a/lib/src/parsed_definitions/tags/plant_part.rs +++ b/lib/src/parsed_definitions/tags/plant_part.rs @@ -38,3 +38,9 @@ pub enum PlantPartTag { #[default] Unknown, } + +impl std::fmt::Display for PlantPartTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} diff --git a/lib/src/parsed_definitions/tags/position.rs b/lib/src/parsed_definitions/tags/position.rs index cd90bfcf..7f120bb4 100644 --- a/lib/src/parsed_definitions/tags/position.rs +++ b/lib/src/parsed_definitions/tags/position.rs @@ -1,5 +1,7 @@ //! Position tags are used to define the properties of a position in the game. They are used in the `position` token. +use crate::traits::IsEmpty; + /// Represents a position token #[derive( serde::Serialize, @@ -248,3 +250,15 @@ pub enum PositionTag { #[default] Unknown, } + +impl std::fmt::Display for PositionTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} + +impl IsEmpty for PositionTag { + fn is_empty(&self) -> bool { + self == &PositionTag::Unknown + } +} diff --git a/lib/src/parsed_definitions/tags/season.rs b/lib/src/parsed_definitions/tags/season.rs index 8ae400d0..b7c47ac7 100644 --- a/lib/src/parsed_definitions/tags/season.rs +++ b/lib/src/parsed_definitions/tags/season.rs @@ -1,5 +1,7 @@ //! The tokens for the seasons +use crate::traits::IsEmpty; + /// The tokens for the seasons #[derive( serde::Serialize, @@ -26,3 +28,15 @@ pub enum SeasonTag { #[default] Unknown, } + +impl std::fmt::Display for SeasonTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} + +impl IsEmpty for SeasonTag { + fn is_empty(&self) -> bool { + self == &Self::Unknown + } +} diff --git a/lib/src/parsed_definitions/tags/select_creature_rule.rs b/lib/src/parsed_definitions/tags/select_creature_rule.rs index 48094db4..6a72dce3 100644 --- a/lib/src/parsed_definitions/tags/select_creature_rule.rs +++ b/lib/src/parsed_definitions/tags/select_creature_rule.rs @@ -21,3 +21,9 @@ pub enum SelectCreatureRuleTag { /// Adds an additional previously defined caste to the selection. Used after `[SELECT_CASTE]`. SelectAdditionalCaste(String), } + +impl std::fmt::Display for SelectCreatureRuleTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} diff --git a/lib/src/parsed_definitions/tags/shrub.rs b/lib/src/parsed_definitions/tags/shrub.rs index 7ef21a26..f46f8bd4 100644 --- a/lib/src/parsed_definitions/tags/shrub.rs +++ b/lib/src/parsed_definitions/tags/shrub.rs @@ -64,3 +64,9 @@ pub enum ShrubTag { #[default] Unknown, } + +impl std::fmt::Display for ShrubTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} diff --git a/lib/src/parsed_definitions/tags/syndrome.rs b/lib/src/parsed_definitions/tags/syndrome.rs index f5622688..3da7f5ed 100644 --- a/lib/src/parsed_definitions/tags/syndrome.rs +++ b/lib/src/parsed_definitions/tags/syndrome.rs @@ -96,3 +96,9 @@ pub enum SyndromeTag { #[default] Unknown, } + +impl std::fmt::Display for SyndromeTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} diff --git a/lib/src/parsed_definitions/tags/tile_page.rs b/lib/src/parsed_definitions/tags/tile_page.rs index 0ffecdef..4bfcf336 100644 --- a/lib/src/parsed_definitions/tags/tile_page.rs +++ b/lib/src/parsed_definitions/tags/tile_page.rs @@ -25,3 +25,9 @@ pub enum TilePageTag { #[default] Unknown, } + +impl std::fmt::Display for TilePageTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} diff --git a/lib/src/parsed_definitions/tags/tree.rs b/lib/src/parsed_definitions/tags/tree.rs index 76516936..9ff7c963 100644 --- a/lib/src/parsed_definitions/tags/tree.rs +++ b/lib/src/parsed_definitions/tags/tree.rs @@ -103,3 +103,9 @@ pub enum TreeTag { #[default] Unknown, } + +impl std::fmt::Display for TreeTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} diff --git a/lib/src/parsed_definitions/tags/twig_placement.rs b/lib/src/parsed_definitions/tags/twig_placement.rs index 38fa57ef..3d3100dc 100644 --- a/lib/src/parsed_definitions/tags/twig_placement.rs +++ b/lib/src/parsed_definitions/tags/twig_placement.rs @@ -1,5 +1,7 @@ //! Twig placement tags +use crate::traits::IsEmpty; + /// The placement of twigs on a tree #[derive( serde::Serialize, @@ -36,3 +38,15 @@ pub enum TwigPlacementTag { #[default] Unknown, } + +impl std::fmt::Display for TwigPlacementTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} + +impl IsEmpty for TwigPlacementTag { + fn is_empty(&self) -> bool { + self == &Self::Unknown + } +} diff --git a/lib/src/parsed_definitions/temperatures.rs b/lib/src/parsed_definitions/temperatures.rs index cb4ed01f..832905fe 100644 --- a/lib/src/parsed_definitions/temperatures.rs +++ b/lib/src/parsed_definitions/temperatures.rs @@ -1,6 +1,18 @@ //! Temperature properties of a material +use dfraw_parser_proc_macros::{Cleanable, IsEmpty}; -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + PartialEq, + Eq, + IsEmpty, + Cleanable, +)] #[serde(rename_all = "camelCase")] /// The temperature properties of a material pub struct Temperatures { @@ -8,33 +20,33 @@ pub struct Temperatures { /// A material with a high specific heat capacity will hold more heat and affect its surroundings more /// before cooling down or heating up to equilibrium. The input for this token is not temperature, /// but rather the specific heat capacity of the material. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] specific_heat: Option, /// This is the temperature at which the material will catch fire. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] ignition_point: Option, /// This is the temperature at which a liquid material will freeze, or a solid material will melt. /// In Dwarf Fortress the melting point and freezing point coincide exactly; this is contrary to many /// real-life materials, which can be supercooled. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] melting_point: Option, /// This is the temperature at which the material will boil or condense. Water boils at 10180 °U - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] boiling_point: Option, /// This is the temperature above which the material will begin to take heat damage. /// Burning items without a heat damage point (or with an exceptionally high one) will take damage very slowly, /// causing them to burn for a very long time (9 months and 16.8 days) before disappearing. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] heat_damage_point: Option, /// This is the temperature below which the material will begin to take frost damage. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] cold_damage_point: Option, /// A material's temperature can be forced to always be a certain value via the `MAT_FIXED_TEMP` /// material definition token. The only standard material which uses this is nether-cap wood, /// whose temperature is always at the melting point of water. If a material's temperature is fixed /// to between its cold damage point and its heat damage point, then items made from that material /// will never suffer cold/heat damage. This makes nether-caps fire-safe and magma-safe despite being a type of wood. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] material_fixed_temperature: Option, } diff --git a/lib/src/parsed_definitions/tile.rs b/lib/src/parsed_definitions/tile.rs index 9a5d9232..4bf3e8ef 100644 --- a/lib/src/parsed_definitions/tile.rs +++ b/lib/src/parsed_definitions/tile.rs @@ -1,20 +1,33 @@ //! Tile definition for DF Classic +use dfraw_parser_proc_macros::{Cleanable, IsEmpty}; + use super::color::Color; #[allow(clippy::module_name_repetitions)] -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + PartialEq, + Eq, + specta::Type, + IsEmpty, + Cleanable, +)] #[serde(rename_all = "camelCase")] /// Representation of a character tile (literally a single character) that is used in DF Classic pub struct Tile { character: String, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] alt_character: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] color: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] glow_character: Option, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] glow_color: Option, } diff --git a/lib/src/parsed_definitions/tile_page.rs b/lib/src/parsed_definitions/tile_page.rs index 82182a2f..59899452 100644 --- a/lib/src/parsed_definitions/tile_page.rs +++ b/lib/src/parsed_definitions/tile_page.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; +use dfraw_parser_proc_macros::{Cleanable, IsEmpty}; use tracing::warn; use crate::{ @@ -9,16 +10,27 @@ use crate::{ metadata::{ObjectType, RawMetadata}, raw_definitions::TILE_PAGE_TOKENS, tags::TilePageTag, - traits::{searchable::clean_search_vec, RawObject, Searchable}, - utilities::build_object_id_from_pieces, + traits::{RawObject, Searchable}, + utilities::{build_object_id_from_pieces, clean_search_vec}, }; /// A struct representing a `TilePage` object. #[allow(clippy::module_name_repetitions)] -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + PartialEq, + Eq, + IsEmpty, + Cleanable, +)] #[serde(rename_all = "camelCase")] pub struct TilePage { - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] metadata: Option, identifier: String, object_id: String, @@ -29,6 +41,18 @@ pub struct TilePage { } impl TilePage { + #[must_use] + pub fn get_file_path(&self) -> PathBuf { + self.file.clone() + } + #[must_use] + pub fn get_tile_dimensions(&self) -> Dimensions { + self.tile_dim + } + #[must_use] + pub fn get_page_dimensions(&self) -> Dimensions { + self.page_dim + } /// Function to create a new empty `TilePage`. /// /// # Returns @@ -64,32 +88,6 @@ impl TilePage { ..Self::default() } } - /// Function to "clean" the creature. This is used to remove any empty list or strings, - /// and to remove any default values. By "removing" it means setting the value to None. - /// - /// This also will remove the metadata if `is_metadata_hidden` is true. - /// - /// Steps for all "Option" fields: - /// - Set any metadata to None if `is_metadata_hidden` is true. - /// - Set any empty string to None. - /// - Set any empty list to None. - /// - Set any default values to None. - /// - /// # Returns - /// - /// * `TilePage` - The cleaned `TilePage`. - #[must_use] - pub fn cleaned(&self) -> Self { - let mut cleaned = self.clone(); - - if let Some(metadata) = &cleaned.metadata { - if metadata.is_hidden() { - cleaned.metadata = None; - } - } - - cleaned - } } #[typetag::serde] @@ -111,15 +109,9 @@ impl RawObject for TilePage { fn get_name(&self) -> &str { &self.identifier } - fn is_empty(&self) -> bool { - self.identifier.is_empty() - } fn get_type(&self) -> &ObjectType { &ObjectType::TilePage } - fn clean_self(&mut self) { - *self = self.cleaned(); - } fn parse_tag(&mut self, key: &str, value: &str) { match TILE_PAGE_TOKENS.get(key).unwrap_or(&TilePageTag::Unknown) { @@ -146,7 +138,9 @@ impl RawObject for TilePage { } } } - + fn get_searchable_tokens(&self) -> Vec<&str> { + Vec::new() + } fn get_object_id(&self) -> &str { &self.object_id } diff --git a/lib/src/parsed_definitions/tree.rs b/lib/src/parsed_definitions/tree.rs index cf989a11..4eba1b97 100644 --- a/lib/src/parsed_definitions/tree.rs +++ b/lib/src/parsed_definitions/tree.rs @@ -1,120 +1,146 @@ //! Tree definition and parsing. +use dfraw_parser_proc_macros::{Cleanable, IsEmpty}; use tracing::{error, warn}; use crate::{ color::Color, - default_checks, name::Name, raw_definitions::TREE_TOKENS, tags::{TreeTag, TwigPlacementTag}, }; /// A struct representing a tree. -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Default, + specta::Type, + PartialEq, + Eq, + IsEmpty, + Cleanable, +)] #[serde(rename_all = "camelCase")] pub struct Tree { /// Tree will yield logs made of that material. Instead, if it's `[TREE:NONE]`, no logs will result. /// Materials are typically found in other raws.. material: String, /// What the trunk of the tree is named - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] trunk_name: Option, /// The maximum z-level height of the trunk, starting from +2 z-levels above the ground. /// Valid values: 1-8 /// Default: 1 - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = 1)] max_trunk_height: Option, /// Upper limit of trunk thickness, in tiles. Has a geometric effect on log yield. /// Valid values: 1-3 /// Default: 1 - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = 1)] max_trunk_diameter: Option, /// The number of years the trunk takes to grow one z-level upward. Default: 1 - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = 1)] trunk_period: Option, /// The number of years the trunk takes to grow one tile wider. Default: 1 - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = 1)] trunk_width_period: Option, /// What thin branches of the tree are named. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] branch_name: Option, /// How dense the branches grow on this tree. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] branch_density: Option, /// The radius to which branches can reach. Appears to never reach further than seven tiles from the centre. /// Does not depend on the trunk branching amount or where trunks are. /// The values used in the game go from 0-3. Higher values than that can cause crashes. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] branch_radius: Option, /// What thick branches of the tree are named. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] heavy_branches_name: Option, /// Similar to `BRANCH_DENSITY` for thick branches. Default: 0 - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] heavy_branch_density: Option, /// Similar as `BRANCH_DENSITY` for thick branches. Values outside 0-3 can cause crashes. Default: 0 - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] heavy_branch_radius: Option, /// How much the trunk branches out. 0 makes the trunk straight (default) - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] trunk_branching: Option, /// What the roots of the tree are named. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] root_name: Option, /// Density of the root growth. Defaults to 0. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] root_density: Option, /// How wide the roots reach out. Defaults to 0. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] root_radius: Option, /// What the twigs of the tree are named. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] twigs_name: Option, /// Where twigs appear, defaults to `[SideBranches, AboveBranches]` - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] twigs_placement: Option>, /// What this mushroom-cap is called. Only makes sense with `TREE_HAS_MUSHROOM_CAP`. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] cap_name: Option, /// Similar to the other PERIOD tags, influences the rate of the mushroom cap growth. Only makes sense with `TREE_HAS_MUSHROOM_CAP`. Default: 1 - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = 1)] cap_period: Option, /// The radius of a mushroom cap. Only makes sense with `TREE_HAS_MUSHROOM_CAP`. Default: 0 - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] cap_radius: Option, /// The tile used for trees of this type on the world map. Defaults to 24 (↑). - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = "↑")] tree_tile: Option, /// The tile used for (un)dead trees and deciduous trees (generally in winter) of this type. Defaults to 198 (╞). - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = "╞")] dead_tree_tile: Option, /// The tile used for saplings of this tree. Defaults to 231 (τ). - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = "τ")] sapling_tile: Option, /// The tile used for dead saplings of this tree. Defaults to 231 (τ). - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = "τ")] dead_sapling_tile: Option, /// The color of the tree on the map. Defaults to 2:0:0 (dark green). - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = (2,0,0))] tree_color: Option, /// The color of the tree on the map when (un)dead. Defaults to 0:0:1 (dark gray). - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = (0,0,1))] dead_tree_color: Option, /// The color of saplings of this tree. Defaults to 2:0:0 (dark green). - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = (2,0,0))] sapling_color: Option, /// The color of dead saplings of this tree. Defaults to 0:0:1 (dark gray). - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = (0,0,1))] dead_sapling_color: Option, /// The sapling of this tree will drown once the water on its tile reaches this level. Defaults to 4. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = 4)] sapling_drown_level: Option, /// The water depth at which this tree will drown. Exact behavior is unknown. Defaults to 7. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] + #[is_empty(value = 7)] tree_drown_level: Option, /// Token tags for the tree. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "crate::traits::IsEmpty::is_empty")] tags: Option>, } @@ -147,153 +173,6 @@ impl Tree { } } - /// Function to "clean" the raw. This is used to remove any empty list or strings, - /// and to remove any default values. By "removing" it means setting the value to None. - /// - /// This also will remove the metadata if `is_metadata_hidden` is true. - /// - /// Steps for all "Option" fields: - /// - Set any metadata to None if `is_metadata_hidden` is true. - /// - Set any empty string to None. - /// - Set any empty list to None. - /// - Set any default values to None. - /// - /// # Returns - /// - /// A new `Tree` object with all the fields cleaned. - #[allow(clippy::too_many_lines, clippy::cognitive_complexity)] - #[must_use] - pub fn cleaned(&self) -> Self { - let mut cleaned = self.clone(); - - if let Some(trunk_name) = &cleaned.trunk_name { - if trunk_name.is_empty() { - cleaned.trunk_name = None; - } - } - if default_checks::is_one_u8(cleaned.max_trunk_height) { - cleaned.max_trunk_height = None; - } - if default_checks::is_one_u8(cleaned.max_trunk_diameter) { - cleaned.max_trunk_diameter = None; - } - if default_checks::is_one_u8(cleaned.trunk_period) { - cleaned.trunk_period = None; - } - if default_checks::is_one_u8(cleaned.trunk_width_period) { - cleaned.trunk_width_period = None; - } - if let Some(branch_name) = &cleaned.branch_name { - if branch_name.is_empty() { - cleaned.branch_name = None; - } - } - if default_checks::is_zero_u8(cleaned.branch_density) { - cleaned.branch_density = None; - } - if default_checks::is_zero_u8(cleaned.branch_radius) { - cleaned.branch_radius = None; - } - if let Some(heavy_branches_name) = &cleaned.heavy_branches_name { - if heavy_branches_name.is_empty() { - cleaned.heavy_branches_name = None; - } - } - if default_checks::is_zero_u8(cleaned.heavy_branch_density) { - cleaned.heavy_branch_density = None; - } - if default_checks::is_zero_u8(cleaned.heavy_branch_radius) { - cleaned.heavy_branch_radius = None; - } - if default_checks::is_zero_u8(cleaned.trunk_branching) { - cleaned.trunk_branching = None; - } - if let Some(root_name) = &cleaned.root_name { - if root_name.is_empty() { - cleaned.root_name = None; - } - } - if default_checks::is_zero_u8(cleaned.root_density) { - cleaned.root_density = None; - } - if default_checks::is_zero_u8(cleaned.root_radius) { - cleaned.root_radius = None; - } - if let Some(twigs_name) = &cleaned.twigs_name { - if twigs_name.is_empty() { - cleaned.twigs_name = None; - } - } - if let Some(twigs_placement) = &cleaned.twigs_placement { - if twigs_placement.is_empty() { - cleaned.twigs_placement = None; - } - } - if let Some(cap_name) = &cleaned.cap_name { - if cap_name.is_empty() { - cleaned.cap_name = None; - } - } - if default_checks::is_one_u8(cleaned.cap_period) { - cleaned.cap_period = None; - } - if default_checks::is_zero_u8(cleaned.cap_radius) { - cleaned.cap_radius = None; - } - if let Some(tree_tile) = &cleaned.tree_tile { - if tree_tile.is_empty() { - cleaned.tree_tile = None; - } - } - if let Some(dead_tree_tile) = &cleaned.dead_tree_tile { - if dead_tree_tile.is_empty() { - cleaned.dead_tree_tile = None; - } - } - if let Some(sapling_tile) = &cleaned.sapling_tile { - if sapling_tile.is_empty() { - cleaned.sapling_tile = None; - } - } - if let Some(dead_sapling_tile) = &cleaned.dead_sapling_tile { - if dead_sapling_tile.is_empty() { - cleaned.dead_sapling_tile = None; - } - } - if let Some(tree_color) = &cleaned.tree_color { - if tree_color.is_default() { - cleaned.tree_color = None; - } - } - if let Some(dead_tree_color) = &cleaned.dead_tree_color { - if dead_tree_color.is_default() { - cleaned.dead_tree_color = None; - } - } - if let Some(sapling_color) = &cleaned.sapling_color { - if sapling_color.is_default() { - cleaned.sapling_color = None; - } - } - if let Some(dead_sapling_color) = &cleaned.dead_sapling_color { - if dead_sapling_color.is_default() { - cleaned.dead_sapling_color = None; - } - } - if default_checks::is_default_sapling_drown_level(cleaned.sapling_drown_level) { - cleaned.sapling_drown_level = None; - } - if default_checks::is_default_tree_drown_level(cleaned.tree_drown_level) { - cleaned.tree_drown_level = None; - } - if let Some(tags) = &cleaned.tags { - if tags.is_empty() { - cleaned.tags = None; - } - } - - cleaned - } /// Parse a new tag from the raw file into this raw object. /// /// # Arguments @@ -304,7 +183,7 @@ impl Tree { pub fn parse_tag(&mut self, key: &str, value: &str) { let Some(tag) = TREE_TOKENS.get(key) else { warn!( - "TreeParsing: called `Option::unwrap()` on a `None` value for presumed tree tag: {}", + "TreeParsing: called `Option::unwrap()` on a `None` value for presumed tree tag: {}", key ); return; diff --git a/lib/src/parser/info_file.rs b/lib/src/parser/info_file.rs index 510332c9..6c89ad51 100644 --- a/lib/src/parser/info_file.rs +++ b/lib/src/parser/info_file.rs @@ -4,9 +4,9 @@ use tracing::{debug, error, info}; use walkdir::DirEntry; use crate::{ + InfoFile, ParserError, metadata::{ParserOptions, RawModuleLocation}, utilities::subdirectories, - InfoFile, ParserError, }; /// The function `parse_module_info_files` parses module information files based on the provided options. @@ -65,12 +65,12 @@ pub fn parse_module_info_files(options: &ParserOptions) -> Result, } if options .locations_to_parse - .contains(&RawModuleLocation::Mods) + .contains(&RawModuleLocation::WorkshopMods) { info!("Dispatching info.txt parse for workshop mod raws"); if let Some(workshop_mods_path) = options .locations - .get_path_for_location(RawModuleLocation::Mods) + .get_path_for_location(RawModuleLocation::WorkshopMods) { results.extend(parse_module_info_files_at_location( &workshop_mods_path, diff --git a/lib/src/parser/parse.rs b/lib/src/parser/parse.rs index 6a485ea8..588e88f0 100644 --- a/lib/src/parser/parse.rs +++ b/lib/src/parser/parse.rs @@ -3,16 +3,15 @@ use std::path::Path; use tracing::{error, info}; use crate::{ - legends_export, + Creature, CreatureVariation, ParserError, legends_export, metadata::{ObjectType, ParserOptions, RawModuleLocation}, parser::{parse_location, parse_module}, - reader::{parse_raw_file, UnprocessedRaw}, + reader::{UnprocessedRaw, parse_raw_file}, traits::RawObject, utilities::{clone_raw_object_box, log_summary, summarize_raws, validate_options}, - Creature, CreatureVariation, ParserError, }; -use super::{info_file::parse_module_info_files, ParseResult}; +use super::{ParseResult, info_file::parse_module_info_files}; #[allow(clippy::too_many_lines)] /// Given the supplied `ParserOptions`, parse the raws and return a vector of boxed dynamic raw objects. @@ -85,12 +84,12 @@ pub fn parse(options: &ParserOptions) -> Result { } if options .locations_to_parse - .contains(&RawModuleLocation::Mods) + .contains(&RawModuleLocation::WorkshopMods) { info!("Dispatching parse for workshop/downloaded mods"); if let Some(workshop_mods_path) = options .locations - .get_path_for_location(RawModuleLocation::Mods) + .get_path_for_location(RawModuleLocation::WorkshopMods) { let parsed_raws = parse_location(&workshop_mods_path, &options)?; results.raws.extend(parsed_raws.parsed_raws); diff --git a/lib/src/raw_definitions/mod.rs b/lib/src/raw_definitions/mod.rs index 91c578fa..3a68b775 100644 --- a/lib/src/raw_definitions/mod.rs +++ b/lib/src/raw_definitions/mod.rs @@ -1,6 +1,7 @@ //! Support for transforming the DF raw files into their Rust-friendly representations. //! +mod raw_object_token_impl; pub mod tokens; pub use tokens::*; diff --git a/lib/src/utilities/caste_tag_lookup.rs b/lib/src/raw_definitions/raw_object_token_impl/caste_token.rs similarity index 67% rename from lib/src/utilities/caste_tag_lookup.rs rename to lib/src/raw_definitions/raw_object_token_impl/caste_token.rs index 2caed639..cef9c1ed 100644 --- a/lib/src/utilities/caste_tag_lookup.rs +++ b/lib/src/raw_definitions/raw_object_token_impl/caste_token.rs @@ -1,13 +1,22 @@ +use crate::Creature; use crate::raw_definitions::tokens::caste::CASTE_TOKENS; use crate::tags::CasteTag; +use crate::traits::RawObjectToken; use std::collections::HashMap; -use std::mem::{discriminant, Discriminant}; +use std::mem::{Discriminant, discriminant}; use std::sync::OnceLock; -impl CasteTag { - /// Retrieves the original string token key for this tag (e.g., "PET_VALUE"). - /// Uses a cached reverse-lookup map for O(1) performance. - pub fn get_key(&self) -> Option<&'static str> { +#[typetag::serialize] +impl RawObjectToken for CasteTag { + fn is_within(&self, object: &Creature) -> bool { + for caste in object.get_castes() { + if caste.get_tags().contains(self) { + return true; + } + } + false + } + fn get_key(&self) -> Option<&'static str> { // 1. Define a static storage for the reverse map static REVERSE_MAP: OnceLock, &'static str>> = OnceLock::new(); diff --git a/lib/src/utilities/creature_tag_lookup.rs b/lib/src/raw_definitions/raw_object_token_impl/creature_token.rs similarity index 71% rename from lib/src/utilities/creature_tag_lookup.rs rename to lib/src/raw_definitions/raw_object_token_impl/creature_token.rs index b13287a0..c34c1f55 100644 --- a/lib/src/utilities/creature_tag_lookup.rs +++ b/lib/src/raw_definitions/raw_object_token_impl/creature_token.rs @@ -1,13 +1,18 @@ +use crate::Creature; use crate::raw_definitions::tokens::creature::CREATURE_TOKENS; use crate::tags::CreatureTag; +use crate::traits::RawObjectToken; use std::collections::HashMap; -use std::mem::{discriminant, Discriminant}; +use std::mem::{Discriminant, discriminant}; use std::sync::OnceLock; -impl CreatureTag { - /// Retrieves the original string token key for this tag (e.g., "FREQUENCY"). - /// Uses a cached reverse-lookup map for O(1) performance. - pub fn get_key(&self) -> Option<&'static str> { +#[typetag::serialize] +impl RawObjectToken for CreatureTag { + fn is_within(&self, object: &Creature) -> bool { + object.get_tags().contains(self) + } + + fn get_key(&self) -> Option<&'static str> { // Static lazy-initialized reverse map from enum discriminant -> token string static REVERSE_MAP: OnceLock, &'static str>> = OnceLock::new(); diff --git a/lib/src/raw_definitions/raw_object_token_impl/mod.rs b/lib/src/raw_definitions/raw_object_token_impl/mod.rs new file mode 100644 index 00000000..af06dea4 --- /dev/null +++ b/lib/src/raw_definitions/raw_object_token_impl/mod.rs @@ -0,0 +1,2 @@ +mod caste_token; +mod creature_token; diff --git a/lib/src/raw_definitions/tokens/condition.rs b/lib/src/raw_definitions/tokens/condition.rs index f7249a65..4dc55f57 100644 --- a/lib/src/raw_definitions/tokens/condition.rs +++ b/lib/src/raw_definitions/tokens/condition.rs @@ -5,10 +5,10 @@ use crate::tags::ConditionTag; /// Map of condition tags to their string representation. pub static CONDITION_TOKENS: phf::Map<&'static str, ConditionTag> = phf::phf_map! { "DEFAULT" => ConditionTag::Default, + "PORTRAIT" => ConditionTag::Portrait, "ANIMATED" => ConditionTag::Animated, "CORPSE" => ConditionTag::Corpse, - "CHILD" => ConditionTag::Child, - "BABY" => ConditionTag::Baby, + "CHILD" => ConditionTag::ChildPrime, "TRAINED_WAR" => ConditionTag::TrainedWar, "TRAINED_HUNTER" => ConditionTag::TrainedHunter, "LIST_ICON" => ConditionTag::ListIcon, @@ -120,4 +120,33 @@ pub static CONDITION_TOKENS: phf::Map<&'static str, ConditionTag> = phf::phf_map "POET" => ConditionTag::Poet, "BARD" => ConditionTag::Bard, "DANCER" => ConditionTag::Dancer, + + "LS_PALETTE" => ConditionTag::LayerSetPalette, + "LS_PALETTE_FILE" => ConditionTag::LayerSetPaletteFile, + "LS_PALETTE_DEFAULT" => ConditionTag::LayerSetPaletteDefault, + "VERMIN_ALT" => ConditionTag::VerminAlt, + "LIGHT_VERMIN_ALT" => ConditionTag::LightVerminAlt, + "LIGHT_SWARM_SMALL" => ConditionTag::LightSwarmSmall, + "LIGHT_SWARM_MEDIUM" => ConditionTag::LightSwarmMedium, + "LIGHT_SWARM_LARGE" => ConditionTag::LightSwarmLarge, + "ITEM_QUALITY" => ConditionTag::ItemQuality, + "USE_PALETTE" => ConditionTag::UsePalette, + "USE_STANDARD_PALETTE_FROM_ITEM" => ConditionTag::UseStandardPaletteFromItem, + "BP_APPEARANCE_MODIFIER_RANGE" => ConditionTag::BodyPartAppearanceModifierRange, + "CONDITION_BP" => ConditionTag::BodyPart, + "LG_CONDITION_BP" => ConditionTag::LayerGroupBodyPart, + "TISSUE_MIN_DENSITY" => ConditionTag::TissueMinDensity, + "TISSUE_MAX_DENSITY" => ConditionTag::TissueMaxDensity, + "CONDITION_BODY_SIZE_MIN" => ConditionTag::BodySizeMin, + "CONDITION_BODY_SIZE_MAX" => ConditionTag::BodySizeMax, + "GLOW" => ConditionTag::Glow, + "GLOW_CHILD" => ConditionTag::GlowChild, + "GLOW_LEFT_GONE" => ConditionTag::GlowLeftGone, + "GLOW_RIGHT_GONE" => ConditionTag::GlowRightGone, + "LAW_ENFORCE" => ConditionTag::LawEnforcement, + "TAX_ESCORT" => ConditionTag::TaxEscort, + "ADVENTURER" => ConditionTag::Adventurer, + "EGG" => ConditionTag::Egg, + "CDI_LIST_ICON" => ConditionTag::CdiListIcon, + "BABY" => ConditionTag::BabyPrime, }; diff --git a/lib/src/reader/parsable_types.rs b/lib/src/reader/parsable_types.rs index 86caff63..7c33764a 100644 --- a/lib/src/reader/parsable_types.rs +++ b/lib/src/reader/parsable_types.rs @@ -1,14 +1,13 @@ use crate::metadata::ObjectType; /// The object types that can be parsed by the parser. -#[allow(dead_code)] -pub const PARSABLE_OBJECT_TYPES: [&ObjectType; 8] = [ - &ObjectType::Creature, - &ObjectType::Plant, - &ObjectType::Inorganic, - &ObjectType::Graphics, - &ObjectType::TilePage, - &ObjectType::Entity, - &ObjectType::MaterialTemplate, - &ObjectType::CreatureVariation, +pub const PARSABLE_OBJECT_TYPES: [ObjectType; 8] = [ + ObjectType::Creature, + ObjectType::Plant, + ObjectType::Inorganic, + ObjectType::Graphics, + ObjectType::TilePage, + ObjectType::Entity, + ObjectType::MaterialTemplate, + ObjectType::CreatureVariation, ]; diff --git a/lib/src/reader/parse_file.rs b/lib/src/reader/parse_file.rs index 89877299..d0203931 100644 --- a/lib/src/reader/parse_file.rs +++ b/lib/src/reader/parse_file.rs @@ -1,20 +1,20 @@ use crate::{ + InfoFile, ParserError, constants::DF_ENCODING, creature_variation::CreatureVariation, entity::Entity, graphic::Graphic, inorganic::Inorganic, material_template::MaterialTemplate, - metadata::{ObjectType, ParserOptions, RawMetadata, RawModuleLocation, OBJECT_TOKEN_MAP}, + metadata::{OBJECT_TOKEN_MAP, ObjectType, ParserOptions, RawMetadata, RawModuleLocation}, plant::Plant, raw_definitions::GRAPHIC_TYPE_TOKENS, - reader::{unprocessed_raw::UnprocessedRaw, PARSABLE_OBJECT_TYPES}, + reader::{PARSABLE_OBJECT_TYPES, unprocessed_raw::UnprocessedRaw}, regex::RAW_TOKEN_RE, tags::{GraphicTypeTag, ModificationTag}, tile_page::TilePage, - traits::RawObject, + traits::{IsEmpty, RawObject}, utilities::try_get_file, - InfoFile, ParserError, }; use encoding_rs_io::DecodeReaderBytesBuilder; use std::{ @@ -25,32 +25,42 @@ use tracing::{debug, error, trace, warn}; use super::{parse_result::FileParseResult, read_raw_file_type}; -/// Parse a raw file into a list of parsed raws and a list of unprocessed raws. -/// -/// This function performs the following steps: +/// Parses a raw file at the specified path. /// -/// 1. Parse the raw file into a list of parsed raws. -/// 2. Filter the parsed raws into a list of unprocessed raws. -/// 3. Return the parsed raws and unprocessed raws. -/// -/// The unprocessed raws need to be resolved so that they can become parsed raws. This is done -/// by calling `resolve` on an `UnprocessedRaw` object. That requires the entirety of the parsed -/// raws to be passed in, so that it can find the raws it needs to resolve against. +/// This function reads and parses a single Dwarf Fortress raw file, extracting +/// all object definitions it contains. /// /// # Arguments /// -/// * `raw_file_path` - The path to the raw file to parse. -/// * `options` - The parser options to use when parsing the raw file. +/// * `raw_file_path` - Path to the raw file to parse +/// * `options` - Parser configuration options /// /// # Returns /// -/// * `Result` - The results of parsing the raw file. +/// Returns a `FileParseResult` containing all parsed objects from the file. /// /// # Errors /// -/// * `ParserError::InvalidRawFile` - If the raw file is invalid. -/// * `ParserError::IOError` - If there is an error reading the raw file. -/// * `ParserError::InfoFileError` - If there is an error reading the module info file. +/// Returns `ParserError` +/// - `IOError`: The file cannot be read +/// - `IOError`: The file contains invalid UTF-8 or Latin-1 encoding +/// - `InvalidRawFile`: The file structure is malformed +/// - `InfoFileError`: There is an error reading the module's info.txt file +/// +/// # Examples +/// +/// ```no_run +/// use dfraw_parser::{parse_raw_file, metadata::ParserOptions}; +/// use std::path::Path; +/// +/// let path = Path::new("data/vanilla/objects/creature_standard.txt"); +/// let options = ParserOptions::default(); +/// +/// match parse_raw_file(&path, &options) { +/// Ok(result) => println!("Parsed {} objects", result.parsed_raws.len()), +/// Err(e) => eprintln!("Parse error: {}", e), +/// } +/// ``` #[allow(dead_code)] pub fn parse_raw_file>( raw_file_path: &P, @@ -62,7 +72,7 @@ pub fn parse_raw_file>( ) { Ok(m) => m, Err(e) => { - warn!("parse_raw_file: Using an empty InfoFile because of error parsing the file"); + warn!("parse_raw_file: Using an empty InfoFile: {e}"); debug!("{e:?}"); InfoFile::new( raw_file_path @@ -148,8 +158,7 @@ pub fn parse_raw_file_with_info>( // If we aren't supposed to parse this type, we should quit here if !options.object_types_to_parse.contains(&object_type) { debug!( - "parse_raw_file_with_info: Quitting early because object type {:?} is not included in options!", - object_type + "parse_raw_file_with_info: quitting early: options.object_types_to_parse doesn't include '{object_type}'" ); return Ok(FileParseResult { parsed_raws: Vec::new(), @@ -158,10 +167,9 @@ pub fn parse_raw_file_with_info>( } // If the type of object is not in our known_list, we should quit here - if !PARSABLE_OBJECT_TYPES.contains(&&object_type) { + if !PARSABLE_OBJECT_TYPES.contains(&object_type) { debug!( - "parse_raw_file_with_info: Quitting early because object type {:?} is not parsable!", - object_type + "parse_raw_file_with_info: quitting early: '{object_type}' is not in PARSABLE_OBJECT_TYPES" ); return Ok(FileParseResult { parsed_raws: Vec::new(), @@ -213,8 +221,7 @@ pub fn parse_raw_file_with_info>( trace!( "parse_raw_file_with_info: Key: {} Value: {}", - captured_key, - captured_value + captured_key, captured_value ); match captured_key { @@ -389,21 +396,21 @@ pub fn parse_raw_file_with_info>( last_parsed_type = ObjectType::Entity; } "GO_TO_END" => { - debug!("began tracking AddToEnding modification"); + trace!("began tracking AddToEnding modification"); // Push the current modification to the unprocessed raw temp_unprocessed_raw.add_modification(current_modification.clone()); // Update the current modification to be an AddToEnding current_modification = ModificationTag::AddToEnding { raws: Vec::new() }; } "GO_TO_START" => { - debug!("began tracking AddToBeginning modification"); + trace!("began tracking AddToBeginning modification"); // Push the current modification to the unprocessed raw temp_unprocessed_raw.add_modification(current_modification.clone()); // Update the current modification to be an AddToBeginning current_modification = ModificationTag::AddToBeginning { raws: Vec::new() }; } "GO_TO_TAG" => { - debug!("began tracking AddBeforeTag:{captured_value} modification"); + trace!("began tracking AddBeforeTag:{captured_value} modification"); // Push the current modification to the unprocessed raw temp_unprocessed_raw.add_modification(current_modification.clone()); // Update the current modification to be an AddBeforeTag @@ -413,14 +420,14 @@ pub fn parse_raw_file_with_info>( }; } "COPY_TAGS_FROM" => { - debug!("began tracking CopyTagsFrom:{captured_value} modification"); + trace!("began tracking CopyTagsFrom:{captured_value} modification"); // Push the CopyTagsFrom modification to the unprocessed raw temp_unprocessed_raw.add_modification(ModificationTag::CopyTagsFrom { identifier: captured_value.to_string(), }); } "APPLY_CREATURE_VARIATION" => { - debug!("began tracking ApplyCreatureVariation:{captured_value} modification"); + trace!("began tracking ApplyCreatureVariation:{captured_value} modification"); // Push the ApplyCreatureVariation modification to the unprocessed raw temp_unprocessed_raw.add_modification( ModificationTag::ApplyCreatureVariation { @@ -520,7 +527,7 @@ pub fn parse_raw_file_with_info>( } debug!( - "parse_raw_file_with_info: Parsed {} raws from {}", + "parse_raw_file_with_info: parsed {} raws from {}", created_raws.len(), raw_filename ); diff --git a/lib/src/reader/unprocessed_raw.rs b/lib/src/reader/unprocessed_raw.rs index e30e5ff8..f6fee9f3 100644 --- a/lib/src/reader/unprocessed_raw.rs +++ b/lib/src/reader/unprocessed_raw.rs @@ -1,13 +1,13 @@ -use tracing::{debug, warn}; +use tracing::{debug, trace}; use crate::{ + ParserError, creature::Creature, creature_variation::CreatureVariation, metadata::{ObjectType, RawMetadata}, tags::ModificationTag, traits::RawObject, utilities::singularly_apply_creature_variation, - ParserError, }; /// An unprocessed raw object @@ -147,56 +147,55 @@ impl UnprocessedRaw { match modification.clone() { ModificationTag::AddBeforeTag { tag, raws } => { // Check if last modification is also an `AddBeforeTag` - if let Some(last_modification) = self.modifications.last_mut() { - if let ModificationTag::AddBeforeTag { + if let Some(last_modification) = self.modifications.last_mut() + && let ModificationTag::AddBeforeTag { tag: last_tag, raws: last_raws, } = last_modification - { - // Check if the tags are the same - if &tag == last_tag { - // They are the same, so we can combine them - last_raws.extend(raws); - return; - } + { + // Check if the tags are the same + if &tag == last_tag { + // They are the same, so we can combine them + last_raws.extend(raws); + return; } } } ModificationTag::AddToBeginning { raws } => { // Check if last modification is also an `AddToBeginning` - if let Some(last_modification) = self.modifications.last_mut() { - if let ModificationTag::AddToBeginning { raws: last_raws } = last_modification { - // They are the same, so we can combine them - last_raws.extend(raws); - return; - } + if let Some(last_modification) = self.modifications.last_mut() + && let ModificationTag::AddToBeginning { raws: last_raws } = last_modification + { + // They are the same, so we can combine them + last_raws.extend(raws); + return; } } ModificationTag::AddToEnding { raws } => { // Check if last modification is also an `AddToEnding` - if let Some(last_modification) = self.modifications.last_mut() { - if let ModificationTag::AddToEnding { raws: last_raws } = last_modification { - // They are the same, so we can combine them - last_raws.extend(raws); - return; - } + if let Some(last_modification) = self.modifications.last_mut() + && let ModificationTag::AddToEnding { raws: last_raws } = last_modification + { + // They are the same, so we can combine them + last_raws.extend(raws); + return; } } ModificationTag::MainRawBody { raws } => { // Check if last modification is also an `MainRawBody` - if let Some(last_modification) = self.modifications.last_mut() { - if let ModificationTag::MainRawBody { raws: last_raws } = last_modification { - // They are the same, so we can combine them - last_raws.extend(raws); - return; - } + if let Some(last_modification) = self.modifications.last_mut() + && let ModificationTag::MainRawBody { raws: last_raws } = last_modification + { + // They are the same, so we can combine them + last_raws.extend(raws); + return; } } _ => {} } // If we get here, we can't combine the modifications, so we just add it - debug!("Adding modification: {:?}", modification); + trace!("Adding modification: {:?}", modification); self.modifications.push(modification); } @@ -215,7 +214,6 @@ impl UnprocessedRaw { /// # Errors /// /// * `ParserError::NotYetImplemented` - If the raw type is not yet implemented - #[allow(dead_code)] pub fn resolve( &mut self, creature_variations: &[CreatureVariation], @@ -254,7 +252,7 @@ impl UnprocessedRaw { .collect::>(); if source_creature_options.len() > 1 { - warn!( + debug!( "Found {} creatures with identifier `{}` to copy tags from. Using the newest one.", source_creature_options.len(), identifier @@ -264,10 +262,6 @@ impl UnprocessedRaw { .get_module_numerical_version() .cmp(b.get_metadata().get_module_numerical_version()) }); - debug!( - "Sorted creature options for {}: {:#?}", - identifier, source_creature_options - ); } if let Some(source_creature) = source_creature_options.first() { @@ -313,14 +307,13 @@ impl UnprocessedRaw { Ok(Box::new(creature)) } - #[allow(dead_code)] fn collapse_modifications(&mut self) { // Grab the base raws first let mut collapsed_raws: Vec = Vec::new(); for modification in &self.modifications { if let ModificationTag::MainRawBody { raws } = modification { collapsed_raws.extend(raws.clone()); - debug!("collapsed {} base raws", raws.len()); + trace!("collapsed {} base raws", raws.len()); } } @@ -333,7 +326,7 @@ impl UnprocessedRaw { for modification in &self.modifications { if let ModificationTag::AddToEnding { raws } = modification { add_to_ending.extend(raws.clone()); - debug!("collapsed {} add to ending raws", raws.len()); + trace!("collapsed {} add to ending raws", raws.len()); } } @@ -346,7 +339,7 @@ impl UnprocessedRaw { for modification in &self.modifications { if let ModificationTag::AddToBeginning { raws } = modification { add_to_beginning.extend(raws.clone()); - debug!("collapsed {} add to beginning raws", raws.len()); + trace!("collapsed {} add to beginning raws", raws.len()); } } @@ -376,7 +369,7 @@ impl UnprocessedRaw { // If we found the index, insert the raws before the tag (without replacing) if let Some(index) = index { collapsed_raws.splice(index..index, raws.clone()); - debug!( + trace!( "collapsed {} add before tag raws, before tag {}", raws.len(), tag @@ -384,7 +377,7 @@ impl UnprocessedRaw { } else { // If we didn't find the index, just add the raws to the end collapsed_raws.extend(raws.clone()); - warn!( + debug!( "resolve: Unable to find tag `{}` to add raws before. Adding raws to end instead.", tag ); diff --git a/lib/src/traits/cleanable.rs b/lib/src/traits/cleanable.rs new file mode 100644 index 00000000..3f10a49a --- /dev/null +++ b/lib/src/traits/cleanable.rs @@ -0,0 +1,25 @@ +//! trait for being able to 'clean' + +use crate::traits::IsEmpty; + +/// Allows for the trait to be reduced. +/// +/// This allows a struct to be "reduced" if it is holding an `Option` +/// with a default value (it sets that field to `None`) +pub trait Cleanable: IsEmpty { + /// Reduces the struct to take up less memory in-place. + /// + /// This should set empty Options to None and recursively clean child elements. + fn clean(&mut self); + + /// Returns a cleaned copy of the struct + #[must_use] + fn cleaned(&self) -> Self + where + Self: Clone, + { + let mut cleaned = self.clone(); + cleaned.clean(); + cleaned + } +} diff --git a/lib/src/traits/is_empty.rs b/lib/src/traits/is_empty.rs new file mode 100644 index 00000000..e51e3ae3 --- /dev/null +++ b/lib/src/traits/is_empty.rs @@ -0,0 +1,133 @@ +/// Helper to check if a type is effectively "default", "empty", or "zeroed". +/// +/// This is used by the `Cleanable` macro to determine if an `Option` can be +/// safely set to `None`. +pub trait IsEmpty { + fn is_empty(&self) -> bool; +} + +impl IsEmpty for bool { + #[inline] + fn is_empty(&self) -> bool { + *self + } +} + +impl IsEmpty for u8 { + #[inline] + fn is_empty(&self) -> bool { + *self == 0 + } +} + +impl IsEmpty for u16 { + #[inline] + fn is_empty(&self) -> bool { + *self == 0 + } +} + +impl IsEmpty for u32 { + #[inline] + fn is_empty(&self) -> bool { + *self == 0 + } +} + +impl IsEmpty for u64 { + #[inline] + fn is_empty(&self) -> bool { + *self == 0 + } +} + +impl IsEmpty for u128 { + #[inline] + fn is_empty(&self) -> bool { + *self == 0 + } +} + +impl IsEmpty for f32 { + #[inline] + fn is_empty(&self) -> bool { + *self == 0.0 + } +} + +impl IsEmpty for f64 { + #[inline] + fn is_empty(&self) -> bool { + *self == 0.0 + } +} + +impl IsEmpty for i8 { + #[inline] + fn is_empty(&self) -> bool { + *self == 0 + } +} + +impl IsEmpty for i16 { + #[inline] + fn is_empty(&self) -> bool { + *self == 0 + } +} + +impl IsEmpty for i32 { + #[inline] + fn is_empty(&self) -> bool { + *self == 0 + } +} + +impl IsEmpty for i64 { + #[inline] + fn is_empty(&self) -> bool { + *self == 0 + } +} + +impl IsEmpty for i128 { + #[inline] + fn is_empty(&self) -> bool { + *self == 0 + } +} + +impl IsEmpty for [u32; 2] { + #[inline] + fn is_empty(&self) -> bool { + self[0] == 0 && self[1] == 0 + } +} + +impl IsEmpty for [i32; 2] { + #[inline] + fn is_empty(&self) -> bool { + self[0] == 0 && self[1] == 0 + } +} + +impl IsEmpty for String { + #[inline] + fn is_empty(&self) -> bool { + self.is_empty() + } +} + +impl IsEmpty for Vec { + #[inline] + fn is_empty(&self) -> bool { + self.is_empty() + } +} + +impl IsEmpty for Option { + #[inline] + fn is_empty(&self) -> bool { + self.as_ref().is_none_or(IsEmpty::is_empty) + } +} diff --git a/lib/src/traits/mod.rs b/lib/src/traits/mod.rs index 89d96029..66e0861c 100644 --- a/lib/src/traits/mod.rs +++ b/lib/src/traits/mod.rs @@ -1,17 +1,37 @@ //! Shared traits amongst various parsed objects. +//! +//! This module defines core traits implemented by parsed raw file objects: +//! - [`RawObject`] - Base trait for all parsed raw objects +//! - [`Searchable`] - Provides search string functionality +//! - [`TokenParser`] - Helper trait for parsing raw file tokens +//! - [`TagOperations`] - Operations for manipulating tags +//! - [`CreatureVariationRequirements`] - Handling creature variation requirements +//! +//! # Examples +//! +//! Using the Searchable trait: +//! ``` +//! use dfraw_parser::traits::Searchable; +//! # use dfraw_parser::Creature; +//! +//! # let creature = Creature::default(); +//! let search_string = creature.get_search_vec(); +//! ``` -pub mod creature_variation_requirements; -pub mod raw_object; -pub mod raw_object_token; -pub mod raw_object_token_to_any; -pub mod searchable; -pub mod tag_operations; -pub mod token_parser; +mod cleanable; +mod creature_variation_requirements; +mod is_empty; +mod raw_object; +mod raw_object_token; +mod searchable; +mod tag_operations; +mod token_parser; +pub use cleanable::Cleanable; pub use creature_variation_requirements::CreatureVariationRequirements; +pub use is_empty::IsEmpty; pub use raw_object::RawObject; pub use raw_object_token::RawObjectToken; -pub use raw_object_token_to_any::RawObjectTokenToAny; pub use searchable::Searchable; pub use tag_operations::TagOperations; pub use token_parser::TokenParser; diff --git a/lib/src/traits/raw_object.rs b/lib/src/traits/raw_object.rs index b32f7274..3f358c8d 100644 --- a/lib/src/traits/raw_object.rs +++ b/lib/src/traits/raw_object.rs @@ -3,7 +3,10 @@ use std::any::Any; -use crate::metadata::{ObjectType, RawMetadata}; +use crate::{ + metadata::{ObjectType, RawMetadata}, + traits::Cleanable, +}; use super::searchable::Searchable; @@ -12,13 +15,11 @@ use super::searchable::Searchable; /// The `RawObject` trait is implemented by all raw objects. This trait is used /// to provide a common interface for all raw objects, so that they can be /// stored in a single vector. It also provides a common interface for parsing. -pub trait RawObject: RawObjectToAny + Send + Sync + Searchable { +pub trait RawObject: RawObjectToAny + Send + Sync + Searchable + Cleanable { /// Get the metadata for the raw. fn get_metadata(&self) -> RawMetadata; /// Get the identifier of the raw. fn get_identifier(&self) -> &str; - /// Returns true if the raw is empty. - fn is_empty(&self) -> bool; /// Get the type of the raw. fn get_type(&self) -> &ObjectType; /// Parse a new tag from the raw file into this raw object. @@ -35,11 +36,15 @@ pub trait RawObject: RawObjectToAny + Send + Sync + Searchable { /// If no name is found, the identifier is returned instead. /// This is used for searching. fn get_name(&self) -> &str; - /// Function to "clean" the creature. This is used to remove any empty list or strings, + /// Function to return "flag" tokens (as strings) for things like `[FLIER]` or `[INTELLIGENT]`, etc + fn get_searchable_tokens(&self) -> Vec<&str>; + /// Function to "clean" the raw. This is used to remove any empty list or strings, /// and to remove any default values. By "removing" it means setting the value to None. /// /// This also will remove the metadata if is_metadata_hidden is true. - fn clean_self(&mut self); + fn clean_self(&mut self) { + self.clean() + } } /// The `RawObjectToAny` trait is implemented by all raw objects. This trait is diff --git a/lib/src/traits/raw_object_token.rs b/lib/src/traits/raw_object_token.rs index 549d060f..a5cb9524 100644 --- a/lib/src/traits/raw_object_token.rs +++ b/lib/src/traits/raw_object_token.rs @@ -7,6 +7,11 @@ use super::RawObject; /// to provide a common interface for all raw object tokens, so that they can be /// stored in a single vector. It also provides a common interface for parsing. pub trait RawObjectToken { - /// Check if the token is within the object. + /// Check if the token is part of the given `RawObject`. This simplifies searching through + /// a `RawObject`'s token arrays to find the token, instead letting that be implemented + /// deliberately/specifically for each combo of token and raw object. fn is_within(&self, object: &T) -> bool; + /// Retrieves the original string token key for this tag (e.g., "PET_VALUE"). This is done + /// by reversing the string --> token mapping that already exists, providing O(1) lookup. + fn get_key(&self) -> Option<&'static str>; } diff --git a/lib/src/traits/raw_object_token_to_any.rs b/lib/src/traits/raw_object_token_to_any.rs deleted file mode 100644 index ed9f4d70..00000000 --- a/lib/src/traits/raw_object_token_to_any.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! Raw Object trait - -#[allow(clippy::module_name_repetitions)] -#[typetag::serde(tag = "type")] -/// The `RawObjectToken` trait is implemented by all raw object tokens. This trait is used -/// to provide a common interface for all raw object tokens, so that they can be -/// stored in a single vector. It also provides a common interface for parsing. -pub trait RawObjectToken: RawObjectTokenToAny + std::fmt::Debug + Send + Sync {} - -impl std::fmt::Display for dyn RawObjectToken { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{self:?}") - } -} - -/// The `RawObjectTokenToAny` trait is implemented by all raw object tokens. This trait is -/// used to be able to downcast a raw object token to `Any`, so it can be downcast to -/// a specific raw object token type. -pub trait RawObjectTokenToAny: 'static { - /// Downcast the raw object token to `Any` - fn as_any(&self) -> &dyn std::any::Any; -} - -impl RawObjectTokenToAny for T { - fn as_any(&self) -> &dyn std::any::Any { - self - } -} diff --git a/lib/src/traits/searchable.rs b/lib/src/traits/searchable.rs index aec6d208..c4fa45fc 100644 --- a/lib/src/traits/searchable.rs +++ b/lib/src/traits/searchable.rs @@ -7,58 +7,3 @@ pub trait Searchable { /// object. fn get_search_vec(&self) -> Vec; } - -/// The `get_search_string` function takes an object that implements the `Searchable` trait and -/// returns a string that can be used to search for the object. -pub fn get_search_string(object: &dyn Searchable) -> String { - object.get_search_vec().join(" ") -} - -/// The `clean_search_vec` function takes a vector of strings, cleans and filters the strings, and -/// returns a new vector. This is used to clean up search terms before they are used to search for -/// raws. -/// -/// This function is used by the `Searchable` trait. -/// -/// Arguments: -/// -/// * `vec`: A vector of strings representing search terms. -/// -/// Returns: -/// -/// The function `clean_search_vec` returns a `Vec`. -pub fn clean_search_vec(vec: &[String]) -> Vec { - let mut vec: Vec = vec.join(" ").split_whitespace().map(String::from).collect(); - - // Lowercase everything - vec = vec.iter().map(|x| x.to_lowercase()).collect(); - - // Remove any periods, commas, etc. - vec = vec - .iter() - .map(|x| x.replace('.', "")) - .map(|x| x.replace(',', "")) - .map(|x| x.replace('(', "")) - .map(|x| x.replace(')', "")) - .map(|x| x.replace(';', "")) - // ! This is dangerous, because it can obscure bad tag parsing. - .map(|x| x.replace(':', " ")) - .collect(); - - // Uniq the vec - vec.sort(); - vec.dedup(); - - // Remove some generic words - vec.retain(|x| !x.eq_ignore_ascii_case("creature")); - vec.retain(|x| !x.eq_ignore_ascii_case("all")); - vec.retain(|x| !x.eq_ignore_ascii_case("the")); - vec.retain(|x| !x.eq_ignore_ascii_case("of")); - vec.retain(|x| !x.eq_ignore_ascii_case("in")); - vec.retain(|x| !x.eq_ignore_ascii_case("and")); - vec.retain(|x| !x.eq_ignore_ascii_case("a")); - vec.retain(|x| !x.eq_ignore_ascii_case("an")); - vec.retain(|x| !x.eq_ignore_ascii_case("with")); - - vec -} diff --git a/lib/src/traits/to_raw_string.rs b/lib/src/traits/to_raw_string.rs new file mode 100644 index 00000000..5a2e8b21 --- /dev/null +++ b/lib/src/traits/to_raw_string.rs @@ -0,0 +1,8 @@ +//! to raw string trait + +/// Provides a way to dispay the trait as it would in a +/// Dwarf Fortress raw file. +pub trait ToRawFileString { + /// Formats the trait as its 'raw' implmentation. + fn to_raw(&self) -> String; +} diff --git a/lib/src/traits/token_parser.rs b/lib/src/traits/token_parser.rs index 5b08547a..ff66f437 100644 --- a/lib/src/traits/token_parser.rs +++ b/lib/src/traits/token_parser.rs @@ -1,6 +1,31 @@ +//! Token parsing utilities for Dwarf Fortress raw file tokens. + use std::fmt::Debug; use std::str::FromStr; +/// Trait for parsing tokens from Dwarf Fortress raw files. +/// +/// This trait provides helper methods for parsing values from raw file tokens, +/// with special handling for the "NONE" keyword and error cases. +/// +/// # Examples +/// +/// ```rust +/// use dfraw_parser::traits::TokenParser; +/// +/// #[derive(Debug)] +/// struct MyParser; +/// +/// impl TokenParser for MyParser {} +/// +/// let parser = MyParser; +/// let value: Option = parser.parse_value("42"); +/// assert_eq!(value, Some(42)); +/// +/// // "NONE" returns default value (0) +/// let none_value: Option = parser.parse_value("NONE"); +/// assert_eq!(none_value, Some(0)); +/// ``` pub trait TokenParser: Debug { /// Internal Helper: Parses a string into T. /// specialized logic: If parsing fails and the value is "NONE", returns T::default() (0). diff --git a/lib/src/utilities/caste_tag_flags.rs b/lib/src/utilities/caste_tag_flags.rs new file mode 100644 index 00000000..53dc143f --- /dev/null +++ b/lib/src/utilities/caste_tag_flags.rs @@ -0,0 +1,173 @@ +use crate::tags::CasteTag; + +impl CasteTag { + /// Array of all caste tags that represent boolean flags. + /// + /// These tags don't require additional parameters and are either present or absent. + /// Used for efficient lookup and validation of flag-type tags. + pub const FLAG_TOKENS: [&CasteTag; 163] = [ + &CasteTag::AdoptsOwner, + &CasteTag::AlcoholDependent, + &CasteTag::AllActive, + &CasteTag::AmbushPredator, + &CasteTag::Amphibious, + &CasteTag::Aquatic, + &CasteTag::ArenaRestricted, + &CasteTag::AtPeaceWithWildlife, + &CasteTag::Benign, + &CasteTag::BloodSucker, + &CasteTag::BoneCarn, + &CasteTag::CanLearn, + &CasteTag::CanOpenDoors, + &CasteTag::CanSpeak, + &CasteTag::CannotBreatheAir, + &CasteTag::CannotBreatheWater, + &CasteTag::CannotClimb, + &CasteTag::CannotJump, + &CasteTag::CannotUndead, + &CasteTag::Carnivore, + &CasteTag::CaveAdaptation, + &CasteTag::CommonDomestic, + &CasteTag::ConvertedSpouse, + &CasteTag::CookableLive, + &CasteTag::Crazed, + &CasteTag::Crepuscular, + &CasteTag::CuriousBeast, + &CasteTag::CuriousBeastEater, + &CasteTag::CuriousBeastGuzzler, + &CasteTag::CuriousBeastItem, + &CasteTag::Demon, + &CasteTag::DieWhenVerminBite, + &CasteTag::Diurnal, + &CasteTag::DiveHuntsVermin, + &CasteTag::FeatureAttackGroup, + &CasteTag::FeatureBeast, + &CasteTag::Female, + &CasteTag::FireImmune, + &CasteTag::FireImmuneSuper, + &CasteTag::FishItem, + &CasteTag::FleeQuick, + &CasteTag::Flier, + &CasteTag::GetsInfectionsFromRot, + &CasteTag::GetsWoundInfections, + &CasteTag::Grasp, + &CasteTag::HasBlood, + &CasteTag::HasNerves, + &CasteTag::HasShell, + &CasteTag::HuntsVermin, + &CasteTag::Immobile, + &CasteTag::ImmobileLand, + &CasteTag::Intelligent, + &CasteTag::LairHunter, + &CasteTag::LargePredator, + &CasteTag::LaysEggs, + &CasteTag::LightGen, + &CasteTag::LikesFighting, + &CasteTag::Lisp, + &CasteTag::LockPicker, + &CasteTag::Magical, + &CasteTag::MagmaVision, + &CasteTag::Male, + &CasteTag::MannerismBreath, + &CasteTag::MannerismEyelids, + &CasteTag::MannerismLaugh, + &CasteTag::MannerismPosture, + &CasteTag::MannerismSit, + &CasteTag::MannerismSmile, + &CasteTag::MannerismStretch, + &CasteTag::MannerismWalk, + &CasteTag::Matutinal, + &CasteTag::Meanderer, + &CasteTag::Megabeast, + &CasteTag::Mischievous, + &CasteTag::Mount, + &CasteTag::MountExotic, + &CasteTag::MultipartFullVision, + &CasteTag::MultipleLitterRare, + &CasteTag::Natural, + &CasteTag::NaturalAnimal, + &CasteTag::NightCreature, + &CasteTag::NightCreatureBogeyman, + &CasteTag::NightCreatureExperimenter, + &CasteTag::NightCreatureHunter, + &CasteTag::NightCreatureNightmare, + &CasteTag::NoConnectionsForMovement, + &CasteTag::NoDizziness, + &CasteTag::NoDrink, + &CasteTag::NoEat, + &CasteTag::NoFall, + &CasteTag::NoFevers, + &CasteTag::NoGender, + &CasteTag::NoPhysicalAttributeGain, + &CasteTag::NoPhysicalAttributeRust, + &CasteTag::NoSleep, + &CasteTag::NoSpring, + &CasteTag::NoSummer, + &CasteTag::NoThoughtCenterForMovement, + &CasteTag::NoUnitTypeColor, + &CasteTag::NoVegetationDisturbance, + &CasteTag::NoWinter, + &CasteTag::NoBones, + &CasteTag::NoBreathe, + &CasteTag::Nocturnal, + &CasteTag::NoEmotion, + &CasteTag::NoExert, + &CasteTag::NoFear, + &CasteTag::NoMeat, + &CasteTag::NoNausea, + &CasteTag::NoPain, + &CasteTag::NoSkin, + &CasteTag::NoSkull, + &CasteTag::NoSmellyRot, + &CasteTag::NoStuckIns, + &CasteTag::NoStun, + &CasteTag::NotButcherable, + &CasteTag::NotFireImmune, + &CasteTag::NotLiving, + &CasteTag::NoThought, + &CasteTag::OpposedToLife, + &CasteTag::OutsiderControllable, + &CasteTag::PackAnimal, + &CasteTag::ParalyzeImmune, + &CasteTag::PatternFlier, + &CasteTag::Pearl, + &CasteTag::Pet, + &CasteTag::PetExotic, + &CasteTag::Power, + &CasteTag::RaceGait, + &CasteTag::RemainsOnVerminBiteDeath, + &CasteTag::RemainsUndetermined, + &CasteTag::ReturnsVerminKillsToOwner, + &CasteTag::SemiMegabeast, + &CasteTag::SlowLearner, + &CasteTag::SmallRemains, + &CasteTag::SpouseConversionTarget, + &CasteTag::SpouseConverter, + &CasteTag::SpreadEvilSpheresIfRuler, + &CasteTag::StanceClimber, + &CasteTag::StandardGrazer, + &CasteTag::StrangeMoods, + &CasteTag::Supernatural, + &CasteTag::SwimsInnate, + &CasteTag::SwimsLearned, + &CasteTag::ThickWeb, + &CasteTag::Titan, + &CasteTag::Trainable, + &CasteTag::TrainableHunting, + &CasteTag::TrainableWar, + &CasteTag::Trances, + &CasteTag::TrapAvoid, + &CasteTag::UnderSwim, + &CasteTag::UniqueDemon, + &CasteTag::Vegetation, + &CasteTag::VerminHateable, + &CasteTag::VerminMicro, + &CasteTag::VerminNoFish, + &CasteTag::VerminNoRoam, + &CasteTag::VerminNoTrap, + &CasteTag::VerminHunter, + &CasteTag::Vespertine, + &CasteTag::WagonPuller, + &CasteTag::WebImmune, + ]; +} diff --git a/lib/src/utilities/creature_tag_flags.rs b/lib/src/utilities/creature_tag_flags.rs new file mode 100644 index 00000000..9075ceb1 --- /dev/null +++ b/lib/src/utilities/creature_tag_flags.rs @@ -0,0 +1,33 @@ +use crate::tags::CreatureTag; + +impl CreatureTag { + pub const FLAG_TOKENS: [&CreatureTag; 27] = [ + &CreatureTag::AllCastesAlive, + &CreatureTag::ArtificialHiveable, + &CreatureTag::DoesNotExist, + &CreatureTag::Equipment, + &CreatureTag::EquipmentWagon, + &CreatureTag::Evil, + &CreatureTag::Fanciful, + &CreatureTag::Generated, + &CreatureTag::Good, + &CreatureTag::LargeRoaming, + &CreatureTag::LocalPopsControllable, + &CreatureTag::LocalPopsProduceHeroes, + &CreatureTag::LooseClusters, + &CreatureTag::MatesToBreed, + &CreatureTag::Mundane, + &CreatureTag::OccursAsEntityRace, + &CreatureTag::Savage, + &CreatureTag::SmallRace, + &CreatureTag::TwoGenders, + &CreatureTag::Ubiquitous, + &CreatureTag::Utterances, + &CreatureTag::VerminEater, + &CreatureTag::VerminFish, + &CreatureTag::VerminGrounder, + &CreatureTag::VerminRotter, + &CreatureTag::VerminSoil, + &CreatureTag::VerminSoilColony, + ]; +} diff --git a/lib/src/utilities/file_operations.rs b/lib/src/utilities/file_operations.rs index 1fa9fbe7..3b923766 100644 --- a/lib/src/utilities/file_operations.rs +++ b/lib/src/utilities/file_operations.rs @@ -19,19 +19,19 @@ use tracing::{debug, error, info, trace, warn}; use walkdir::WalkDir; use crate::{ + ParserError, creature::Creature, creature_variation::CreatureVariation, entity::Entity, graphic::Graphic, inorganic::Inorganic, material_template::MaterialTemplate, - metadata::{ObjectType, ParserOptions, RawMetadata}, + metadata::{ObjectType, ParserOptions, RawMetadata, RawModuleLocation}, plant::Plant, regex::VARIATION_ARGUMENT_RE, select_creature::SelectCreature, tile_page::TilePage, - traits::{searchable::get_search_string, CreatureVariationRequirements, RawObject, Searchable}, - ParserError, + traits::{CreatureVariationRequirements, IsEmpty, RawObject}, }; #[tracing::instrument] @@ -262,7 +262,7 @@ pub fn validate_options(options: &ParserOptions) -> Result Result Result String { - get_search_string(raw_object) -} diff --git a/lib/src/utilities/mod.rs b/lib/src/utilities/mod.rs index 1d55fe67..f85e36c8 100644 --- a/lib/src/utilities/mod.rs +++ b/lib/src/utilities/mod.rs @@ -1,16 +1,45 @@ +//! Utility functions and helpers for parsing Dwarf Fortress raw files. +//! +//! This module provides various utilities including: +//! - File operations for reading and writing raw files +//! - Directory lookup functions for Steam and user data directories +//! - Tag lookup tables for creatures, plants, entities, and more +//! - Flag constants for various game object types +//! +//! # Examples +//! +//! Finding the game installation path: +//! +//! ``` +//! use dfraw_parser::utilities::find_game_path; +//! +//! let app_id = 975370; +//! if let Some(path) = find_game_path(app_id) { +//! println!("Game found at: {:?}", path); +//! } +//! ``` + mod biome_tag_lookup; -mod caste_tag_lookup; +mod caste_tag_flags; mod condition_tag_lookup; mod creature_effect_property_tag_lookup; mod creature_effect_tag_lookup; -mod creature_tag_lookup; +mod creature_tag_flags; mod creature_variation_tag_lookup; mod entity_tag_lookup; mod file_operations; mod object_type_lookup; +mod plant_growth_tag_lookup; +mod plant_growth_type_tag_flags; +mod plant_growth_type_tag_lookup; +mod plant_part_tag_lookup; +mod plant_tag_flags; +mod plant_tag_lookup; +mod searchable; mod steam_directory_lookup; mod user_directory_lookup; pub use file_operations::*; +pub use searchable::*; pub use steam_directory_lookup::find_game_path; pub use user_directory_lookup::find_user_data_path; diff --git a/lib/src/utilities/plant_growth_tag_lookup.rs b/lib/src/utilities/plant_growth_tag_lookup.rs new file mode 100644 index 00000000..04001365 --- /dev/null +++ b/lib/src/utilities/plant_growth_tag_lookup.rs @@ -0,0 +1,27 @@ +use crate::raw_definitions::PLANT_GROWTH_TOKENS; +use crate::tags::PlantGrowthTag; +use std::collections::HashMap; +use std::mem::{discriminant, Discriminant}; +use std::sync::OnceLock; + +impl PlantGrowthTag { + /// Retrieves the original string token key for this tag (e.g., "MOUNTAIN"). + /// Uses a cached reverse-lookup map for O(1) performance. + pub fn get_key(&self) -> Option<&'static str> { + // Lazily-initialized static reverse map: Discriminant -> &'static str + static REVERSE_MAP: OnceLock, &'static str>> = + OnceLock::new(); + + let map = REVERSE_MAP.get_or_init(|| { + let mut m = HashMap::new(); + // Populate the reverse map from the existing PHF token map + for (key, tag_template) in &PLANT_GROWTH_TOKENS { + m.insert(discriminant(tag_template), *key); + } + m + }); + + // Lookup the token string by this enum variant's discriminant + map.get(&discriminant(self)).copied() + } +} diff --git a/lib/src/utilities/plant_growth_type_tag_flags.rs b/lib/src/utilities/plant_growth_type_tag_flags.rs new file mode 100644 index 00000000..81f71f77 --- /dev/null +++ b/lib/src/utilities/plant_growth_type_tag_flags.rs @@ -0,0 +1,17 @@ +use crate::tags::PlantGrowthTypeTag; + +impl PlantGrowthTypeTag { + pub const FLAG_TOKENS: [&PlantGrowthTypeTag; 11] = [ + &PlantGrowthTypeTag::Cone, + &PlantGrowthTypeTag::Eggs, + &PlantGrowthTypeTag::Feathers, + &PlantGrowthTypeTag::Flowers, + &PlantGrowthTypeTag::Fruit, + &PlantGrowthTypeTag::Leaves, + &PlantGrowthTypeTag::Nut, + &PlantGrowthTypeTag::Pod, + &PlantGrowthTypeTag::SeedCatkins, + &PlantGrowthTypeTag::PollenCatkins, + &PlantGrowthTypeTag::PollenCone, + ]; +} diff --git a/lib/src/utilities/plant_growth_type_tag_lookup.rs b/lib/src/utilities/plant_growth_type_tag_lookup.rs new file mode 100644 index 00000000..d6ad9ae5 --- /dev/null +++ b/lib/src/utilities/plant_growth_type_tag_lookup.rs @@ -0,0 +1,27 @@ +use crate::raw_definitions::PLANT_GROWTH_TYPE_TOKENS; +use crate::tags::PlantGrowthTypeTag; +use std::collections::HashMap; +use std::mem::{discriminant, Discriminant}; +use std::sync::OnceLock; + +impl PlantGrowthTypeTag { + /// Retrieves the original string token key for this tag (e.g., "MOUNTAIN"). + /// Uses a cached reverse-lookup map for O(1) performance. + pub fn get_key(&self) -> Option<&'static str> { + // Lazily-initialized static reverse map: Discriminant -> &'static str + static REVERSE_MAP: OnceLock, &'static str>> = + OnceLock::new(); + + let map = REVERSE_MAP.get_or_init(|| { + let mut m = HashMap::new(); + // Populate the reverse map from the existing PHF token map + for (key, tag_template) in &PLANT_GROWTH_TYPE_TOKENS { + m.insert(discriminant(tag_template), *key); + } + m + }); + + // Lookup the token string by this enum variant's discriminant + map.get(&discriminant(self)).copied() + } +} diff --git a/lib/src/utilities/plant_part_tag_lookup.rs b/lib/src/utilities/plant_part_tag_lookup.rs new file mode 100644 index 00000000..e6c252c7 --- /dev/null +++ b/lib/src/utilities/plant_part_tag_lookup.rs @@ -0,0 +1,27 @@ +use crate::raw_definitions::PLANT_PART_TOKENS; +use crate::tags::PlantPartTag; +use std::collections::HashMap; +use std::mem::{discriminant, Discriminant}; +use std::sync::OnceLock; + +impl PlantPartTag { + /// Retrieves the original string token key for this tag (e.g., "MOUNTAIN"). + /// Uses a cached reverse-lookup map for O(1) performance. + pub fn get_key(&self) -> Option<&'static str> { + // Lazily-initialized static reverse map: Discriminant -> &'static str + static REVERSE_MAP: OnceLock, &'static str>> = + OnceLock::new(); + + let map = REVERSE_MAP.get_or_init(|| { + let mut m = HashMap::new(); + // Populate the reverse map from the existing PHF token map + for (key, tag_template) in &PLANT_PART_TOKENS { + m.insert(discriminant(tag_template), *key); + } + m + }); + + // Lookup the token string by this enum variant's discriminant + map.get(&discriminant(self)).copied() + } +} diff --git a/lib/src/utilities/plant_tag_flags.rs b/lib/src/utilities/plant_tag_flags.rs new file mode 100644 index 00000000..ff70d248 --- /dev/null +++ b/lib/src/utilities/plant_tag_flags.rs @@ -0,0 +1,11 @@ +use crate::tags::PlantTag; + +impl PlantTag { + pub const FLAG_TOKENS: [&PlantTag; 5] = [ + &PlantTag::Dry, + &PlantTag::Evil, + &PlantTag::Good, + &PlantTag::Savage, + &PlantTag::Wet, + ]; +} diff --git a/lib/src/utilities/plant_tag_lookup.rs b/lib/src/utilities/plant_tag_lookup.rs new file mode 100644 index 00000000..5eeaa410 --- /dev/null +++ b/lib/src/utilities/plant_tag_lookup.rs @@ -0,0 +1,27 @@ +use crate::raw_definitions::PLANT_TOKENS; +use crate::tags::PlantTag; +use std::collections::HashMap; +use std::mem::{discriminant, Discriminant}; +use std::sync::OnceLock; + +impl PlantTag { + /// Retrieves the original string token key for this tag (e.g., "MOUNTAIN"). + /// Uses a cached reverse-lookup map for O(1) performance. + pub fn get_key(&self) -> Option<&'static str> { + // Lazily-initialized static reverse map: Discriminant -> &'static str + static REVERSE_MAP: OnceLock, &'static str>> = + OnceLock::new(); + + let map = REVERSE_MAP.get_or_init(|| { + let mut m = HashMap::new(); + // Populate the reverse map from the existing PHF token map + for (key, tag_template) in &PLANT_TOKENS { + m.insert(discriminant(tag_template), *key); + } + m + }); + + // Lookup the token string by this enum variant's discriminant + map.get(&discriminant(self)).copied() + } +} diff --git a/lib/src/utilities/searchable.rs b/lib/src/utilities/searchable.rs new file mode 100644 index 00000000..ec1c0dbb --- /dev/null +++ b/lib/src/utilities/searchable.rs @@ -0,0 +1,71 @@ +use crate::traits::Searchable; + +/// The `get_search_string` function takes an object that implements the `Searchable` trait and +/// returns a string that can be used to search for the object. +pub fn get_search_string(object: &dyn Searchable) -> String { + object.get_search_vec().join(" ") +} + +/// The function `build_search_string` takes a `raw_object` that implements the `Searchable` trait and +/// returns a string representation of the object for searching purposes. +/// +/// Arguments: +/// +/// * `raw_object`: The `raw_object` parameter is a reference to an object that implements the +/// `Searchable` trait. +/// +/// Returns: +/// +/// The function `build_search_string` returns a `String` value. +pub fn build_search_string(raw_object: &dyn Searchable) -> String { + get_search_string(raw_object) +} + +/// The `clean_search_vec` function takes a vector of strings, cleans and filters the strings, and +/// returns a new vector. This is used to clean up search terms before they are used to search for +/// raws. +/// +/// This function is used by the `Searchable` trait. +/// +/// Arguments: +/// +/// * `vec`: A vector of strings representing search terms. +/// +/// Returns: +/// +/// The function `clean_search_vec` returns a `Vec`. +pub fn clean_search_vec(vec: &[String]) -> Vec { + let mut vec: Vec = vec.join(" ").split_whitespace().map(String::from).collect(); + + // Lowercase everything + vec = vec.iter().map(|x| x.to_lowercase()).collect(); + + // Remove any periods, commas, etc. + vec = vec + .iter() + .map(|x| x.replace('.', "")) + .map(|x| x.replace(',', "")) + .map(|x| x.replace('(', "")) + .map(|x| x.replace(')', "")) + .map(|x| x.replace(';', "")) + // ! This is dangerous, because it can obscure bad tag parsing. + .map(|x| x.replace(':', " ")) + .collect(); + + // Uniq the vec + vec.sort(); + vec.dedup(); + + // Remove some generic words + vec.retain(|x| !x.eq_ignore_ascii_case("creature")); + vec.retain(|x| !x.eq_ignore_ascii_case("all")); + vec.retain(|x| !x.eq_ignore_ascii_case("the")); + vec.retain(|x| !x.eq_ignore_ascii_case("of")); + vec.retain(|x| !x.eq_ignore_ascii_case("in")); + vec.retain(|x| !x.eq_ignore_ascii_case("and")); + vec.retain(|x| !x.eq_ignore_ascii_case("a")); + vec.retain(|x| !x.eq_ignore_ascii_case("an")); + vec.retain(|x| !x.eq_ignore_ascii_case("with")); + + vec +} diff --git a/lib/src/utilities/steam_directory_lookup.rs b/lib/src/utilities/steam_directory_lookup.rs index 96149381..3b592a55 100644 --- a/lib/src/utilities/steam_directory_lookup.rs +++ b/lib/src/utilities/steam_directory_lookup.rs @@ -1,4 +1,4 @@ -#[cfg(not(target_os = "windows"))] +#[cfg(target_os = "linux")] use directories::BaseDirs; use std::path::PathBuf; use std::{fs, path::Path}; diff --git a/proc_macros/Cargo.toml b/proc_macros/Cargo.toml new file mode 100644 index 00000000..75a00849 --- /dev/null +++ b/proc_macros/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "dfraw_parser_proc_macros" +version = "0.1.0" +edition = "2024" + +[lib] +# This is the crucial part that enables the proc_macro crate +proc-macro = true + +[lints] +workspace = true + +[dependencies] +proc-macro2 = "1.0.105" +quote = "1.0.43" +syn ={version= "2.0.114", features=["full"]} diff --git a/proc_macros/src/derive_cleanable.rs b/proc_macros/src/derive_cleanable.rs new file mode 100644 index 00000000..2c7199ba --- /dev/null +++ b/proc_macros/src/derive_cleanable.rs @@ -0,0 +1,147 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{Data, DeriveInput, Fields, LitStr, parse_macro_input}; + +/// Implmentation of the derive macro for `dfraw_parser::traits::Cleanable` +#[allow(clippy::too_many_lines)] +pub fn impl_derive_cleanable(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let name = input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + let fields = match &input.data { + Data::Struct(data) => match &data.fields { + Fields::Named(fields) => &fields.named, + _ => panic!("Cleanable only supports named fields"), + }, + _ => panic!("Cleanable only supports structs"), + }; + + // Find the metadata field and check its type string to avoid errors on non-RawMetadata fields + let metadata_field = fields + .iter() + .find(|f| f.ident.as_ref().is_some_and(|i| i == "metadata")); + let mut special_metadata_handled = false; + + let metadata_cleaning = metadata_field.map_or_else( + || quote! {}, + |field| { + let type_str = quote!(#field.ty).to_string(); + + let mut is_ignored = false; + for attr in &field.attrs { + if attr.path().is_ident("cleanable") || attr.path().is_ident("is_empty") { + let _ = attr.parse_nested_meta(|meta| { + if meta.path.is_ident("ignore") { + is_ignored = true; + } + Ok(()) + }); + } + } + + if !is_ignored && type_str.contains("RawMetadata") { + special_metadata_handled = true; + quote! { + let hide_metadata = self.metadata.as_ref().map_or(false, |m| m.is_hidden()); + if hide_metadata { + self.metadata = None; + } + } + } else { + quote! {} + } + }, + ); + + let fields_cleaning: Vec = fields + .iter() + .map(|f| { + let field_name = &f.ident; + + // Skip metadata if we handled it specially above + if field_name.as_ref().is_some_and(|i| i == "metadata") && special_metadata_handled { + return quote! {}; + } + + let type_str = quote!(#f.ty).to_string(); + let is_option = type_str.contains("Option"); + + let mut is_recursive = false; + let mut only_if_none = false; + let mut is_ignored = false; + let mut custom_is_default: Option = None; + + for attr in &f.attrs { + if attr.path().is_ident("cleanable") || attr.path().is_ident("is_empty") { + let _ = attr.parse_nested_meta(|meta| { + if meta.path.is_ident("recursive") { is_recursive = true; } + else if meta.path.is_ident("only_if_none") { only_if_none = true; } + else if meta.path.is_ident("ignore") { is_ignored = true; } + else if meta.path.is_ident("is_default") { + let s: LitStr = meta.value()?.parse()?; + custom_is_default = Some(s.parse()?); + } + Ok(()) + }); + } + } + + if is_ignored { return quote! {}; } + + // Handle custom check override + if let Some(check_path) = custom_is_default { + return quote! { + if #check_path(&self.#field_name) { + self.#field_name = None; + } + }; + } + + if is_option { + if only_if_none { + if is_recursive { + quote! { + if let Some(val) = self.#field_name.as_mut() { + val.clean(); + } + } + } else { + quote! {} + } + } else if is_recursive { + // Use take() pattern to clean recursively and put back if not empty + quote! { + if let Some(mut val) = self.#field_name.take() { + val.clean(); + if !crate::traits::IsEmpty::is_empty(&val) { + self.#field_name = Some(val); + } + } + } + } else { + quote! { + if self.#field_name.as_ref().map_or(false, crate::traits::IsEmpty::is_empty) { + self.#field_name = None; + } + } + } + } else if is_recursive { + quote! { self.#field_name.clean(); } + } else { + quote! {} + } + }) + .collect(); + + let expanded = quote! { + impl #impl_generics crate::traits::Cleanable for #name #ty_generics #where_clause { + fn clean(&mut self) { + #metadata_cleaning + #(#fields_cleaning)* + } + } + }; + + TokenStream::from(expanded) +} diff --git a/proc_macros/src/derive_is_empty.rs b/proc_macros/src/derive_is_empty.rs new file mode 100644 index 00000000..9a03e651 --- /dev/null +++ b/proc_macros/src/derive_is_empty.rs @@ -0,0 +1,83 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{Data, DeriveInput, Expr, Fields, LitStr, parse_macro_input}; + +/// Implmentation of the derive macro for `dfraw_parser::traits::IsEmpty` +pub fn impl_derive_is_empty(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let name = input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + let fields = match &input.data { + Data::Struct(data) => match &data.fields { + Fields::Named(fields) => &fields.named, + _ => panic!("IsEmpty only supports named fields"), + }, + _ => panic!("IsEmpty only supports structs"), + }; + + let has_identifier = fields + .iter() + .any(|f| f.ident.as_ref().is_some_and(|i| i == "identifier")); + + let is_empty_impl = if has_identifier { + quote! { self.identifier.is_empty() } + } else { + let checks = fields.iter().filter_map(|f| { + let field_name = &f.ident; + let type_str = quote!(#f.ty).to_string(); + let is_option = type_str.contains("Option"); + + let mut is_ignored = false; + let mut only_if_none = false; + let mut custom_is_default: Option = None; + let mut custom_value: Option = None; + + for attr in &f.attrs { + if attr.path().is_ident("is_empty") { + let _ = attr.parse_nested_meta(|meta| { + if meta.path.is_ident("ignore") { + is_ignored = true; + } else if meta.path.is_ident("only_if_none") { + only_if_none = true; + } else if meta.path.is_ident("is_default") { + let s: LitStr = meta.value()?.parse()?; + custom_is_default = Some(s.parse()?); + } else if meta.path.is_ident("value") { + custom_value = Some(meta.value()?.parse()?); + } + Ok(()) + }); + } + } + + if is_ignored || field_name.as_ref().is_some_and(|i| i == "metadata") { + None + } else if let Some(val) = custom_value { + if is_option { + Some(quote! { self.#field_name.as_ref().map_or(true, |v| v == &#val) }) + } else { + Some(quote! { self.#field_name == #val }) + } + } else if let Some(check_path) = custom_is_default { + Some(quote! { #check_path(&self.#field_name) }) + } else if only_if_none { + Some(quote! { self.#field_name.is_none() }) + } else { + Some(quote! { crate::traits::IsEmpty::is_empty(&self.#field_name) }) + } + }); + + quote! { #(#checks)&&* } + }; + + let expanded = quote! { + impl #impl_generics crate::traits::IsEmpty for #name #ty_generics #where_clause { + fn is_empty(&self) -> bool { + #is_empty_impl + } + } + }; + + TokenStream::from(expanded) +} diff --git a/proc_macros/src/lib.rs b/proc_macros/src/lib.rs new file mode 100644 index 00000000..6014f98c --- /dev/null +++ b/proc_macros/src/lib.rs @@ -0,0 +1,56 @@ +//! Provides macros used in the `dfraw_parser` library + +mod derive_cleanable; +mod derive_is_empty; + +use proc_macro::TokenStream; + +/// Derives [`dfraw_parser::traits::Cleanable`] +/// +/// This macro generates the `clean(&mut self)` method, which is used to reduce +/// the memory footprint of a struct by pruning "empty" values (setting `Option` +/// fields to `None`). +/// +/// ### Attributes (`#[cleanable(...)]` or `#[is_empty(...)]`) +/// - `recursive`: Calls `.clean()` on the field's value. Required for nested structs. +/// - `ignore`: Prevents the macro from touching this field during the cleaning process. +/// - `is_default = "path"`: Points to a custom function `fn(&T) -> bool` to determine +/// if a field should be pruned. +/// - `value = `: prunes the field if it matches the provided value (e.g., `500` or `[0,0]`). +/// +/// ### Behavior +/// - `Option`: If the inner value is "empty" (according to `IsEmpty`), the field is set to `None`. +/// - `metadata`: If a field is named `metadata` and is a `RawMetadata` type, it is +/// automatically set to `None` if `is_hidden()` returns true. +#[proc_macro_derive(Cleanable, attributes(cleanable, is_empty))] +pub fn derive_cleanable(input: TokenStream) -> TokenStream { + derive_cleanable::impl_derive_cleanable(input) +} + +/// Derives [`dfraw_parser::traits::IsEmpty`] for a struct. +/// +/// This macro determines if a struct is "empty", which is used by [`Cleanable`] +/// and can be used for optimized serialization (e.g., `skip_serializing_if`). +/// +/// ### Attributes (`#[is_empty(...)]` +/// - `ignore`: Skips the field when calculating if the struct is empty. +/// - `only_if_none`: For `Option`, returns true only if the field is `None`, +/// ignoring whether the inner value is empty. +/// - `is_default = "path"`: Uses a custom function to determine if the field is empty. +/// - `value = `: Returns true if the field matches the provided value. +/// +/// ### Behavior +/// - **Identifier Heuristic**: If the struct has a field named `identifier`, +/// `is_empty` simply checks `self.identifier.is_empty()`. +/// - **Component Heuristic**: Otherwise, it returns true only if **all** +/// non-ignored fields are empty. +#[proc_macro_derive(IsEmpty, attributes(is_empty))] +pub fn derive_is_empty(input: TokenStream) -> TokenStream { + derive_is_empty::impl_derive_is_empty(input) +} + +// mod derive_searchable; +// #[proc_macro_derive(Searchable)] +// pub fn derive_searchable(input: TokenStream) -> TokenStream { +// derive_searchable::impl_derive_searchable(input) +// } diff --git a/sqlite_lib/.gitignore b/sqlite_lib/.gitignore deleted file mode 100644 index 60708a0d..00000000 --- a/sqlite_lib/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# Ignore any database files -*.db -*.db-wal -*.db-shm diff --git a/sqlite_lib/Cargo.toml b/sqlite_lib/Cargo.toml index b79e40d3..8127367a 100644 --- a/sqlite_lib/Cargo.toml +++ b/sqlite_lib/Cargo.toml @@ -1,24 +1,20 @@ [package] -name = "sqlite_lib" -version = "0.1.0" +name = "dfraw_parser_sqlite_lib" +version = "0.2.0" edition = "2024" [dependencies] -const_format = "0.2.35" -tracing = "0.1.43" -turso = "0.3.2" -strum = "0.27" - -[dependencies.dfraw_parser] -path = "../lib" -version = "0" +tracing = { workspace = true } +rusqlite = { workspace = true, features = ["bundled", "blob"] } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +dfraw_parser = { workspace = true } +specta = {workspace = true} +chrono = {workspace = true, features = ["serde"]} [dev-dependencies] -tracing-subscriber = "0.3.0" - -[dev-dependencies.pollster] -version = "0.4.0" -features = ["macro"] +tracing-subscriber = { workspace = true } +test_util = { workspace = true } [lints] workspace = true diff --git a/sqlite_lib/src/client.rs b/sqlite_lib/src/client.rs deleted file mode 100644 index a6fae78c..00000000 --- a/sqlite_lib/src/client.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! Provides a client for interacting with the database. This holds the database "object" and provides methods for interacting with it. -use crate::{db_init, util}; - -/// A client for interacting with the database. -#[derive(Clone)] -pub struct DbClient { - db: turso::Database, -} - -impl DbClient { - /// Creates a new client for interacting with the database. - /// - /// # Errors - /// Returns an error if the database cannot be opened. - pub async fn new(path: &str) -> Result { - let db = turso::Builder::new_local(path).build().await?; - - Ok(Self { db }) - } - - /// Initializes the database. - /// - /// This returns early if the database is already initialized to the latest version. - /// - /// This could end up corrupting the database by forcing a schema migration, there are no data - /// integrity checks in place to prevent this. - /// - /// # Errors - /// Returns an error if the database cannot be initialized. - pub async fn init(&self) -> Result<(), std::boxed::Box> { - let conn = self.get_connection()?; - - if util::get_schema_version(&conn).await? == db_init::LATEST_SCHEMA_VERSION { - return Ok(()); - } - - db_init::initialize_database(&self.db).await?; - - Ok(()) - } - - /// Connects to the database. - /// - /// # Errors - /// Returns an error if the connection cannot be established. - pub fn get_connection(&self) -> Result { - self.db.connect() - } -} diff --git a/sqlite_lib/src/db/client.rs b/sqlite_lib/src/db/client.rs new file mode 100644 index 00000000..2351bc1c --- /dev/null +++ b/sqlite_lib/src/db/client.rs @@ -0,0 +1,621 @@ +use std::path::Path; + +use chrono::{TimeDelta, prelude::*}; +use dfraw_parser::ParseResult; +use dfraw_parser::metadata::ParserOptions; +use dfraw_parser::traits::RawObject; +use rusqlite::{Connection, Result}; +use tracing::{debug, info, warn}; + +use crate::db::client_options::ClientOptions; +use crate::db::metadata_markers::{ + FavoriteRaws, LastRawsInsertion, LastRawsParsingOperation, PreferredSearchLimit, + PreviousDwarfFortressGamePath, PreviousDwarfFortressUserPath, PreviousInsertionDuration, + PreviousParseDuration, PreviousParserOptions, RecentSearchTerms, UseSteamAutodetect, +}; +use crate::db::migrate::{apply_migrations, migrate_down}; +use crate::db::migrations::LATEST_SCHEMA_VERSION; +use crate::db::queries::{self}; +use crate::db::search_query::{DEFAULT_SEARCH_LIMIT, SearchQuery}; +use crate::db::util::get_current_schema_version; +use crate::{SearchResults, SpriteGraphicData, TilePageData}; + +/// A client for interacting with a database to contain details about parsed raws. +#[derive(Debug)] +pub struct DbClient { + conn: Connection, + options: ClientOptions, +} + +impl DbClient { + /// Opens a connection to the database and initializes it if it doesn't exist. + /// + /// Path is relative to directory the application is run in. + /// + /// # Errors + /// + /// - Issue creating/opening database + /// - Issue performing migrations + pub fn init_db(path: &str, options: ClientOptions) -> Result { + let conn = Connection::open(path)?; + info!("Database connection opened."); + debug!("Database: {path}"); + let mut current_schema_version: i32 = get_current_schema_version(&conn)?; + + if options.reset_database && current_schema_version != 0 { + warn!("Asked to reset database, will empty database."); + migrate_down(&conn, 0)?; + current_schema_version = get_current_schema_version(&conn)?; + } + + info!( + "Current database schema: v{current_schema_version}, Target database schema: v{LATEST_SCHEMA_VERSION}" + ); + + if current_schema_version < LATEST_SCHEMA_VERSION { + apply_migrations(&conn)?; + } + + Ok(Self { conn, options }) + } + + /// Uses the provided `SearchQuery` to return the JSON of all matching raws defined in the database. + /// + /// # Errors + /// + /// - On database error + /// # Returns + /// + /// The `SearchResults` with the results as the JSON strings as byte arrays. + #[tracing::instrument(level = "debug", skip(self))] + pub fn search_raws(&self, query: &SearchQuery) -> Result>> { + self.add_recent_search_term(query.search_string.clone())?; + queries::search_raws(&self.conn, query) + } + + /// Insert the `ParseResult` returned from `[dfraw_parser::parse]` into the database. + /// + /// # Errors + /// + /// - Database errors + /// - Issues working with downcasting raws to obtain data to insert + pub fn insert_parse_results(&mut self, parse_results: ParseResult) -> Result<()> { + let start = Utc::now(); + queries::insert_parse_results(&mut self.conn, &self.options, parse_results)?; + let end = Utc::now(); + let insertion_duration = end - start; + self.set_last_insertion_utc_datetime(&end)?; + self.set_last_insertion_duration(&insertion_duration)?; + Ok(()) + } + + /// Set the last used parser options + /// + /// If there is no saved options, returns None + /// + /// # Errors + /// + /// - database error + /// - seialization error + pub fn set_last_used_parser_options(&self, options: &ParserOptions) -> Result<()> { + self.set_last_used_df_game_dir( + options + .locations + .get_df_directory() + .unwrap_or_default() + .as_path(), + )?; + self.set_last_used_df_user_dir( + options + .locations + .get_user_data_directory() + .unwrap_or_default() + .as_path(), + )?; + + queries::set_typed_metadata::(&self.conn, options) + } + + /// Get the last used parser options + /// + /// # Errors + /// + /// - database error + /// - deseialization error + pub fn get_last_used_parser_options(&self) -> Result> { + queries::get_typed_metadata::(&self.conn) + } + + /// Set the last parse duration from a `chrono::TimeDelta` + /// + /// # Errors + /// + /// - database error + /// - seialization error + pub fn set_last_parse_duration(&self, duration: &TimeDelta) -> Result<()> { + queries::set_typed_metadata::(&self.conn, duration) + } + + /// Get the last parse duration as a `chrono::TimeDelta` + /// + /// If there is no saved measurement, returns None + /// + /// # Errors + /// + /// - database error + /// - deseialization error + pub fn get_last_parse_duration(&self) -> Result> { + queries::get_typed_metadata::(&self.conn) + } + + /// Set the last raw files insertion duration from a `chrono::TimeDelta` + /// + /// # Errors + /// + /// - database error + /// - seialization error + pub fn set_last_insertion_duration(&self, duration: &TimeDelta) -> Result<()> { + queries::set_typed_metadata::(&self.conn, duration) + } + + /// Get the last raw files insertion duration as a `chrono::TimeDelta` + /// + /// If there is no saved measurement, returns None + /// + /// # Errors + /// + /// - database error + /// - deseialization error + pub fn get_last_insertion_duration(&self) -> Result> { + queries::get_typed_metadata::(&self.conn) + } + + /// Add the last search term to the list of last 10 search terms + /// + /// # Errors + /// + /// - database error + /// - de/serialization error + pub fn add_recent_search_term(&self, search_term: Option) -> Result<()> { + let search_term = search_term.unwrap_or_default(); + if search_term.is_empty() { + return Ok(()); + } + + let mut terms = + queries::get_typed_metadata::(&self.conn)?.unwrap_or_default(); + + let mut replaced = false; + for t in &mut terms { + if search_term.starts_with(t.as_str()) { + t.clone_from(&search_term); + replaced = true; + break; + } + } + + if !replaced && + // Check if exact term already exists to avoid duplicates + !terms.contains(&search_term) + { + terms.insert(0, search_term); + } + + // Maintain limit of 10 + terms.truncate(10); + + queries::set_typed_metadata::(&self.conn, &terms) + } + + /// Get up to the last 10 recent search terms + /// + /// # Errors + /// + /// - database error + /// - deseialization error + pub fn get_recent_search_terms(&self) -> Result> { + (queries::get_typed_metadata::(&self.conn)?) + .map_or_else(|| Ok(Vec::new()), Ok) + } + + /// Set the user's preference for using the Steam-based autodetect of Dwarf Fortress + /// + /// # Errors + /// + /// - database error + /// - serialization error + pub fn set_use_steam_autodetect(&self, use_steam_autodetect: bool) -> Result<()> { + queries::set_typed_metadata::(&self.conn, &use_steam_autodetect) + } + + /// Get the user's preference for using the Steam-based autodetect of Dwarf Fortress + /// + /// # Errors + /// + /// - database error + /// - deserialization error + pub fn get_use_steam_autodetect(&self) -> Result { + (queries::get_typed_metadata::(&self.conn)?) + .map_or_else(|| Ok(false), Ok) + } + + /// Set the last used Dwarf Fortress installation directory + /// + /// # Errors + /// + /// - database error + /// - serialization error + pub fn set_last_used_df_game_dir(&self, dwarf_fortress_directory: &Path) -> Result<()> { + let path_str = dwarf_fortress_directory.to_string_lossy().to_string(); + queries::set_typed_metadata::(&self.conn, &path_str) + } + + /// Get the last used Dwarf Fortress installation directory + /// + /// # Errors + /// + /// - database error + /// - deserialization error + pub fn get_last_used_df_game_dir(&self) -> Result { + (queries::get_typed_metadata::(&self.conn)?) + .map_or_else(|| Ok(String::new()), Ok) + } + + /// Set the last used Dwarf Fortress user directory + /// + /// # Errors + /// + /// - database error + /// - serialization error + pub fn set_last_used_df_user_dir(&self, user_data_dir: &Path) -> Result<()> { + let path_str = user_data_dir.to_string_lossy().to_string(); + queries::set_typed_metadata::(&self.conn, &path_str) + } + + /// Get the last used Dwarf Fortress user directory + /// + /// # Errors + /// + /// - database error + /// - deserialization error + pub fn get_last_used_df_user_dir(&self) -> Result { + (queries::get_typed_metadata::(&self.conn)?) + .map_or_else(|| Ok(String::new()), Ok) + } + + /// Adds a raw ID to the user's favorites list. + /// + /// # Errors + /// + /// - database error + /// - de/serialization error + pub fn add_favorite_raw(&self, raw_id: i64) -> Result<()> { + let mut favorites = + queries::get_typed_metadata::(&self.conn)?.unwrap_or_default(); + + if !favorites.contains(&raw_id) { + favorites.push(raw_id); + queries::set_typed_metadata::(&self.conn, &favorites)?; + } + + Ok(()) + } + + /// Removes a raw ID from the user's favorites list. + /// + /// # Errors + /// + /// - database error + /// - deserialization error + pub fn remove_favorite_raw(&self, raw_id: i64) -> Result<()> { + let favorites_opt = queries::get_typed_metadata::(&self.conn)?; + + if let Some(mut favorites) = favorites_opt { + let initial_len = favorites.len(); + favorites.retain(|&id| id != raw_id); + + if favorites.len() != initial_len { + queries::set_typed_metadata::(&self.conn, &favorites)?; + } + } + + Ok(()) + } + + /// Get the user's favorited raws as ids (to be used with the database for retrival/matching). + /// + /// # Errors + /// + /// - database error + /// - deserialization error + pub fn get_favorite_raws(&self) -> Result> { + (queries::get_typed_metadata::(&self.conn)?) + .map_or_else(|| Ok(Vec::new()), Ok) + } + + /// Set the user's preference for number of results per page + /// + /// # Errors + /// + /// - database error + /// - serialization error + pub fn set_preferred_search_limit(&self, search_limit: u32) -> Result<()> { + queries::set_typed_metadata::(&self.conn, &search_limit) + } + + /// Get the user's preference for number of results per page + /// + /// Returns the default value if not previously updated + /// + /// # Errors + /// + /// - database error + /// - deserialization error + pub fn get_preferred_search_limit(&self) -> Result { + (queries::get_typed_metadata::(&self.conn)?) + .map_or_else(|| Ok(DEFAULT_SEARCH_LIMIT), Ok) + } + + /// Set the date of the last insertion. Expects a `DateTime` in UTC timezone. + /// + /// # Errors + /// + /// - database error + /// - serialization error + pub fn set_last_insertion_utc_datetime(&self, utc_date: &DateTime) -> Result<()> { + let str_date = utc_date.to_rfc3339(); + queries::set_typed_metadata::(&self.conn, &str_date) + } + + /// Set the date of the last insertion. Expects RFC 3339 (ISO 8601) formatted string. + /// + /// # Errors + /// + /// - database error + /// - serialization error + pub fn set_last_insertion_date(&self, insertion_date: &str) -> Result<()> { + queries::set_typed_metadata::(&self.conn, &insertion_date.to_string()) + } + + /// Get the date of the last insertion. + /// + /// # Errors + /// + /// - database error + /// - serialization error + pub fn get_last_insertion_date(&self) -> Result { + (queries::get_typed_metadata::(&self.conn)?) + .map_or_else(|| Ok(String::new()), Ok) + } + + /// Set the date of the last insertion. Expects a `DateTime` in UTC timezone. + /// + /// # Errors + /// + /// - database error + /// - serialization error + pub fn set_last_parse_operation_utc_datetime(&self, utc_date: &DateTime) -> Result<()> { + let str_date = utc_date.to_rfc3339(); + queries::set_typed_metadata::(&self.conn, &str_date) + } + /// Set the date of the last insertion. Expects RFC 3339 (ISO 8601) formatted string. + /// + /// # Errors + /// + /// - database error + /// - serialization error + pub fn set_last_parse_operation_date(&self, insertion_date: &str) -> Result<()> { + queries::set_typed_metadata::( + &self.conn, + &insertion_date.to_string(), + ) + } + + /// Get the date of the last insertion. + /// + /// # Errors + /// + /// - database error + /// - serialization error + pub fn get_last_parse_operation_date(&self) -> Result { + (queries::get_typed_metadata::(&self.conn)?) + .map_or_else(|| Ok(String::new()), Ok) + } + + /// Retrieves a raw object by its database ID. + /// + /// # Errors + /// + /// - database error + pub fn get_raw(&self, id: i64) -> Result> { + queries::get_raw_definition(&self.conn, id) + } + + /// Creates a new raw definition and populates all associated search and graphics tables. + /// + /// # Errors + /// + /// - database error + #[allow(clippy::borrowed_box)] + pub fn create_raw(&self, raw: &Box) -> Result { + queries::create_raw_definition(&self.conn, raw) + } + + /// Updates or creates a raw definition based on its identifier and module identity. + /// + /// # Errors + /// + /// - database error + #[allow(clippy::borrowed_box)] + pub fn upsert_raw(&self, raw: &Box) -> Result { + queries::upsert_raw_definition(&self.conn, raw) + } + + /// Updates the data blob and associated tables for an existing raw definition. + /// + /// # Errors + /// + /// - database error + #[allow(clippy::borrowed_box)] + pub fn update_raw(&self, id: i64, raw: &Box) -> Result<()> { + queries::update_raw_definition(&self.conn, id, raw) + } + + /// Deletes a raw definition. + /// + /// # Errors + /// + /// - database error + pub fn delete_raw(&self, id: i64) -> Result<()> { + queries::delete_raw_definition(&self.conn, id) + } + + /// Retrieves the top result for a module id matching the data in the raw's metadata. + /// + /// # Errors + /// + /// - database error + #[allow(clippy::borrowed_box)] + pub fn get_module_id_from_raw(&self, raw: &Box) -> Result { + queries::get_module_id_from_raw(&self.conn, raw) + } + + /// Creates a new raw defintion with a link to a specific module + /// + /// # Errors + /// + /// - database error + #[allow(clippy::borrowed_box)] + pub fn create_raw_definition_with_module( + &self, + module_id: i64, + raw: &Box, + ) -> Result { + queries::create_raw_definition_with_module(&self.conn, module_id, raw) + } + + /// Returns true if the raw exists in the database. + /// + /// Searches for a match based on the raw identifier and its metadata: location, + /// module name and module version. + /// + /// # Errors + /// + /// - database error + #[allow(clippy::borrowed_box)] + pub fn exists_raw(&self, raw: &Box) -> Result { + queries::exists_raw(&self.conn, raw) + } + + /// Attempts to find the database ID for a specific raw definition. + /// + /// Returns `Ok(Some(id))` if it exists, or `Ok(None)` if it does not. + /// This is useful for checking existence and obtaining the key for updates + /// in a single operation. + /// + /// # Errors + /// + /// - database error + #[allow(clippy::borrowed_box)] + pub fn try_get_raw_id(&self, raw: &Box) -> Result> { + queries::try_get_raw_id(&self.conn, raw) + } + + /// Get a tile page by its id + /// + /// # Errors + /// + /// - database error + /// - no tile page with given `id` + pub fn get_tile_page_by_id(&self, id: i64) -> Result { + queries::get_tile_page_by_id(&self.conn, id) + } + + /// Get a tile page by its identifier + /// + /// This returns only the top result. If no results, returns None + /// + /// # Errors + /// + /// - database error + pub fn get_tile_page_by_identifier(&self, identifier: &str) -> Result> { + queries::get_tile_page_by_identifier(&self.conn, identifier) + } + + /// Get a tile page by its linked raw id + /// + /// # Errors + /// + /// - database error + /// - no tile page with given `raw id` + pub fn get_tile_page_by_raw_id(&self, raw_id: i64) -> Result { + queries::get_tile_page_by_raw_id(&self.conn, raw_id) + } + + /// Get a sprite graphic by its target identifier + /// + /// # Errors + /// + /// - database error + /// - no sprite graphic with given `target` + pub fn get_sprite_graphics_for_target_identifier( + &self, + target_identifier: &str, + ) -> Result> { + queries::get_sprite_graphics_for_target_identifier(&self.conn, target_identifier) + } + + /// Get a sprite graphic by its target identifier and include any caste-specific matches + /// + /// # Errors + /// + /// - database error + /// - no sprite graphic with given `target` + pub fn get_sprite_graphics_for_target_identifier_and_any_castes( + &self, + target_identifier: &str, + ) -> Result> { + queries::get_sprite_graphics_for_target_identifier_and_any_castes( + &self.conn, + target_identifier, + ) + } + + /// Get a sprite graphic by its target identifier and caste + /// + /// # Errors + /// + /// - database error + /// - no sprite graphic with given `target` + pub fn get_sprite_graphics_for_target_identifier_and_caste( + &self, + target_identifier: &str, + target_caste: &str, + ) -> Result> { + queries::get_sprite_graphics_for_target_identifier_and_caste( + &self.conn, + target_identifier, + target_caste, + ) + } + + /// Get a sprite graphic by its linked raw id + /// + /// # Errors + /// + /// - database error + /// - no sprite graphic with given `raw_id` + pub fn get_sprite_graphic_by_raw_id(&self, raw_id: i64) -> Result { + queries::get_sprite_graphic_by_raw_id(&self.conn, raw_id) + } + + /// Get a sprite graphic by its id + /// + /// # Errors + /// + /// - database error + /// - no sprite graphic with given `id` + pub fn get_sprite_graphic_by_id(&self, id: i64) -> Result { + queries::get_sprite_graphic_by_id(&self.conn, id) + } +} diff --git a/sqlite_lib/src/db/client_options.rs b/sqlite_lib/src/db/client_options.rs new file mode 100644 index 00000000..463e384e --- /dev/null +++ b/sqlite_lib/src/db/client_options.rs @@ -0,0 +1,11 @@ +use serde::{Deserialize, Serialize}; + +/// Options for configuring the database client behavior. +#[derive(Debug, Clone, Default, Serialize, Deserialize, specta::Type)] +#[serde(rename_all = "camelCase")] +pub struct ClientOptions { + /// If true, the database will be wiped and re-initialized on startup. + pub reset_database: bool, + /// If true, existing raw definitions will be updated if the identifier exists in the module. + pub overwrite_raws: bool, +} diff --git a/sqlite_lib/src/db/graphics_data.rs b/sqlite_lib/src/db/graphics_data.rs new file mode 100644 index 00000000..64fea6d2 --- /dev/null +++ b/sqlite_lib/src/db/graphics_data.rs @@ -0,0 +1,47 @@ +/// A simplified struct for tile page data +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[serde(rename_all = "camelCase")] +pub struct TilePageData { + /// database id for this page + pub id: i64, + /// linked id in database for the raw data of this tile page + pub raw_id: i64, + /// identifier of the tile page + pub identifier: String, + /// file path to tile page + pub file_path: String, + /// width of tiles + pub tile_width: u32, + /// height of tiles + pub tile_height: u32, + /// width of page + pub page_width: u32, + /// height of page + pub page_height: u32, +} + +/// A simplified struct for sprite graphic data +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default, specta::Type)] +#[serde(rename_all = "camelCase")] +pub struct SpriteGraphicData { + /// database id for this sprite graphic + pub id: i64, + /// linked raw id (of graphics raw) this belongs to + pub raw_id: i64, + /// identifier of tile page sprite is on + pub tile_page_identifier: String, + /// sprite offset x1 + pub offset_x: i64, + /// sprite offset y1 + pub offset_y: i64, + /// for large sprites, offset x2 + pub offset_x_2: Option, + /// for large sprites, offset y2 + pub offset_y_2: Option, + /// primary condition for the sprite + pub primary_condition: String, + /// secondary condition for the sprite + pub secondary_condition: String, + /// the identifier of the thing this sprite displays + pub target_identifier: String, +} diff --git a/sqlite_lib/src/db/metadata.rs b/sqlite_lib/src/db/metadata.rs new file mode 100644 index 00000000..31920d86 --- /dev/null +++ b/sqlite_lib/src/db/metadata.rs @@ -0,0 +1,80 @@ +//! Defines the metadata tracked about the database, inside the database. + +/// Represents known identity keys for the `app_metadata` table. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum AppMetadataKey { + /// The ISO-8601 (RFC 3339) timestamp of the last successful raw database sync + LastRawsInsertion, + /// Whether to use the Steam-based autodetect to find relevant directories + UseSteamAutodetect, + /// Array of the last 10 searches (greedy, so prefers the most complete search; e.g. searched for 'dva','dvar', and 'dvark' will store 'dvark') + RecentSearchTerms, + /// The last-used path for the Dwarf Fortress installation directory + PreviousDwarfFortressGamePath, + /// The last-used path for the Dwarf Fortress user-data directory + PreviousDwarfFortressUserPath, + /// The duration of the last parse + PreviousParseDuration, + /// The options used for the last parse + PreviousParserOptions, + /// The duration of the last raws insertion to the database + PreviousInsertionDuration, + /// Array of raws the user has favorited + FavoriteRaws, + /// The user preference for how many results per page + PreferredSearchLimit, + /// The ISO-8601 (RFC 3339) timestamp of the last successful raw parsing operation + LastRawsParsingOperation, +} + +impl AppMetadataKey { + /// Returns the string key used in the database. + #[must_use] + pub const fn as_str(self) -> &'static str { + match self { + Self::LastRawsInsertion => "last_raws_insertion", + Self::UseSteamAutodetect => "use_steam_autodetect", + Self::RecentSearchTerms => "recent_search_terms", + Self::PreviousDwarfFortressGamePath => "previous_dwarf_fortress_game_path", + Self::PreviousDwarfFortressUserPath => "previous_dwarf_fortress_user_path", + Self::PreviousParseDuration => "previous_parse_duration", + Self::PreviousParserOptions => "previous_parser_options", + Self::FavoriteRaws => "favorite_raws", + Self::PreferredSearchLimit => "preferred_search_limit", + Self::PreviousInsertionDuration => "previous_insertion_duration", + Self::LastRawsParsingOperation => "last_raws_parse", + } + } + + /// Returns a human-readable description for documentation or UI. + #[must_use] + pub const fn description(self) -> &'static str { + match self { + Self::LastRawsInsertion => { + "The ISO-8601 (RFC 3339) timestamp of the last successful raw database sync" + } + Self::UseSteamAutodetect => { + "Whether to use the Steam-based autodetect to find relevant directories" + } + Self::RecentSearchTerms => { + "Array of the last 10 searches (greedy, so prefers the most complete search; e.g. searched for 'dva','dvar', and 'dvark' will store 'dvark')" + } + Self::PreviousDwarfFortressGamePath => { + "The last-used path for the Dwarf Fortress installation directory" + } + Self::PreviousDwarfFortressUserPath => { + "The last-used path for the Dwarf Fortress user-data directory" + } + Self::PreviousParseDuration => "The duration of the last parse", + Self::PreviousParserOptions => "The options used for the last parse", + Self::FavoriteRaws => "Array of raws the user has favorited", + Self::PreferredSearchLimit => "The user preference for how many results per page", + Self::PreviousInsertionDuration => { + "The duration of the last raws insertion to the database" + } + Self::LastRawsParsingOperation => { + "The ISO-8601 (RFC 3339) timestamp of the last successful raw parsing operation" + } + } + } +} diff --git a/sqlite_lib/src/db/metadata_markers.rs b/sqlite_lib/src/db/metadata_markers.rs new file mode 100644 index 00000000..d8974489 --- /dev/null +++ b/sqlite_lib/src/db/metadata_markers.rs @@ -0,0 +1,113 @@ +use chrono::TimeDelta; +use dfraw_parser::metadata::ParserOptions; +use serde::{Deserialize, Serialize}; + +use crate::db::metadata::AppMetadataKey; + +/// A trait that connects a metadata key to its expected Rust type. +pub trait TypedMetadata { + /// The type this metadata value should deserialize into. + type Value: Serialize + for<'de> Deserialize<'de>; + + /// Returns the corresponding enum key. + fn key() -> AppMetadataKey; +} + +/// Marker struct for the last insertion timestamp. +pub struct LastRawsInsertion; +impl TypedMetadata for LastRawsInsertion { + type Value = String; // Store as ISO-8601 string + fn key() -> AppMetadataKey { + AppMetadataKey::LastRawsInsertion + } +} + +/// Marker struct for the last insertion timestamp. +pub struct LastRawsParsingOperation; +impl TypedMetadata for LastRawsParsingOperation { + type Value = String; // Store as ISO-8601 string + fn key() -> AppMetadataKey { + AppMetadataKey::LastRawsParsingOperation + } +} + +/// Marker struct for Steam autodetect preference. +pub struct UseSteamAutodetect; +impl TypedMetadata for UseSteamAutodetect { + type Value = bool; + fn key() -> AppMetadataKey { + AppMetadataKey::UseSteamAutodetect + } +} + +/// Marker struct for the recent search terms array. +pub struct RecentSearchTerms; +impl TypedMetadata for RecentSearchTerms { + type Value = Vec; + fn key() -> AppMetadataKey { + AppMetadataKey::RecentSearchTerms + } +} + +/// Marker struct for the last game installation path. +pub struct PreviousDwarfFortressGamePath; +impl TypedMetadata for PreviousDwarfFortressGamePath { + type Value = String; + fn key() -> AppMetadataKey { + AppMetadataKey::PreviousDwarfFortressGamePath + } +} + +/// Marker struct for the last user data path. +pub struct PreviousDwarfFortressUserPath; +impl TypedMetadata for PreviousDwarfFortressUserPath { + type Value = String; + fn key() -> AppMetadataKey { + AppMetadataKey::PreviousDwarfFortressUserPath + } +} + +/// Marker struct for the last parse duration. +pub struct PreviousParseDuration; +impl TypedMetadata for PreviousParseDuration { + type Value = TimeDelta; + fn key() -> AppMetadataKey { + AppMetadataKey::PreviousParseDuration + } +} + +/// Marker struct for the last insertion duration. +pub struct PreviousInsertionDuration; +impl TypedMetadata for PreviousInsertionDuration { + type Value = TimeDelta; + fn key() -> AppMetadataKey { + AppMetadataKey::PreviousInsertionDuration + } +} + +/// Marker struct for the last parse options. +pub struct PreviousParserOptions; +impl TypedMetadata for PreviousParserOptions { + type Value = ParserOptions; + fn key() -> AppMetadataKey { + AppMetadataKey::PreviousParserOptions + } +} + +/// Marker struct for favorited raw identifiers. +pub struct FavoriteRaws; +impl TypedMetadata for FavoriteRaws { + type Value = Vec; + fn key() -> AppMetadataKey { + AppMetadataKey::FavoriteRaws + } +} + +/// Marker struct for the preferred search results limit. +pub struct PreferredSearchLimit; +impl TypedMetadata for PreferredSearchLimit { + type Value = u32; + fn key() -> AppMetadataKey { + AppMetadataKey::PreferredSearchLimit + } +} diff --git a/sqlite_lib/src/db/migrate.rs b/sqlite_lib/src/db/migrate.rs new file mode 100644 index 00000000..69cc7a1f --- /dev/null +++ b/sqlite_lib/src/db/migrate.rs @@ -0,0 +1,58 @@ +//! Provides functions for initializing and managing the database. + +use super::migrations::{DOWN_MIGRATIONS, LATEST_SCHEMA_VERSION, UP_MIGRATIONS}; +use super::util::get_current_schema_version; +use rusqlite::{Connection, Error}; +use tracing::{error, info, warn}; + +pub(super) fn apply_migrations(conn: &Connection) -> Result<(), Error> { + migrate_up(conn, LATEST_SCHEMA_VERSION) +} + +pub(super) fn migrate_up(conn: &Connection, final_schema: i32) -> Result<(), Error> { + let starting_version: i32 = get_current_schema_version(conn)?; + info!("Migrating database schema v{starting_version:02} >> v{final_schema:02}"); + for (schema_version, up_sql) in UP_MIGRATIONS { + if starting_version < schema_version { + info!("Applying databse schema v{schema_version:02}"); + let current_version = get_current_schema_version(conn)?; + if let Err(e) = conn.execute_batch(up_sql) { + error!( + "Failed upgrade from {current_version:02} >> {schema_version:02}. Attempting to undo any partial changes." + ); + + if let Err(rollback_err) = migrate_down(conn, current_version) { + error!( + "CRITICAL: Failed to rollback schema to {current_version:02} after upgrade failure: {rollback_err}" + ); + } + + return Err(e); + } + conn.pragma_update(None, "user_version", schema_version)?; + } + } + + Ok(()) +} + +pub(super) fn migrate_down(conn: &Connection, final_schema: i32) -> Result<(), Error> { + let starting_version: i32 = get_current_schema_version(conn)?; + if starting_version < final_schema { + return Ok(()); + } + let mut current_version = starting_version; + for (previous_schema_version, down_sql) in DOWN_MIGRATIONS.iter().rev() { + if current_version > *previous_schema_version { + warn!("Performing database downgrade {current_version} >> {previous_schema_version}"); + conn.execute_batch(down_sql)?; + conn.pragma_update(None, "user_version", previous_schema_version)?; + current_version = *previous_schema_version; + } + if current_version <= final_schema { + return Ok(()); + } + } + + Ok(()) +} diff --git a/sqlite_lib/src/db/migrations/mod.rs b/sqlite_lib/src/db/migrations/mod.rs new file mode 100644 index 00000000..3d5d9559 --- /dev/null +++ b/sqlite_lib/src/db/migrations/mod.rs @@ -0,0 +1,23 @@ +//! Contains the raw SQL text for migrating the database to different versions. +mod sql_001_initial; +mod sql_002_names; +mod sql_003_graphics; +mod sql_004_db_metadata; + +/// The highest (and most recent) schema version. +pub const LATEST_SCHEMA_VERSION: i32 = 4; + +/// Migrations forward in the format (`schema_version`, SQL), in order of ascending schema version. +pub(super) const UP_MIGRATIONS: [(i32, &str); 4] = [ + (1, sql_001_initial::UP), + (2, sql_002_names::UP), + (3, sql_003_graphics::UP), + (4, sql_004_db_metadata::UP), +]; +/// Migrations backward in in the format (`previous_schema_version`, SQL), in order of ascending schema version. +pub(super) const DOWN_MIGRATIONS: [(i32, &str); 4] = [ + (0, sql_001_initial::DOWN), + (1, sql_002_names::DOWN), + (2, sql_003_graphics::DOWN), + (3, sql_004_db_metadata::DOWN), +]; diff --git a/sqlite_lib/src/db/migrations/sql_001_initial.rs b/sqlite_lib/src/db/migrations/sql_001_initial.rs new file mode 100644 index 00000000..ed47934e --- /dev/null +++ b/sqlite_lib/src/db/migrations/sql_001_initial.rs @@ -0,0 +1,189 @@ +pub const UP: &str = r" +BEGIN; +CREATE TABLE module_locations ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL +); +INSERT INTO module_locations VALUES + (1, 'Vanilla'), + (2, 'Workshop Mods'), + (3, 'Installed Mods'), + -- For any module loaded from some arbitrary location instead of one of the 3 recognized ones + (4, 'Unknown'); + +CREATE TABLE modules ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + identifier TEXT NOT NULL, + version INTEGER NOT NULL, + display_version TEXT, + earliest_compatible_version INTEGER, + earliest_compatible_display_version TEXT, + author TEXT DEFAULT 'unknown', + description TEXT, + module_directory_path TEXT NOT NULL, + module_location_id INTEGER NOT NULL, + steam_file_id INTEGER, + steam_title TEXT, + steam_description TEXT, + steam_changelog TEXT, + FOREIGN KEY(module_location_id) REFERENCES module_locations(id) +); + +CREATE TABLE load_order ( + position INTEGER PRIMARY KEY, + module_id INTEGER NOT NULL UNIQUE, + -- Ensure we don't accidentally link to a non-existent module + FOREIGN KEY(module_id) REFERENCES modules(id) ON DELETE CASCADE +); + +CREATE TABLE steam_metadata ( + module_id INTEGER NOT NULL, + metadata TEXT +); + +CREATE TABLE steam_tags ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL +); + +CREATE TABLE module_steam_tags ( + module_id INTEGER NOT NULL, + steam_tag_id INTEGER NOT NULL, + PRIMARY KEY(module_id, steam_tag_id), + FOREIGN KEY(module_id) REFERENCES modules(id), + FOREIGN KEY(steam_tag_id) REFERENCES steam_tags(id) +); + +CREATE TABLE steam_key_value_keys ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL +); + +CREATE TABLE module_steam_key_value_pairs ( + module_id INTEGER NOT NULL, + steam_key_value_id INTEGER NOT NULL, + PRIMARY KEY(module_id, steam_key_value_id), + FOREIGN KEY(module_id) REFERENCES modules(id), + FOREIGN KEY(steam_key_value_id) REFERENCES steam_key_value_keys(id) +); + +CREATE TABLE module_restriction_rules ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL +); +INSERT INTO module_restriction_rules (id, name) VALUES + (1, 'REQUIRES'), + (2, 'CONFLICTS'), + (3, 'BEFORE'), + (4, 'AFTER'); + +CREATE TABLE module_dependencies ( + module_id INTEGER NOT NULL, -- The mod that has the rule + target_identifier TEXT NOT NULL, -- The identifier it points to + restriction_type_id INTEGER NOT NULL, -- Link to module_restriction_rules + PRIMARY KEY(module_id, target_identifier, restriction_type_id), + FOREIGN KEY(module_id) REFERENCES modules(id), + FOREIGN KEY(restriction_type_id) REFERENCES module_restriction_rules(id) +); + +CREATE TABLE raw_types ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL +); +INSERT INTO raw_types (id, name) VALUES + (1, 'CREATURE'), + (2, 'INORGANIC'), + (3, 'PLANT'), + (4, 'ITEM'), + (5, 'ITEM_AMMO'), + (6, 'ITEM_ARMOR'), + (7, 'ITEM_FOOD'), + (8, 'ITEM_GLOVES'), + (9, 'ITEM_HELM'), + (10, 'ITEM_INSTRUMENT'), + (11, 'ITEM_PANTS'), + (12, 'ITEM_SHIELD'), + (13, 'ITEM_SHOES'), + (14, 'ITEM_SIEGEAMMO'), + (15, 'ITEM_TOOL'), + (16, 'ITEM_TOY'), + (17, 'ITEM_TRAPCOMP'), + (18, 'ITEM_WEAPON'), + (19, 'BUILDING'), + (20, 'BUILDING_WORKSHOP'), + (21, 'BUILDING_FURNACE'), + (22, 'REACTION'), + (23, 'GRAPHICS'), + (24, 'MATERIAL_TEMPLATE'), + (25, 'BODY_DETAIL_PLAN'), + (26, 'BODY'), + (27, 'ENTITY'), + (28, 'LANGUAGE'), + (29, 'TRANSLATION'), + (30, 'TISSUE_TEMPLATE'), + (31, 'CREATURE_VARIATION'), + (32, 'TEXT_SET'), + (33, 'TILE_PAGE'), + (34, 'DESCRIPTOR_COLOR'), + (35, 'DESCRIPTOR_PATTERN'), + (36, 'DESCRIPTOR_SHAPE'), + (37, 'PALETTE'), + (38, 'MUSIC'), + (39, 'SOUND'), + (40, 'INTERACTION'); + +CREATE TABLE raw_definitions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + raw_type_id INTEGER NOT NULL, + identifier TEXT NOT NULL, + module_id INTEGER NOT NULL, + data_blob BLOB NOT NULL, + FOREIGN KEY(module_id) REFERENCES modules(id), + FOREIGN KEY(raw_type_id) REFERENCES raw_types(id) +); + +CREATE TABLE common_raw_flags ( + raw_id INTEGER NOT NULL, + token_name TEXT NOT NULL, + FOREIGN KEY(raw_id) REFERENCES raw_definitions(id) ON DELETE CASCADE +); + +CREATE TABLE common_raw_flags_with_numeric_value ( + raw_id INTEGER NOT NULL, + token_name TEXT NOT NULL, + value INTEGER NOT NULL, + FOREIGN KEY(raw_id) REFERENCES raw_definitions(id) ON DELETE CASCADE +); + + +CREATE INDEX idx_module_versions ON modules(identifier, version); +CREATE INDEX idx_raw_lookup ON raw_definitions(raw_type_id, identifier, module_id); +CREATE INDEX idx_flags_token_search ON common_raw_flags(token_name, raw_id); +CREATE INDEX idx_numeric_flags_search ON common_raw_flags_with_numeric_value(token_name, value, raw_id); +CREATE INDEX idx_module_location ON modules(module_location_id); + +COMMIT; +"; + +pub const DOWN: &str = r" +PRAGMA foreign_keys = OFF; +BEGIN; +DROP TABLE IF EXISTS common_raw_flags_with_numeric_value; +DROP TABLE IF EXISTS common_raw_flags; +DROP TABLE IF EXISTS raw_definitions; +DROP TABLE IF EXISTS raw_types; +DROP TABLE IF EXISTS module_dependencies; +DROP TABLE IF EXISTS module_restriction_rules; +DROP TABLE IF EXISTS module_steam_key_value_pairs; +DROP TABLE IF EXISTS steam_key_value_keys; +DROP TABLE IF EXISTS module_steam_tags; +DROP TABLE IF EXISTS steam_tags; +DROP TABLE IF EXISTS steam_metadata; +DROP TABLE IF EXISTS load_order; +DROP TABLE IF EXISTS modules; +DROP TABLE IF EXISTS module_locations; +COMMIT; +PRAGMA foreign_keys = ON; +VACUUM; +"; diff --git a/sqlite_lib/src/db/migrations/sql_002_names.rs b/sqlite_lib/src/db/migrations/sql_002_names.rs new file mode 100644 index 00000000..15cd0f8a --- /dev/null +++ b/sqlite_lib/src/db/migrations/sql_002_names.rs @@ -0,0 +1,30 @@ +pub const UP: &str = r" +BEGIN; + +-- Table for all names associated with a raw (Castes, Growths, etc.) +-- Use this for exact lookups or populating UI lists. +CREATE TABLE raw_names ( + raw_id INTEGER NOT NULL, + name TEXT NOT NULL, + FOREIGN KEY(raw_id) REFERENCES raw_definitions(id) ON DELETE CASCADE +); +CREATE INDEX idx_raw_names_lookup ON raw_names(name); + +-- FTS5 Virtual Table for high-performance name and description searching. +-- Populating 'names' with your space-separated string is ideal here. +-- 'trigram' is used to allow substring matching (e.g., 'oad' matches 'toad'). +CREATE VIRTUAL TABLE raw_search_index USING fts5( + raw_id UNINDEXED, + names, + description, + tokenize='trigram' +); + +COMMIT; +"; +pub const DOWN: &str = r" +BEGIN; +DROP TABLE raw_names; +DROP TABLE raw_search_index; +COMMIT; +"; diff --git a/sqlite_lib/src/db/migrations/sql_003_graphics.rs b/sqlite_lib/src/db/migrations/sql_003_graphics.rs new file mode 100644 index 00000000..2ad6261e --- /dev/null +++ b/sqlite_lib/src/db/migrations/sql_003_graphics.rs @@ -0,0 +1,46 @@ +pub const UP: &str = r" +BEGIN; + +-- Stores the physical image file locations and metadata +CREATE TABLE tile_pages ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + raw_id INTEGER NOT NULL, + identifier TEXT NOT NULL, + file_path TEXT NOT NULL, + tile_width INTEGER NOT NULL, + tile_height INTEGER NOT NULL, + page_width INTEGER NOT NULL, + page_height INTEGER NOT NULL, + FOREIGN KEY(raw_id) REFERENCES raw_definitions(id) ON DELETE CASCADE +); + +-- Stores individual sprite entries and links them to tile pages by identifier +CREATE TABLE sprite_graphics ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + raw_id INTEGER NOT NULL, -- The GRAPHICS raw object this belongs to + tile_page_identifier TEXT NOT NULL, + offset_x INTEGER NOT NULL, + offset_y INTEGER NOT NULL, + offset_x_2 INTEGER, + offset_y_2 INTEGER, + primary_condition TEXT NOT NULL, -- e.g. DEFAULT, CHILD, SHAKING, etc. + secondary_condition TEXT, -- (optional) e.g. DEFAULT, CHILD, SHAKING, etc. + target_identifier TEXT NOT NULL, -- The identifier of the creature/item this represents + FOREIGN KEY(raw_id) REFERENCES raw_definitions(id) ON DELETE CASCADE +); + +-- Indexes for fast lookups when building the UI +CREATE INDEX idx_sprite_target ON sprite_graphics(target_identifier); +CREATE INDEX idx_tile_page_ident ON tile_pages(identifier); + +COMMIT; +"; + +pub const DOWN: &str = r" +BEGIN; +DROP INDEX IF EXISTS idx_tile_page_ident; +DROP INDEX IF EXISTS idx_sprite_target; +DROP TABLE IF EXISTS sprite_graphics; +DROP TABLE IF EXISTS tile_pages; +COMMIT; +"; diff --git a/sqlite_lib/src/db/migrations/sql_004_db_metadata.rs b/sqlite_lib/src/db/migrations/sql_004_db_metadata.rs new file mode 100644 index 00000000..08aa5870 --- /dev/null +++ b/sqlite_lib/src/db/migrations/sql_004_db_metadata.rs @@ -0,0 +1,16 @@ +pub const UP: &str = r" +BEGIN; + +CREATE TABLE app_metadata ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL +); + +COMMIT; +"; + +pub const DOWN: &str = r" +BEGIN; +DROP TABLE IF EXISTS app_metadata; +COMMIT; +"; diff --git a/sqlite_lib/src/db/mod.rs b/sqlite_lib/src/db/mod.rs new file mode 100644 index 00000000..1c9bb710 --- /dev/null +++ b/sqlite_lib/src/db/mod.rs @@ -0,0 +1,14 @@ +//! Provides database functionality. + +pub mod client; +pub mod client_options; +pub mod graphics_data; +pub mod metadata; +mod metadata_markers; +mod migrate; +mod migrations; +mod queries; +mod rusqlite_extensions; +pub mod search_query; +pub mod search_results; +mod util; diff --git a/sqlite_lib/src/db/queries/get_set_db_metadata.rs b/sqlite_lib/src/db/queries/get_set_db_metadata.rs new file mode 100644 index 00000000..9eec241c --- /dev/null +++ b/sqlite_lib/src/db/queries/get_set_db_metadata.rs @@ -0,0 +1,55 @@ +use rusqlite::{Connection, Result, params}; + +use crate::db::metadata_markers::TypedMetadata; +use crate::db::rusqlite_extensions::OptionalResultExtension; + +/// Sets a metadata value using a typed key marker. +/// This automatically serializes the value to a JSON string for storage. +/// +/// # Errors +/// +/// - database insertion error +pub fn set_typed_metadata(conn: &Connection, value: &T::Value) -> Result<()> +where + T: TypedMetadata, +{ + let key = T::key().as_str(); + // We use JSON serialization to ensure complex types or strings with spaces + // are stored correctly in the TEXT column. + let val_str = serde_json::to_string(value).map_err(|_| rusqlite::Error::InvalidQuery)?; + + conn.execute( + "INSERT OR REPLACE INTO app_metadata (key, value) VALUES (?1, ?2)", + params![key, val_str], + )?; + Ok(()) +} + +/// Gets a metadata value and automatically deserializes it into the correct Rust type. +/// +/// # Errors +/// +/// - database read error +/// - deserialization of 'value' error +pub fn get_typed_metadata(conn: &Connection) -> Result> +where + T: TypedMetadata, +{ + let key = T::key().as_str(); + let raw_val: Option = conn + .query_row( + "SELECT value FROM app_metadata WHERE key = ?1", + params![key], + |row| row.get(0), + ) + .optional()?; + + raw_val.map_or_else( + || Ok(None), + |s| { + serde_json::from_str(&s) + .map(Some) + .map_err(|_| rusqlite::Error::InvalidQuery) + }, + ) +} diff --git a/sqlite_lib/src/db/queries/insert_modules.rs b/sqlite_lib/src/db/queries/insert_modules.rs new file mode 100644 index 00000000..de344762 --- /dev/null +++ b/sqlite_lib/src/db/queries/insert_modules.rs @@ -0,0 +1,140 @@ +use dfraw_parser::{InfoFile, traits::RawObject}; +use rusqlite::{Connection, Result, Transaction, params}; +use tracing::{debug, info}; + +use crate::{ClientOptions, db::queries::process_raw_insertions}; + +use super::super::rusqlite_extensions::OptionalResultExtension; + +/// Insert a raw module into the database, including its metadata and all raws that belong to it. +pub fn insert_module_data( + conn: &mut Connection, + options: &ClientOptions, + info: &InfoFile, + raws: &[Box], +) -> Result<()> { + let overwrite_raws = options.overwrite_raws; + + let tx = conn.transaction()?; + + // 1. Check for existing module considering identifier, version, and location_id + let existing_module_id: Option = tx.query_row( + "SELECT id FROM modules WHERE identifier = ?1 AND version = ?2 AND module_location_id = ?3 LIMIT 1", + params![ + info.get_identifier(), + i64::from(info.get_numeric_version()), + info.get_location() as i32 + ], + |row| row.get(0), + ).optional()?; + debug!( + "existing_module_id searched '{}' '{}' '{}' => {existing_module_id:?}", + info.get_identifier(), + i64::from(info.get_numeric_version()), + info.get_location() as i32 + ); + + let module_db_id = if let Some(id) = existing_module_id { + if !overwrite_raws { + info!( + "Module {} (v{}, loc {}) already exists. Skipping.", + info.get_identifier(), + info.get_numeric_version(), + info.get_location() + ); + return Ok(()); + } + id + } else { + insert_module_record(&tx, info)? + }; + + // 2. Process Dependencies (only if module is new) + if existing_module_id.is_none() { + insert_module_dependencies(&tx, module_db_id, info)?; + } + + // 3. Process Raws + process_raw_insertions(&tx, module_db_id, info, raws, overwrite_raws)?; + + tx.commit() +} + +/// Insert a module record into the `modules` table +/// +/// # Error +/// +/// - on database error +pub fn insert_module_record(tx: &Transaction, info: &InfoFile) -> Result { + tx.execute( + "INSERT INTO modules ( + name, identifier, version, display_version, + earliest_compatible_version, earliest_compatible_display_version, + author, description, module_directory_path, module_location_id, + steam_file_id + ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)", + params![ + info.get_name(), + info.get_identifier(), + i64::from(info.get_numeric_version()), + info.get_version(), + i64::from(info.get_earliest_compatible_numeric_version()), + info.get_earliest_compatible_displayed_version(), + info.get_author(), + info.get_description(), + info.get_parent_directory(), + i64::from(i32::from(info.get_location())), + info.get_steam_data() + .as_ref() + .map(|s| s.get_file_id().cast_signed()) + ], + )?; + Ok(tx.last_insert_rowid()) +} + +/// Insert a module dependency into the `module_dependencies` table +/// +/// # Error +/// +/// - on database error +pub fn insert_module_dependencies( + tx: &Transaction, + module_db_id: i64, + info: &InfoFile, +) -> Result<()> { + let mut dep_stmt = tx.prepare_cached( + "INSERT INTO module_dependencies (module_id, target_identifier, restriction_type_id) + VALUES (?1, ?2, ?3)", + )?; + info!( + "Inserting module dependencies for {}", + info.get_identifier() + ); + + if let Some(ids) = info.get_requires_ids() { + info!("Attempting to insert required ids: {ids:?}"); + for id in ids { + dep_stmt.execute(params![module_db_id, id, 1])?; + } + } + if let Some(ids) = info.get_conflicts_with_ids() { + info!("Attempting to insert conflicting ids: {ids:?}"); + for id in ids { + dep_stmt.execute(params![module_db_id, id, 2])?; + } + } + if let Some(ids) = info.get_requires_ids_before() { + info!("Attempting to insert required_before ids: {ids:?}"); + for id in ids { + dep_stmt.execute(params![module_db_id, id, 3])?; + } + } + if let Some(ids) = info.get_requires_ids_after() { + info!("Attempting to insert required_after ids: {ids:?}"); + for id in ids { + dep_stmt.execute(params![module_db_id, id, 4])?; + } + } + + Ok(()) +} diff --git a/sqlite_lib/src/db/queries/insert_parse_results.rs b/sqlite_lib/src/db/queries/insert_parse_results.rs new file mode 100644 index 00000000..77977c8c --- /dev/null +++ b/sqlite_lib/src/db/queries/insert_parse_results.rs @@ -0,0 +1,50 @@ +use std::collections::HashMap; + +use dfraw_parser::ParseResult; +use rusqlite::{Connection, Result}; +use tracing::info; + +use crate::ClientOptions; + +use super::insert_module_data; + +/// Helper that will insert the `ParseResult` directly. +/// +/// It handles breaking up the results into each module, then calling `insert_module_data` on each. +/// +/// # Errors +/// +/// - on database errors +pub fn insert_parse_results( + conn: &mut Connection, + options: &ClientOptions, + parse_results: ParseResult, +) -> Result<()> { + // group Raws by Module Identity + // We use a composite key of (name, version, location_id) to match Raws to their InfoFiles. + // This allows us to handle multi-module parsing (Vanilla + Mods) correctly. + let mut module_map = HashMap::new(); + for raw in parse_results.raws { + let meta = raw.get_metadata(); + let key = ( + String::from(meta.get_module_name()), + String::from(meta.get_module_version()), + i32::from(meta.get_location()), + ); + module_map.entry(key).or_insert_with(Vec::new).push(raw); + } + + // We iterate through the parsed info files and grab the raws associated with each. + for info in &parse_results.info_files { + let key = ( + info.get_name(), + info.get_version(), + i32::from(info.get_location()), + ); + info!("Inserting raws for {key:?}"); + if let Some(module_raws) = module_map.get(&key) { + insert_module_data(conn, options, info, module_raws)?; + } + } + Ok(()) +} diff --git a/sqlite_lib/src/db/queries/insert_raw_definitions.rs b/sqlite_lib/src/db/queries/insert_raw_definitions.rs new file mode 100644 index 00000000..d14e4241 --- /dev/null +++ b/sqlite_lib/src/db/queries/insert_raw_definitions.rs @@ -0,0 +1,344 @@ +use dfraw_parser::{ + Graphic, InfoFile, TilePage, metadata::ObjectType, tags::ConditionTag, traits::RawObject, +}; +use rusqlite::{Result, Transaction, params}; +use tracing::error; + +use crate::{db::util::remove_dup_strings, search_helpers::extract_names_and_descriptions}; + +use super::super::rusqlite_extensions::OptionalResultExtension; + +/// Inserts a batch of raws using prepared statements for efficiency. +/// +/// # Errors +/// +/// - Database error (will not commit transaction if error) +#[allow(clippy::too_many_lines)] +pub fn process_raw_insertions( + tx: &Transaction, + module_db_id: i64, + info: &InfoFile, + raws: &[Box], + overwrite_raws: bool, +) -> Result<()> { + let mut error_count = 0; + + // Check if a raw exists already + let mut check_raw_stmt = tx.prepare_cached( + "SELECT id FROM raw_definitions WHERE module_id = ?1 AND identifier = ?2 LIMIT 1", + )?; + + // Insert new raw data + let mut insert_raw_stmt = tx.prepare_cached( + "INSERT INTO raw_definitions (raw_type_id, identifier, module_id, data_blob) + VALUES ((SELECT id FROM raw_types WHERE name = ?1), ?2, ?3, jsonb(?4))", + )?; + + // Search by name + let mut insert_name_stmt = + tx.prepare_cached("INSERT INTO raw_names (raw_id, name) VALUES (?1, ?2)")?; + + // Search descriptions/details about raw + let mut insert_search_stmt = tx.prepare_cached( + "INSERT INTO raw_search_index (raw_id, names, description) VALUES (?1, ?2, ?3)", + )?; + + // Clear search names (used when overwriting raws) - virutal table doesn't support delete cascade + let mut clear_names_stmt = tx.prepare_cached("DELETE FROM raw_names WHERE raw_id = ?1")?; + + // Clear search descriptions (used when overwriting raws) - virutal table doesn't support delete cascade + let mut delete_search_stmt = + tx.prepare_cached("DELETE FROM raw_search_index WHERE raw_id = ?1")?; + + // Update raw_defintion (used when overwriting raws) + let mut update_raw_stmt = + tx.prepare_cached("UPDATE raw_definitions SET data_blob = jsonb(?1) WHERE id = ?2")?; + + // Insert common flags (flags without values) into searchable table + let mut insert_flag_stmt = + tx.prepare_cached("INSERT INTO common_raw_flags (raw_id, token_name) VALUES (?1, ?2)")?; + + // Clear common flags (if we overwrite, then reset stored flags) + let mut clear_flags_stmt = + tx.prepare_cached("DELETE FROM common_raw_flags WHERE raw_id = ?1")?; + + // Special case for graphics for ease of retrieval. + let mut insert_tile_page_stmt = tx.prepare_cached( + "INSERT INTO tile_pages + (raw_id, identifier, file_path, tile_width, tile_height, page_width, page_height) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", + )?; + + // Regular sprite + let mut insert_sprite_graphic_stmt = tx.prepare_cached( + "INSERT INTO sprite_graphics + (raw_id, tile_page_identifier, offset_x, offset_y, primary_condition, secondary_condition, target_identifier) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", + )?; + + // Large sprites + let mut insert_large_sprite_graphic_stmt = tx.prepare_cached( + "INSERT INTO sprite_graphics + (raw_id, tile_page_identifier, offset_x, offset_y, offset_x_2, offset_y_2, primary_condition, secondary_condition, target_identifier) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)", + )?; + + for raw in raws { + let existing_raw_id: Option = check_raw_stmt + .query_row(params![module_db_id, raw.get_identifier()], |row| { + row.get(0) + }) + .optional()?; + + // Handle Serialization with retry/exit logic + let json_payload = match serde_json::to_string(&raw) { + Ok(payload) => payload, + Err(e) => { + error_count += 1; + error!( + "Failed to serialize raw '{}' in module {}: {}", + raw.get_identifier(), + info.get_identifier(), + e + ); + + if error_count >= 5 { + error!( + "Reached maximum serialization error threshold (5) for module {}. Aborting insertion.", + info.get_identifier() + ); + return Err(rusqlite::Error::InvalidQuery); + } + continue; + } + }; + + let mut exists_already = false; + let raw_db_id = if let Some(id) = existing_raw_id { + if overwrite_raws { + update_raw_stmt + .execute(params![json_payload, id]) + .inspect_err(|e| { + tracing::error!( + "Failed updating {} ({}): {e}", + raw.get_identifier(), + raw.get_type().to_string().to_uppercase().replace(' ', "_") + ); + })?; + clear_flags_stmt.execute(params![id]).inspect_err(|e| { + tracing::error!( + "Failed clearing flags for {} ({}): {e}", + raw.get_identifier(), + raw.get_type().to_string().to_uppercase().replace(' ', "_") + ); + })?; + clear_names_stmt.execute(params![id]).inspect_err(|e| { + tracing::error!( + "Failed clearing names for {} ({}): {e}", + raw.get_identifier(), + raw.get_type().to_string().to_uppercase().replace(' ', "_") + ); + })?; + delete_search_stmt.execute(params![id]).inspect_err(|e| { + tracing::error!( + "Failed removing search index for {} ({}): {e}", + raw.get_identifier(), + raw.get_type().to_string().to_uppercase().replace(' ', "_") + ); + })?; + } else { + // Skip if exists and not overwriting + tracing::debug!("Avoiding overwrite of {} (id:{id})", raw.get_identifier()); + } + exists_already = true; + id + } else { + insert_raw_stmt + .execute(params![ + raw.get_type().to_string().to_uppercase().replace(' ', "_"), + raw.get_identifier(), + module_db_id, + json_payload + ]) + .inspect_err(|e| { + tracing::error!( + "Failed inserting {} ({}): {e}", + raw.get_identifier(), + raw.get_type().to_string().to_uppercase().replace(' ', "_") + ); + })?; + tx.last_insert_rowid() + }; + + // Only run flag and search updates if we are overwriting or new definition + if !exists_already || overwrite_raws { + for flag in raw.get_searchable_tokens() { + insert_flag_stmt + .execute(params![raw_db_id, flag]) + .inspect_err(|e| { + tracing::error!( + "Failed inserting flags for {} ({}): {e}", + raw.get_identifier(), + raw.get_type().to_string().to_uppercase().replace(' ', "_") + ); + })?; + } + + let (search_names, search_descriptions) = extract_names_and_descriptions(raw); + + // Populate Names Table (for Exact/Partial ID lookup) + for name in &search_names { + insert_name_stmt.execute(params![raw_db_id, name])?; + } + + // Populate FTS5 Index (for high-speed text search) + insert_search_stmt + .execute(params![ + raw_db_id, + remove_dup_strings(search_names, true).join(" "), + search_descriptions.join(" ") + ]) + .inspect_err(|e| { + tracing::error!( + "Failed inserting search index for {} ({}): {e}", + raw.get_identifier(), + raw.get_type().to_string().to_uppercase().replace(' ', "_") + ); + })?; + } + + // Handle extra graphic data + // Portraits and other sprites are defined in two separate files, so we have to allow insertion of new + // graphics and tile pages if possible. + match raw.get_type() { + ObjectType::TilePage => { + if let Some(tp) = raw.as_any().downcast_ref::() { + let tile_dimensions = tp.get_tile_dimensions(); + let page_dimensions = tp.get_page_dimensions(); + insert_tile_page_stmt + .execute(params![ + raw_db_id, + tp.get_identifier(), + tp.get_file_path().to_str(), + tile_dimensions.x, + tile_dimensions.y, + page_dimensions.x, + page_dimensions.y + ]) + .inspect_err(|e| { + tracing::error!( + "Failed inserting tile page for {} ({}): {e}", + raw.get_identifier(), + raw.get_type().to_string().to_uppercase().replace(' ', "_") + ); + })?; + } + } + ObjectType::Graphics => { + if let Some(g) = raw.as_any().downcast_ref::() { + // Insert any sprites + for s in &g.get_sprites() { + let s_offset = s.get_offset(); + if let Some(s_offset_2) = s.get_offset2() { + insert_large_sprite_graphic_stmt + .execute(params![ + raw_db_id, + s.get_tile_page_id(), + s_offset.x, + s_offset.y, + s_offset_2.x, + s_offset_2.y, + ConditionTag::get_key(&s.get_primary_condition()) + .unwrap_or_default(), + ConditionTag::get_key(&s.get_secondary_condition()), + g.get_identifier() + ]) + .inspect_err(|e| { + tracing::error!( + "Failed inserting sprite graphic for {} ({}): {e}", + raw.get_identifier(), + raw.get_type().to_string().to_uppercase().replace(' ', "_") + ); + })?; + } else { + insert_sprite_graphic_stmt + .execute(params![ + raw_db_id, + s.get_tile_page_id(), + s_offset.x, + s_offset.y, + ConditionTag::get_key(&s.get_primary_condition()) + .unwrap_or_default(), + ConditionTag::get_key(&s.get_secondary_condition()), + g.get_identifier() + ]) + .inspect_err(|e| { + tracing::error!( + "Failed inserting sprite graphic for {} ({}): {e}", + raw.get_identifier(), + raw.get_type().to_string().to_uppercase().replace(' ', "_") + ); + })?; + } + } + // Insert _some_ layers. Specifically we care about portraits for now. + for l in &g.get_layers() { + let primary_condition = l.0.clone(); + // layers are (NAME: [LAYER DEFINTIONS..]) + for layer in &l.1 { + let s_offset = layer.get_offset(); + if let Some(s_offset_2) = layer.get_offset2() { + insert_large_sprite_graphic_stmt + .execute(params![ + raw_db_id, + layer.get_tile_page_id(), + s_offset.x, + s_offset.y, + s_offset_2.x, + s_offset_2.y, + primary_condition, + Some(&layer.get_name()), + g.get_identifier() + ]) + .inspect_err(|e| { + tracing::error!( + "Failed inserting sprite layer graphic for {} ({}): {e}", + raw.get_identifier(), + raw.get_type() + .to_string() + .to_uppercase() + .replace(' ', "_") + ); + })?; + } else { + insert_sprite_graphic_stmt + .execute(params![ + raw_db_id, + layer.get_tile_page_id(), + s_offset.x, + s_offset.y, + primary_condition, + Some(&layer.get_name()), + g.get_identifier() + ]) + .inspect_err(|e| { + tracing::error!( + "Failed inserting sprite graphic for {} ({}): {e}", + raw.get_identifier(), + raw.get_type() + .to_string() + .to_uppercase() + .replace(' ', "_") + ); + })?; + } + } + } + } + } + _ => {} + } + } + + Ok(()) +} diff --git a/sqlite_lib/src/db/queries/mod.rs b/sqlite_lib/src/db/queries/mod.rs new file mode 100644 index 00000000..803886e1 --- /dev/null +++ b/sqlite_lib/src/db/queries/mod.rs @@ -0,0 +1,19 @@ +mod get_set_db_metadata; +mod insert_modules; +mod insert_parse_results; +mod insert_raw_definitions; +mod module_locations; +mod raw_definitions; +mod search_raw_definitions; +mod sprite_graphics; +mod tile_pages; + +pub(super) use get_set_db_metadata::*; +pub(super) use insert_modules::*; +pub(super) use insert_parse_results::*; +pub(super) use insert_raw_definitions::*; +pub(super) use module_locations::*; +pub(super) use raw_definitions::*; +pub(super) use search_raw_definitions::*; +pub(super) use sprite_graphics::*; +pub(super) use tile_pages::*; diff --git a/sqlite_lib/src/db/queries/module_locations.rs b/sqlite_lib/src/db/queries/module_locations.rs new file mode 100644 index 00000000..cec2ffdc --- /dev/null +++ b/sqlite_lib/src/db/queries/module_locations.rs @@ -0,0 +1,17 @@ +use dfraw_parser::metadata::RawModuleLocation; +use rusqlite::{Connection, Result, params}; + +/// Get the id of a `RawModuleLocation` +/// +/// # Errors +/// +/// - location not found in database +pub fn get_id_for_module_location(conn: &Connection, location: RawModuleLocation) -> Result { + let name = location.to_string(); + + conn.query_row( + "SELECT id FROM module_locations WHERE name = ?1", + params![name], + |row| row.get(0), + ) +} diff --git a/sqlite_lib/src/db/queries/raw_definitions.rs b/sqlite_lib/src/db/queries/raw_definitions.rs new file mode 100644 index 00000000..63be79f1 --- /dev/null +++ b/sqlite_lib/src/db/queries/raw_definitions.rs @@ -0,0 +1,262 @@ +use dfraw_parser::{Creature, Graphic, TilePage, metadata::ObjectType, traits::RawObject}; +use rusqlite::{Connection, Result, params}; + +use crate::db::queries::get_id_for_module_location; + +use super::super::rusqlite_extensions::OptionalResultExtension; + +/// Returns true if the raw exists in the database. +/// +/// Searches for a match based on the raw identifier and its metadata: location, +/// module name and module version. +/// +/// # Errors +/// +/// - database error +#[allow(clippy::borrowed_box)] +pub fn exists_raw(conn: &Connection, raw: &Box) -> Result { + match try_get_raw_id(conn, raw) { + Ok(res) => Ok(res.is_some()), + Err(e) => Err(e), + } +} + +/// Attempts to find the database ID for a specific raw definition. +/// +/// Returns `Ok(Some(id))` if it exists, or `Ok(None)` if it does not. +/// This is useful for checking existence and obtaining the key for updates +/// in a single operation. +/// +/// # Errors +/// +/// - database error +#[allow(clippy::borrowed_box)] +pub fn try_get_raw_id(conn: &Connection, raw: &Box) -> Result> { + let meta = raw.get_metadata(); + let module_location_id = get_id_for_module_location(conn, meta.get_location())?; + + conn.query_row( + "SELECT r.id FROM raw_definitions r + JOIN modules m ON r.module_id = m.id + WHERE r.identifier = ?1 + AND m.identifier = ?2 + AND m.version = ?3 + AND m.module_location_id = ?4", + params![ + raw.get_identifier(), + meta.get_module_name(), + meta.get_module_version(), + module_location_id + ], + |row| row.get(0), + ) + .optional() +} + +/// Creates a new raw definition and populates all associated search and graphics tables. +/// +/// # Errors +/// +/// - database error +#[allow(clippy::borrowed_box)] +pub fn create_raw_definition(conn: &Connection, raw: &Box) -> Result { + let module_id = get_module_id_from_raw(conn, raw)?; + create_raw_definition_with_module(conn, module_id, raw) +} + +/// Updates or creates a raw definition based on its identifier and module identity. +/// +/// # Errors +/// +/// - database error +#[allow(clippy::borrowed_box)] +pub fn upsert_raw_definition(conn: &Connection, raw: &Box) -> Result { + let existing_id: Option = try_get_raw_id(conn, raw)?; + + match existing_id { + Some(id) => { + update_raw_definition(conn, id, raw)?; + Ok(id) + } + None => create_raw_definition(conn, raw), + } +} + +/// Retrieves a raw object by its database ID. +/// +/// # Errors +/// +/// - database error +pub fn get_raw_definition(conn: &Connection, id: i64) -> Result> { + let json_str: String = conn.query_row( + "SELECT json(data_blob) FROM raw_definitions WHERE id = ?1", + params![id], + |row| row.get(0), + )?; + serde_json::from_str(&json_str).map_err(|_| rusqlite::Error::InvalidQuery) +} + +/// Updates the data blob and associated tables for an existing raw definition. +/// +/// # Errors +/// +/// - database error +#[allow(clippy::borrowed_box)] +pub fn update_raw_definition(conn: &Connection, id: i64, raw: &Box) -> Result<()> { + let json_payload = serde_json::to_string(&raw).map_err(|_| rusqlite::Error::InvalidQuery)?; + + conn.execute( + "UPDATE raw_definitions SET data_blob = jsonb(?1) WHERE id = ?2", + params![json_payload, id], + )?; + + // Clear side tables (CASCADE handles tile_pages, sprite_graphics, raw_names, flags) + conn.execute( + "DELETE FROM common_raw_flags WHERE raw_id = ?1", + params![id], + )?; + conn.execute("DELETE FROM raw_names WHERE raw_id = ?1", params![id])?; + conn.execute( + "DELETE FROM raw_search_index WHERE raw_id = ?1", + params![id], + )?; + + populate_side_tables(conn, id, raw)?; + Ok(()) +} + +/// Deletes a raw definition. FTS5 index is cleared manually. +/// +/// # Errors +/// +/// - database error +pub fn delete_raw_definition(conn: &Connection, id: i64) -> Result<()> { + // FTS5 doesn't support ON DELETE CASCADE + conn.execute( + "DELETE FROM raw_search_index WHERE raw_id = ?1", + params![id], + )?; + conn.execute("DELETE FROM raw_definitions WHERE id = ?1", params![id])?; + Ok(()) +} + +/// Retrieves the top result for a module id matching the data in the raw's metadata. +/// +/// # Errors +/// +/// - database error +#[allow(clippy::borrowed_box)] +pub fn get_module_id_from_raw(conn: &Connection, raw: &Box) -> Result { + let meta = raw.get_metadata(); + let module_location_id = get_id_for_module_location(conn, meta.get_location())?; + conn.query_row( + "SELECT id FROM modules WHERE identifier = ?1 AND version = ?2 AND module_location_id = ?3 LIMIT 1", + params![ + meta.get_module_name(), + meta.get_module_version(), + module_location_id + ], + |row| row.get(0), + ) +} + +/// Creates a new raw defintion with a link to a specific module +/// +/// # Errors +/// +/// - database error +#[allow(clippy::borrowed_box)] +pub fn create_raw_definition_with_module( + conn: &Connection, + module_id: i64, + raw: &Box, +) -> Result { + let json_payload = serde_json::to_string(&raw).map_err(|_| rusqlite::Error::InvalidQuery)?; + + conn.execute( + "INSERT INTO raw_definitions (raw_type_id, identifier, module_id, data_blob) + VALUES ((SELECT id FROM raw_types WHERE name = ?1), ?2, ?3, jsonb(?4))", + params![ + raw.get_type().to_string().to_uppercase().replace(' ', "_"), + raw.get_identifier(), + module_id, + json_payload + ], + )?; + + let raw_id = conn.last_insert_rowid(); + populate_side_tables(conn, raw_id, raw)?; + Ok(raw_id) +} + +/// Helper to populate the "side tables", i.e. the flag and search tables for most raws, and the graphic tables +/// for graphics. +#[allow(clippy::borrowed_box)] +fn populate_side_tables(conn: &Connection, raw_id: i64, raw: &Box) -> Result<()> { + // Flags + for flag in raw.get_searchable_tokens() { + conn.execute( + "INSERT INTO common_raw_flags (raw_id, token_name) VALUES (?1, ?2)", + params![raw_id, flag], + )?; + } + + let mut search_names = Vec::<&str>::new(); + let mut search_descriptions = Vec::<&str>::new(); + + match raw.get_type() { + ObjectType::Creature => { + if let Some(c) = raw.as_any().downcast_ref::() { + search_names.clone_from(&c.get_all_names()); + search_descriptions.clone_from(&c.get_all_descriptions()); + } + } + ObjectType::TilePage => { + if let Some(tp) = raw.as_any().downcast_ref::() { + let tile_dimensions = tp.get_tile_dimensions(); + let page_dimensions = tp.get_page_dimensions(); + conn.execute( + "INSERT INTO tile_pages (raw_id, identifier, file_path, tile_width, tile_height, page_width, page_height) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", + params![raw_id, tp.get_identifier(), tp.get_file_path().to_str(), tile_dimensions.x, tile_dimensions.y, page_dimensions.x, page_dimensions.y] + )?; + } + } + ObjectType::Graphics => { + if let Some(g) = raw.as_any().downcast_ref::() { + for s in &g.get_sprites() { + let s_offset = s.get_offset(); + if let Some(s_offset_2) = s.get_offset2() { + conn.execute( + "INSERT INTO sprite_graphics (raw_id, tile_page_identifier, offset_x, offset_y, offset_x_2, offset_y_2, primary_condition, secondary_condition, target_identifier) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)", + params![raw_id, s.get_tile_page_id(), s_offset.x, s_offset.y, s_offset_2.x, s_offset_2.y, &s.get_primary_condition().to_string(), g.get_identifier()] + )?; + } else { + conn.execute( + "INSERT INTO sprite_graphics (raw_id, tile_page_identifier, offset_x, offset_y, primary_condition, secondary_condition, target_identifier) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", + params![raw_id, s.get_tile_page_id(), s_offset.x, s_offset.y, &s.get_primary_condition().to_string(), g.get_identifier()] + )?; + } + } + } + } + _ => {} + } + + for n in &search_names { + conn.execute( + "INSERT INTO raw_names (raw_id, name) VALUES (?1, ?2)", + params![raw_id, n], + )?; + } + + conn.execute( + "INSERT INTO raw_search_index (raw_id, names, description) VALUES (?1, ?2, ?3)", + params![ + raw_id, + search_names.join(" "), + search_descriptions.join(" ") + ], + )?; + + Ok(()) +} diff --git a/sqlite_lib/src/db/queries/search_raw_definitions.rs b/sqlite_lib/src/db/queries/search_raw_definitions.rs new file mode 100644 index 00000000..0da66f38 --- /dev/null +++ b/sqlite_lib/src/db/queries/search_raw_definitions.rs @@ -0,0 +1,221 @@ +use dfraw_parser::metadata::{ObjectType, RawModuleLocation}; +use rusqlite::{Connection, Result}; +use std::fmt::Write as _; +use tracing::info; + +use crate::{ResultWithId, SearchQuery, SearchResults}; + +/// Uses the provided `SearchQuery` to return the JSON of all matching raws defined in the database. +/// +/// # Errors +/// +/// - On database error +/// +/// # Returns +/// +/// The `SearchResults` with the results as the JSON strings as byte arrays. +pub fn search_raws(conn: &Connection, query: &SearchQuery) -> Result>> { + let mut sql = String::from("FROM raw_definitions r "); + let mut conditions = Vec::new(); + let mut params_vec: Vec> = Vec::new(); + + let is_full_text_search = query.is_full_text_search(); + + // Full-Text Search Join (Names & Descriptions) + if is_full_text_search && let Some(search_text) = query.search_string.as_ref() { + sql.push_str("JOIN raw_search_index s ON r.id = s.raw_id "); + conditions.push(format!("raw_search_index MATCH ?{}", params_vec.len() + 1)); + params_vec.push(Box::new(search_text.clone())); + } + + // Flags Joins + for (i, flag) in query.required_flags.iter().enumerate() { + let alias = format!("f{i}"); + let _ = write!( + sql, + "JOIN common_raw_flags {alias} ON r.id = {alias}.raw_id " + ); + conditions.push(format!("{alias}.token_name = ?{}", params_vec.len() + 1)); + params_vec.push(Box::new(flag.clone())); + } + + // Module Join (if module info needed) + if !query.locations.is_empty() { + sql.push_str("JOIN modules m ON r.module_id = m.id "); + } + + // A default condition that's always true to simplify adding an unknown amount of other conditions + sql.push_str(" WHERE 1=1 "); + + // Identifier Filter + add_identifier_filter(query, &mut conditions, &mut params_vec); + + add_type_filter(query, &mut conditions, &mut params_vec); + + // Location Filter + add_location_filter(query, &mut conditions, &mut params_vec); + + // Append conditions to the SQL query using AND + if !conditions.is_empty() { + sql.push_str(" AND "); + sql.push_str(&conditions.join(" AND ")); + } + + // We have to find total amount to provide pagination. + let total_count: u32 = { + let count_sql = format!("SELECT COUNT(DISTINCT r.id) {sql}"); + let params_ref: Vec<&dyn rusqlite::ToSql> = + params_vec.iter().map(std::convert::AsRef::as_ref).collect(); + + conn.query_row(&count_sql, ¶ms_ref[..], |row| row.get(0))? + }; + + // Now we can set up our actual results + let mut results_sql = format!("SELECT r.id,json(r.data_blob) {sql}"); + + // Ensure we use BM25 ranking if searching text + if is_full_text_search { + // Sorts by matching on text, best results at the top + // More info: https://sqlite.org/fts5.html#the_bm25_function + results_sql.push_str(" ORDER BY bm25(raw_search_index, 5.0, 1.0)"); + } else { + // Specify a default sorting to keep pagination consistent + results_sql.push_str(" ORDER BY r.identifier ASC, r.id ASC"); + } + + // Apply Limit and Offset + let current_param_idx = params_vec.len(); + let _ = write!( + results_sql, + " LIMIT ?{} OFFSET ?{}", + current_param_idx + 1, + current_param_idx + 2 + ); + + let mut final_params = params_vec; + final_params.push(Box::new(query.limit)); + final_params.push(Box::new(query.offset())); + + // Prepare parementers + let params_ref: Vec<&dyn rusqlite::ToSql> = final_params + .iter() + .map(std::convert::AsRef::as_ref) + .collect(); + let mut stmt = conn.prepare(&results_sql)?; + + // Run query + let rows = stmt.query_map(¶ms_ref[..], |row| { + let id: i64 = row.get(0)?; + let json_string: String = row.get(1)?; // Get as Text + Ok(ResultWithId { + id, + data: json_string.into_bytes(), // Convert to Vec for the return type + }) + })?; + + let mut results = Vec::new(); + let mut rows_count = 0; + for res in rows { + results.push(res?); + rows_count += 1; + } + + info!( + "search_raws: {rows_count}/{total_count} results, page {} of {}", + query.page, + (total_count / query.limit) + 1 + ); + + Ok(SearchResults { + results, + total_count, + }) +} + +/// Internal function to add a LIKE clause to `conditions` to match the `query.identifier_query` string +fn add_identifier_filter( + query: &SearchQuery, + conditions: &mut Vec, + params_vec: &mut Vec>, +) { + if let Some(ident) = query.identifier_query.as_ref() { + conditions.push(format!("r.identifier LIKE ?{}", params_vec.len() + 1)); + params_vec.push(Box::new(format!("%{ident}%"))); + } +} + +/// Internal function to add the `RawModuleLocation` filter into `params_vec` and `conditions` +/// +/// Will return early if `query.locations` is empty (no locations to filter on) +/// +/// 1. Creates parameter placeholders based on which param number we are on, for each location to filter for +/// 2. Appends an IN clause to conditions (which is joined with AND) to let the location filter function on OR +/// 3. Pushes the actual locations to filter on into the `params_vec` for final prepare at end of `search_raws` +fn add_location_filter( + query: &SearchQuery, + conditions: &mut Vec, + params_vec: &mut Vec>, +) { + if query.locations.is_empty() { + return; + } + + // Placeholders + let start_idx = params_vec.len() + 1; + let location_placeholders: Vec = (0..query.locations.len()) + .map(|i| format!("?{}", start_idx + i)) + .collect(); + + // Use an IN clause for OR + conditions.push(format!( + "m.module_location_id IN (SELECT id FROM module_locations WHERE name IN ({}))", + location_placeholders.join(", ") + )); + + // Register the locations for insertion + for l in &query.locations { + // Map enum variants to exact DB strings + let db_name = match l { + RawModuleLocation::Vanilla => "Vanilla", + RawModuleLocation::WorkshopMods => "Workshop Mods", + RawModuleLocation::InstalledMods => "Installed Mods", + _ => "Unknown", + }; + params_vec.push(Box::new(db_name.to_string())); + } +} + +/// Internal function to add the `ObjectType` filter into `params_vec` and `conditions` +/// +/// Will return early if `query.raw_types` is empty (no types to filter on) +/// +/// 1. Creates parameter placeholders based on which param number we are on, for each object type to filter for +/// 2. Appends an IN clause to conditions (which is joined with AND) to let the object type filter function on OR +/// 3. Pushes the actual object types to filter on into the `params_vec` for final prepare at end of `search_raws` +fn add_type_filter( + query: &SearchQuery, + conditions: &mut Vec, + params_vec: &mut Vec>, +) { + if query.raw_types.is_empty() { + return; + } + + // Build a list of placeholders: ?N, ?N+1... + let start_idx = params_vec.len() + 1; + let type_placeholders: Vec = (0..query.raw_types.len()) + .map(|i| format!("?{}", start_idx + i)) + .collect(); + + // Use IN clause to treat multiple types as OR + conditions.push(format!( + "r.raw_type_id IN (SELECT id FROM raw_types WHERE name IN ({}))", + type_placeholders.join(", ") + )); + + // Put the types into the IN clause + for t in &query.raw_types { + // Object types stored in database by "key", i.e. all caps: CREATURE, PLANT, etc + params_vec.push(Box::new(ObjectType::get_key(t))); + } +} diff --git a/sqlite_lib/src/db/queries/sprite_graphics.rs b/sqlite_lib/src/db/queries/sprite_graphics.rs new file mode 100644 index 00000000..63de0ee0 --- /dev/null +++ b/sqlite_lib/src/db/queries/sprite_graphics.rs @@ -0,0 +1,184 @@ +use rusqlite::{Connection, Result, params}; + +use crate::SpriteGraphicData; + +const GET_SPRITE_GRAPHIC_BY_ID: &str = r" +SELECT + id, raw_id, tile_page_identifier, offset_x, offset_y, offset_x_2, offset_y_2, primary_condition, secondary_condition, target_identifier +FROM sprite_graphics +WHERE + id = ?1; +"; +const GET_SPRITE_GRAPHIC_BY_RAW_ID: &str = r" +SELECT + id, raw_id, tile_page_identifier, offset_x, offset_y, offset_x_2, offset_y_2, primary_condition, secondary_condition, target_identifier +FROM sprite_graphics +WHERE + raw_id = ?1; +"; +const GET_SPRITE_GRAPHICS_FOR_TARGET_IDENTIFIER: &str = r" +SELECT + id, raw_id, tile_page_identifier, offset_x, offset_y, offset_x_2, offset_y_2, primary_condition, secondary_condition, target_identifier +FROM sprite_graphics +WHERE + target_identifier = ?1; +"; +const GET_SPRITE_GRAPHICS_FOR_TARGET_IDENTIFIER_AND_CASTE: &str = r" +SELECT + id, raw_id, tile_page_identifier, offset_x, offset_y, offset_x_2, offset_y_2, primary_condition, secondary_condition, target_identifier +FROM sprite_graphics +WHERE + target_identifier = ?1; +"; +const GET_SPRITE_GRAPHICS_FOR_TARGET_IDENTIFIER_AND_ANY_CASTES: &str = r" +SELECT + id, raw_id, tile_page_identifier, offset_x, offset_y, offset_x_2, offset_y_2, primary_condition, secondary_condition, target_identifier +FROM sprite_graphics +WHERE + target_identifier = ?1 + OR target_identifier LIKE ?2; +"; + +/// Get a sprite graphic by its id +/// +/// # Errors +/// +/// - database error +/// - no sprite graphic with given `id` +pub fn get_sprite_graphic_by_id(conn: &Connection, id: i64) -> Result { + conn.query_row(GET_SPRITE_GRAPHIC_BY_ID, params![id], |row| { + Ok(SpriteGraphicData { + id: row.get(0)?, + raw_id: row.get(1)?, + tile_page_identifier: row.get(2)?, + offset_x: row.get(3)?, + offset_y: row.get(4)?, + offset_x_2: row.get(5)?, + offset_y_2: row.get(6)?, + primary_condition: row.get(7)?, + secondary_condition: row.get(8).unwrap_or_default(), + target_identifier: row.get(9)?, + }) + }) +} + +/// Get a sprite graphic by its linked raw id +/// +/// # Errors +/// +/// - database error +/// - no sprite graphic with given `raw_id` +pub fn get_sprite_graphic_by_raw_id(conn: &Connection, raw_id: i64) -> Result { + conn.query_row(GET_SPRITE_GRAPHIC_BY_RAW_ID, params![raw_id], |row| { + Ok(SpriteGraphicData { + id: row.get(0)?, + raw_id: row.get(1)?, + tile_page_identifier: row.get(2)?, + offset_x: row.get(3)?, + offset_y: row.get(4)?, + offset_x_2: row.get(5)?, + offset_y_2: row.get(6)?, + primary_condition: row.get(7)?, + secondary_condition: row.get(8).unwrap_or_default(), + target_identifier: row.get(9)?, + }) + }) +} + +/// Get a sprite graphic by its linked raw identifier +/// +/// # Errors +/// +/// - database error +/// - no sprite graphic with given `raw_id` +pub fn get_sprite_graphics_for_target_identifier( + conn: &Connection, + target_identifier: &str, +) -> Result> { + let mut stmt = conn.prepare(GET_SPRITE_GRAPHICS_FOR_TARGET_IDENTIFIER)?; + let mut rows = stmt.query(params![target_identifier])?; + let mut sprites = Vec::new(); + + while let Some(row) = rows.next()? { + sprites.push(SpriteGraphicData { + id: row.get(0)?, + raw_id: row.get(1)?, + tile_page_identifier: row.get(2)?, + offset_x: row.get(3)?, + offset_y: row.get(4)?, + offset_x_2: row.get(5)?, + offset_y_2: row.get(6)?, + primary_condition: row.get(7)?, + secondary_condition: row.get(8).unwrap_or_default(), + target_identifier: row.get(9)?, + }); + } + + Ok(sprites) +} + +/// Get a sprite graphic by its linked raw identifier and caste +/// +/// # Errors +/// +/// - database error +/// - no sprite graphic with given `raw_id` +pub fn get_sprite_graphics_for_target_identifier_and_caste( + conn: &Connection, + target_identifier: &str, + target_caste: &str, +) -> Result> { + let mut stmt = conn.prepare(GET_SPRITE_GRAPHICS_FOR_TARGET_IDENTIFIER_AND_CASTE)?; + let mut rows = stmt.query(params![format!("{target_identifier}:{target_caste}")])?; + let mut sprites = Vec::new(); + + while let Some(row) = rows.next()? { + sprites.push(SpriteGraphicData { + id: row.get(0)?, + raw_id: row.get(1)?, + tile_page_identifier: row.get(2)?, + offset_x: row.get(3)?, + offset_y: row.get(4)?, + offset_x_2: row.get(5)?, + offset_y_2: row.get(6)?, + primary_condition: row.get(7)?, + secondary_condition: row.get(8).unwrap_or_default(), + target_identifier: row.get(9)?, + }); + } + + Ok(sprites) +} + +/// Get a sprite graphic by its linked raw identifier and include any graphics for its castes. +/// +/// # Errors +/// +/// - database error +/// - no sprite graphic with given `raw_id` +pub fn get_sprite_graphics_for_target_identifier_and_any_castes( + conn: &Connection, + target_identifier: &str, +) -> Result> { + let mut stmt = conn.prepare(GET_SPRITE_GRAPHICS_FOR_TARGET_IDENTIFIER_AND_ANY_CASTES)?; + let caste_pattern = format!("{target_identifier}:%"); + let mut rows = stmt.query(params![target_identifier, caste_pattern])?; + let mut sprites = Vec::new(); + + while let Some(row) = rows.next()? { + sprites.push(SpriteGraphicData { + id: row.get(0)?, + raw_id: row.get(1)?, + tile_page_identifier: row.get(2)?, + offset_x: row.get(3)?, + offset_y: row.get(4)?, + offset_x_2: row.get(5)?, + offset_y_2: row.get(6)?, + primary_condition: row.get(7)?, + secondary_condition: row.get(8).unwrap_or_default(), + target_identifier: row.get(9)?, + }); + } + + Ok(sprites) +} diff --git a/sqlite_lib/src/db/queries/tile_pages.rs b/sqlite_lib/src/db/queries/tile_pages.rs new file mode 100644 index 00000000..90b71e64 --- /dev/null +++ b/sqlite_lib/src/db/queries/tile_pages.rs @@ -0,0 +1,95 @@ +use rusqlite::{Connection, Result, params}; + +use crate::TilePageData; + +use super::super::rusqlite_extensions::OptionalResultExtension; + +const TILE_PAGE_BY_ID_QUERY: &str = r" +SELECT + id, raw_id, identifier, file_path, tile_width, tile_height, page_width, page_height +FROM tile_pages +WHERE id = ?1; +"; + +const TILE_PAGE_BY_IDENTIFIER_QUERY: &str = r" +SELECT + id, raw_id, identifier, file_path, tile_width, tile_height, page_width, page_height +FROM tile_pages +WHERE identifier = ?1; +"; + +const TILE_PAGE_BY_RAW_ID_QUERY: &str = r" +SELECT + id, raw_id, identifier, file_path, tile_width, tile_height, page_width, page_height +FROM tile_pages +WHERE raw_id = ?1; +"; + +/// Get a tile page by its id +/// +/// # Errors +/// +/// - database error +/// - no tile page with given `id` +pub fn get_tile_page_by_id(conn: &Connection, id: i64) -> Result { + conn.query_row(TILE_PAGE_BY_ID_QUERY, params![id], |row| { + Ok(TilePageData { + id: row.get(0)?, + raw_id: row.get(1)?, + identifier: row.get(2)?, + file_path: row.get(3)?, + tile_width: row.get(4)?, + tile_height: row.get(5)?, + page_width: row.get(6)?, + page_height: row.get(7)?, + }) + }) +} + +/// Get a tile page by its linked raw id +/// +/// # Errors +/// +/// - database error +/// - no tile page with given `raw id` +pub fn get_tile_page_by_raw_id(conn: &Connection, raw_id: i64) -> Result { + conn.query_row(TILE_PAGE_BY_RAW_ID_QUERY, params![raw_id], |row| { + Ok(TilePageData { + id: row.get(0)?, + raw_id: row.get(1)?, + identifier: row.get(2)?, + file_path: row.get(3)?, + tile_width: row.get(4)?, + tile_height: row.get(5)?, + page_width: row.get(6)?, + page_height: row.get(7)?, + }) + }) +} + +/// Get a tile page by its identifier +/// +/// This returns only the top result +/// +/// # Errors +/// +/// - database error +/// - no tile page with given `id` +pub fn get_tile_page_by_identifier( + conn: &Connection, + identifier: &str, +) -> Result> { + conn.query_row(TILE_PAGE_BY_IDENTIFIER_QUERY, params![identifier], |row| { + Ok(TilePageData { + id: row.get(0)?, + raw_id: row.get(1)?, + identifier: row.get(2)?, + file_path: row.get(3)?, + tile_width: row.get(4)?, + tile_height: row.get(5)?, + page_width: row.get(6)?, + page_height: row.get(7)?, + }) + }) + .optional() +} diff --git a/sqlite_lib/src/db/rusqlite_extensions.rs b/sqlite_lib/src/db/rusqlite_extensions.rs new file mode 100644 index 00000000..195f7c7b --- /dev/null +++ b/sqlite_lib/src/db/rusqlite_extensions.rs @@ -0,0 +1,16 @@ +use rusqlite::Result; + +/// Simple extension trait for Rusqlite to handle Optional rows easily. +pub trait OptionalResultExtension { + fn optional(self) -> Result>; +} + +impl OptionalResultExtension for Result { + fn optional(self) -> Result> { + match self { + Ok(val) => Ok(Some(val)), + Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None), + Err(e) => Err(e), + } + } +} diff --git a/sqlite_lib/src/db/search_query.rs b/sqlite_lib/src/db/search_query.rs new file mode 100644 index 00000000..2467f0d9 --- /dev/null +++ b/sqlite_lib/src/db/search_query.rs @@ -0,0 +1,91 @@ +use dfraw_parser::metadata::{ObjectType, RawModuleLocation}; +use serde::{Deserialize, Serialize}; + +/// A query for searching raw objects in the database. +#[derive(Debug, Clone, Serialize, Deserialize, specta::Type)] +#[serde(rename_all = "camelCase")] +pub struct SearchQuery { + /// A general text search string for names and descriptions. + pub search_string: Option, + /// Search specifically for an identifier (exact or partial). + pub identifier_query: Option, + /// Limit search to raws found within these locations + pub locations: Vec, + /// Limit search to only be raws of this type + pub raw_types: Vec, + /// Used to return only results with these token flags + /// + /// These should be the keys (from `to_keys`) on `CreatureTag`, `CasteTag`, `PlantTag`, etc. + /// (e.g. `FLIER`, `EGG_LAYER`, `FIREIMMUNE`) + pub required_flags: Vec, + /// Used to return only results with these token-value pairings + /// + /// These should be the keys (from `to_keys`) on `CreatureTag`, `CasteTag`, `PlantTag`, etc. + /// (e.g. `LITTER_SIZE`, `POP_RATIO`, `CLUSTER_NUMBER`) + /// + /// The value provided will be used for (minimum/exact value, maximum value) + pub numeric_filters: Vec<(String, i32, Option)>, + /// Limit the number of raws returned to this amount per page + /// + /// Default: `50` + pub limit: u32, + /// Which page to return + /// + /// Default: `1` + pub page: u32, +} + +impl SearchQuery { + /// Whether the query meets the requirements for a full-text search + #[must_use] + pub const fn is_full_text_search(&self) -> bool { + if let Some(s) = self.search_string.as_ref() + && s.len() > 2 + { + true + } else { + false + } + } + /// Computed offset + #[must_use] + pub const fn offset(&self) -> u32 { + (self.page.saturating_sub(1)) * self.limit + } + /// Cleans the query by setting any empty strings into None instead + #[must_use] + pub fn clean(&self) -> Self { + let mut cleaned = self.clone(); + + if let Some(s) = self.search_string.as_ref() + && s.is_empty() + { + cleaned.search_string = None; + } + + if let Some(s) = self.identifier_query.as_ref() + && s.is_empty() + { + cleaned.identifier_query = None; + } + + cleaned + } +} + +pub const DEFAULT_SEARCH_LIMIT: u32 = 50; + +impl Default for SearchQuery { + fn default() -> Self { + Self { + search_string: None, + identifier_query: None, + locations: Vec::new(), + raw_types: Vec::new(), + required_flags: Vec::new(), + numeric_filters: Vec::new(), + limit: DEFAULT_SEARCH_LIMIT, + page: 1, + } + } +} diff --git a/sqlite_lib/src/db/search_results.rs b/sqlite_lib/src/db/search_results.rs new file mode 100644 index 00000000..97d3853c --- /dev/null +++ b/sqlite_lib/src/db/search_results.rs @@ -0,0 +1,43 @@ +use std::fmt::Debug; + +/// A structured response for search operations, containing the requested page of data +/// and the total count of matches in the database. +#[derive(serde::Serialize, serde::Deserialize, Clone, Default, specta::Type)] +#[serde(rename_all = "camelCase")] +pub struct SearchResults { + /// The page of results found. + pub results: Vec>, + /// The total number of matches in the database (ignoring pagination limits). + pub total_count: u32, +} + +/// A carrier struct for passing the database id along with the object we retrieved. +#[derive(serde::Serialize, serde::Deserialize, Clone, Default, specta::Type, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct ResultWithId { + /// id of the object in the database on its respective table + pub id: i64, + /// the object retrieved + pub data: T, +} + +impl Debug for SearchResults { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let calculated_result_len = format!("Vec::{}", &self.results.len()); + f.debug_struct("SearchResults") + .field("results_count", &calculated_result_len) + .field("type_t", &std::any::type_name::()) + .field("total_count", &self.total_count) + .finish() + } +} + +#[allow(clippy::missing_fields_in_debug)] +impl Debug for ResultWithId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ResultWithId") + .field("id", &self.id) + .field("data_type", &std::any::type_name::()) + .finish() + } +} diff --git a/sqlite_lib/src/db/util.rs b/sqlite_lib/src/db/util.rs new file mode 100644 index 00000000..add5e4e6 --- /dev/null +++ b/sqlite_lib/src/db/util.rs @@ -0,0 +1,40 @@ +use std::collections::HashSet; + +use rusqlite::{Connection, Error}; + +pub(super) fn get_current_schema_version(conn: &Connection) -> Result { + let user_version: i32 = conn.pragma_query_value(None, "user_version", |row| row.get(0))?; + Ok(user_version) +} + +/// Removes duplicate strings or substrings in a `Vec<&str>` +pub(super) fn remove_dup_strings(strv: Vec<&str>, remove_singular_when_plural: bool) -> Vec<&str> { + let mut deduped = HashSet::new(); + for str in strv { + str.split_whitespace().for_each(|s| { + if !s.eq("STP") { + deduped.insert(s); + } + }); + } + + if !remove_singular_when_plural { + return deduped.into_iter().collect(); + } + + deduped + .iter() + .copied() + .filter(|&word| { + // Check if it's a singular word (doesn't end in 's') + if word.ends_with('s') { + // Always keep words that already end in 's' + true + } else { + let plural = format!("{word}s"); + // Only keep it if the plural doesn't exist in our set + !deduped.contains(plural.as_str()) + } + }) + .collect() +} diff --git a/sqlite_lib/src/db_init/caste.rs b/sqlite_lib/src/db_init/caste.rs deleted file mode 100644 index 7b47884e..00000000 --- a/sqlite_lib/src/db_init/caste.rs +++ /dev/null @@ -1,480 +0,0 @@ -//! Caste table DDL and documentation. -//! -//! This module defines the SQL DDL for all caste-related tables used by the -//! sqlite backend. Each `*_TABLE` constant contains the `CREATE TABLE` (and -//! optional `CREATE INDEX`) statements used when initializing the database. -//! -//! The doc comment for each table documents: -//! - the purpose of the table, -//! - example values where useful, -//! - foreign keys (FK) referencing other tables, and -//! - indices (primary key and other indices, including composite indices). -//! -//! Any explanatory comments that were previously embedded in the SQL strings -//! have been moved into these doc comments and removed from the SQL itself. - -/// `castes` -/// -/// Stores castes for a creature (e.g., `MALE`, `FEMALE`, `DEFAULT`). -/// -/// Foreign keys: -/// - `creature_id` -> `creatures(id)` -/// -/// Primary key: -/// - `id` (INTEGER PRIMARY KEY) -pub const CASTES_TABLE: &str = r" -CREATE TABLE castes ( - id INTEGER PRIMARY KEY, - creature_id INTEGER NOT NULL, - identifier TEXT NOT NULL, - FOREIGN KEY (creature_id) REFERENCES creatures(id) -);"; - -/// `caste_tags` -/// -/// Simple tag entries for castes. -/// -/// Foreign keys: -/// - `caste_id` -> `castes(id)` -/// - `tag_id` -> `ref_caste_token_tags(id)` -/// -/// Primary key: -/// - `id` (INTEGER PRIMARY KEY) -/// -/// Indices: -/// - `idx_caste_tags` on `(caste_id, tag_id)` — composite index to speed tag lookups -pub const CASTE_TAGS_TABLE: &str = r" -CREATE TABLE caste_tags ( - id INTEGER PRIMARY KEY, - caste_id INTEGER NOT NULL, - tag_id INTEGER NOT NULL, - tag_position INTEGER NOT NULL, - - FOREIGN KEY (caste_id) REFERENCES castes(id), - FOREIGN KEY (tag_id) REFERENCES ref_caste_token_tags(id) -); -CREATE INDEX IF NOT EXISTS idx_caste_tags ON caste_tags (caste_id, tag_id);"; - -/// `caste_value_tags` -/// -/// Stores tag values that may be represented as a boolean, strings and/or -/// integers. This table supports up to 1 boolean (`value_bit`), 7 strings -/// (`value_string1..7`) and 7 integers (`value_int1..7`) per tag row. -/// -/// Foreign keys: -/// - `caste_id` -> `castes(id)` -/// - `tag_id` -> `ref_caste_token_tags(id)` -/// -/// Primary key: -/// - `id` (INTEGER PRIMARY KEY) -/// -/// Indices: -/// - `idx_caste_value_tags` on `(caste_id, tag_id)` — composite index for value lookups -pub const CASTE_VALUE_TAGS_TABLE: &str = r" -CREATE TABLE caste_value_tags ( - id INTEGER PRIMARY KEY, - caste_id INTEGER NOT NULL, - tag_id INTEGER NOT NULL, - tag_position INTEGER NOT NULL, - value_bit INTEGER, - value_string1 TEXT, - value_string2 TEXT, - value_string3 TEXT, - value_string4 TEXT, - value_string5 TEXT, - value_string6 TEXT, - value_string7 TEXT, - value_int1 INTEGER, - value_int2 INTEGER, - value_int3 INTEGER, - value_int4 INTEGER, - value_int5 INTEGER, - value_int6 INTEGER, - value_int7 INTEGER, - FOREIGN KEY (caste_id) REFERENCES castes(id), - FOREIGN KEY (tag_id) REFERENCES ref_caste_token_tags(id) -); -CREATE INDEX IF NOT EXISTS idx_caste_value_tags ON caste_value_tags (caste_id, tag_id);"; - -/// `caste_attacks` -/// -/// Attack definitions associated with a caste (e.g., `BITE` on `BY_TOKEN:MOUTH`). -/// -/// Foreign keys: -/// - `caste_id` -> `castes(id)` -/// - `tag_id` -> `ref_caste_token_tags(id)` -/// -/// Primary key: -/// - `id` (INTEGER PRIMARY KEY) -/// -/// Indices: -/// - `idx_caste_attacks` on `(caste_id, tag_position)` — composite index for attack ordering -pub const CASTE_ATTACKS_TABLE: &str = r" -CREATE TABLE caste_attacks ( - id INTEGER PRIMARY KEY, - caste_id INTEGER NOT NULL, - tag_id INTEGER NOT NULL, - tag_position INTEGER NOT NULL, - name TEXT NOT NULL, - body_part TEXT, - FOREIGN KEY (caste_id) REFERENCES castes(id), - FOREIGN KEY (tag_id) REFERENCES ref_caste_token_tags(id) -); -CREATE INDEX IF NOT EXISTS idx_caste_attacks ON caste_attacks (caste_id, tag_position);"; - -/// `caste_attack_triggers` -/// -/// Attack-trigger related numeric properties (population, wealth, etc.). -/// -/// Foreign keys: -/// - `caste_id` -> `castes(id)` -/// - `tag_id` -> `ref_caste_token_tags(id)` -/// -/// Primary key: -/// - `id` (INTEGER PRIMARY KEY) -/// -/// Indices: -/// - `idx_caste_attack_triggers` on `(caste_id, tag_position)` -pub const CASTE_ATTACK_TRIGGERS_TABLE: &str = r" -CREATE TABLE caste_attack_triggers ( - id INTEGER PRIMARY KEY, - caste_id INTEGER NOT NULL, - tag_id INTEGER NOT NULL, - tag_position INTEGER NOT NULL, - population INTEGER DEFAULT 0, - exported_wealth INTEGER DEFAULT 0, - created_wealth INTEGER DEFAULT 0, - FOREIGN KEY (caste_id) REFERENCES castes(id), - FOREIGN KEY (tag_id) REFERENCES ref_caste_token_tags(id) -); -CREATE INDEX IF NOT EXISTS idx_caste_attack_triggers ON caste_attack_triggers (caste_id, tag_position);"; - -/// `caste_body_detail_plans` -/// -/// Named body-detail plans associated with a caste. -/// -/// Example: a plan name describing a body-detail layout. -/// -/// Foreign keys: -/// - `caste_id` -> `castes(id)` -/// - `tag_id` -> `ref_caste_token_tags(id)` -/// -/// Primary key: -/// - `id` (INTEGER PRIMARY KEY) -/// -/// Indices: -/// - `idx_caste_body_detail_plans` on `(caste_id, tag_position)` -pub const CASTE_BODY_DETAIL_PLANS_TABLE: &str = r" -CREATE TABLE caste_body_detail_plans ( - id INTEGER PRIMARY KEY, - caste_id INTEGER NOT NULL, - tag_id INTEGER NOT NULL, - tag_position INTEGER NOT NULL, - name TEXT NOT NULL, - FOREIGN KEY (caste_id) REFERENCES castes(id), - FOREIGN KEY (tag_id) REFERENCES ref_caste_token_tags(id) -); -CREATE INDEX IF NOT EXISTS idx_caste_body_detail_plans ON caste_body_detail_plans (caste_id, tag_position);"; - -/// `caste_body_detail_plan_args` -/// -/// Arguments for a `caste_body_detail_plans` entry. Arguments are ordered by -/// `argument_index`. -/// -/// Foreign keys: -/// - `body_detail_plan_id` -> `caste_body_detail_plans(id)` -/// - `caste_id` -> `castes(id)` -/// - `tag_id` -> `ref_caste_token_tags(id)` -/// -/// Primary key: -/// - composite `(body_detail_plan_id, argument_index)` -/// -/// Notes: -/// - This table references its parent plan by `body_detail_plan_id`. -pub const CASTE_BODY_DETAIL_PLAN_ARGS_TABLE: &str = r" -CREATE TABLE caste_body_detail_plan_args ( - caste_id INTEGER NOT NULL, - tag_id INTEGER NOT NULL, - tag_position INTEGER NOT NULL, - body_detail_plan_id INTEGER NOT NULL, - argument_index INTEGER, - argument TEXT, - PRIMARY KEY (body_detail_plan_id, argument_index), - FOREIGN KEY (caste_id) REFERENCES castes(id), - FOREIGN KEY (tag_id) REFERENCES ref_caste_token_tags(id), - FOREIGN KEY (body_detail_plan_id) REFERENCES caste_body_detail_plans(id) -);"; - -/// `caste_color_tags` -/// -/// Color tag entries linking a caste tag to a color. -/// -/// Foreign keys: -/// - `caste_id` -> `castes(id)` -/// - `tag_id` -> `ref_caste_token_tags(id)` -/// - `color_id` -> `colors(id)` -/// -/// Primary key: -/// - composite `(caste_id, tag_id, tag_position)` -/// -/// Notes: -/// - Composite primary key ensures uniqueness per caste/tag/position. -pub const CASTE_COLOR_TAGS_TABLE: &str = r" -CREATE TABLE caste_color_tags ( - color_id INTEGER NOT NULL, - caste_id INTEGER NOT NULL, - tag_id INTEGER NOT NULL, - tag_position INTEGER NOT NULL, - PRIMARY KEY (caste_id, tag_id, tag_position), - FOREIGN KEY (caste_id) REFERENCES castes(id), - FOREIGN KEY (tag_id) REFERENCES ref_caste_token_tags(id), - FOREIGN KEY (color_id) REFERENCES colors(id) -);"; - -/// `caste_item_tags` -/// -/// Item tag entries linking a caste tag to a dynamic item-of-material. -/// -/// Foreign keys: -/// - `caste_id` -> `castes(id)` -/// - `tag_id` -> `ref_caste_token_tags(id)` -/// - `dyn_item_id` -> `dyn_items_of_material(id)` -/// -/// Primary key: -/// - composite `(caste_id, tag_id, tag_position)` -pub const CASTE_ITEM_TAGS_TABLE: &str = r" -CREATE TABLE caste_item_tags ( - dyn_item_id INTEGER NOT NULL, - caste_id INTEGER NOT NULL, - tag_id INTEGER NOT NULL, - tag_position INTEGER NOT NULL, - PRIMARY KEY (caste_id, tag_id, tag_position), - FOREIGN KEY (caste_id) REFERENCES castes(id), - FOREIGN KEY (tag_id) REFERENCES ref_caste_token_tags(id), - FOREIGN KEY (dyn_item_id) REFERENCES dyn_items_of_material(id) -);"; - -/// `caste_material_tags` -/// -/// Material tag entries linking a caste tag to a dynamic material-in-state. -/// -/// Foreign keys: -/// - `caste_id` -> `castes(id)` -/// - `tag_id` -> `ref_caste_token_tags(id)` -/// - `dyn_material_id` -> `dyn_materials_in_state(id)` -/// -/// Primary key: -/// - composite `(caste_id, tag_id, tag_position)` -pub const CASTE_MATERIAL_TAGS_TABLE: &str = r" -CREATE TABLE caste_material_tags ( - dyn_material_id INTEGER NOT NULL, - caste_id INTEGER NOT NULL, - tag_id INTEGER NOT NULL, - tag_position INTEGER NOT NULL, - PRIMARY KEY (caste_id, tag_id, tag_position), - FOREIGN KEY (caste_id) REFERENCES castes(id), - FOREIGN KEY (tag_id) REFERENCES ref_caste_token_tags(id), - FOREIGN KEY (dyn_material_id) REFERENCES dyn_materials_in_state(id) -);"; - -/// `caste_creature_caste_tags` -/// -/// Links a caste tag to another creature/caste definition (dynamic reference). -/// -/// Foreign keys: -/// - `caste_id` -> `castes(id)` -/// - `tag_id` -> `ref_caste_token_tags(id)` -/// - `creature_caste_tag_id` -> `dyn_creature_caste_tags(id)` -/// -/// Primary key: -/// - composite `(caste_id, tag_position)` -pub const CASTE_CREATURE_CASTE_TAGS_TABLE: &str = r" -CREATE TABLE caste_creature_caste_tags ( - creature_caste_tag_id INTEGER NOT NULL, - caste_id INTEGER NOT NULL, - tag_id INTEGER NOT NULL, - tag_position INTEGER NOT NULL, - PRIMARY KEY (caste_id, tag_position), - FOREIGN KEY (caste_id) REFERENCES castes(id), - FOREIGN KEY (tag_id) REFERENCES ref_caste_token_tags(id), - FOREIGN KEY (creature_caste_tag_id) REFERENCES dyn_creature_caste_tags(id) -);"; - -/// `caste_gaits` -/// -/// Defines gait types and movement parameters for a caste (e.g., `WALK`, `FLY`). -/// -/// Foreign keys: -/// - `caste_id` -> `castes(id)` -/// - `tag_id` -> `ref_caste_token_tags(id)` -/// -/// Primary key: -/// - `id` `(INTEGER PRIMARY KEY)` -pub const CASTE_GAITS: &str = r" -CREATE TABLE caste_gaits ( - id INTEGER PRIMARY KEY, - caste_id INTEGER NOT NULL, - tag_id INTEGER NOT NULL, - tag_position INTEGER NOT NULL, - gait_type TEXT NOT NULL, - max_speed INTEGER, - build_up_time INTEGER, - turning_max INTEGER, - start_speed INTEGER, - energy_usage INTEGER, - FOREIGN KEY (caste_id) REFERENCES castes(id), - FOREIGN KEY (tag_id) REFERENCES ref_caste_token_tags(id) -);"; - -/// `caste_tiles` -/// -/// Tile entries associating a caste tag with a tile reference. -/// -/// Foreign keys: -/// - `caste_id` -> `castes(id)` -/// - `tag_id` -> `ref_caste_token_tags(id)` -/// - `tile_id` -> `tiles(id)` -/// -/// Primary key: -/// - composite `(caste_id, tag_id, tag_position)` -pub const CASTE_TILES_TABLE: &str = r" -CREATE TABLE caste_tiles ( - tile_id INTEGER NOT NULL, - caste_id INTEGER NOT NULL, - tag_id INTEGER NOT NULL, - tag_position INTEGER NOT NULL, - PRIMARY KEY (caste_id, tag_id, tag_position), - FOREIGN KEY (caste_id) REFERENCES castes(id), - FOREIGN KEY (tag_id) REFERENCES ref_caste_token_tags(id), - FOREIGN KEY (tile_id) REFERENCES tiles(id) -);"; - -/// `caste_lairs` -/// -/// Lair token references that include a probability value. -/// -/// Foreign keys: -/// - `caste_id` -> `castes(id)` -/// - `tag_id` -> `ref_caste_token_tags(id)` -/// - `lair_id` -> `ref_lair_token_tags(id)` -/// -/// Primary key: -/// - composite `(caste_id, tag_id, tag_position)` -pub const CASTE_LAIRS_TABLE: &str = r" -CREATE TABLE caste_lairs ( - lair_id INTEGER NOT NULL, - caste_id INTEGER NOT NULL, - tag_id INTEGER NOT NULL, - tag_position INTEGER NOT NULL, - probability INTEGER NOT NULL, - PRIMARY KEY (caste_id, tag_id, tag_position), - FOREIGN KEY (caste_id) REFERENCES castes(id), - FOREIGN KEY (tag_id) REFERENCES ref_caste_token_tags(id), - FOREIGN KEY (lair_id) REFERENCES ref_lair_token_tags(id) -);"; - -/// `caste_names` -/// -/// Name entries for a caste referencing dynamic names. -/// -/// Foreign keys: -/// - `name_id` -> `dyn_names(id)` -/// - `caste_id` -> `castes(id)` -/// - `tag_id` -> `ref_caste_token_tags(id)` -/// -/// Primary key: -/// - `id` (INTEGER PRIMARY KEY) -/// -/// Indices: -/// - `idx_caste_names` on `(caste_id, tag_id, tag_position)` -pub const CASTE_NAMES: &str = r" -CREATE TABLE caste_names ( - id INTEGER PRIMARY KEY, - name_id INTEGER NOT NULL, - caste_id INTEGER NOT NULL, - tag_id INTEGER NOT NULL, - tag_position INTEGER NOT NULL, - FOREIGN KEY (caste_id) REFERENCES castes(id), - FOREIGN KEY (tag_id) REFERENCES ref_caste_token_tags(id), - FOREIGN KEY (name_id) REFERENCES dyn_names(id) -); -CREATE INDEX IF NOT EXISTS idx_caste_names ON caste_names (caste_id, tag_id, tag_position);"; - -/// `caste_profession_names` -/// -/// Profession-specific names referencing a caste name entry. -/// -/// Foreign keys: -/// - `caste_id` -> `castes(id)` -/// - `tag_id` -> `ref_caste_token_tags(id)` -/// -/// Primary key: -/// - composite `(caste_id, tag_id, tag_position)` -pub const CASTE_PROFESSION_NAMES: &str = r" -CREATE TABLE caste_profession_names ( - profession_identifier TEXT NOT NULL, - caste_name_id INTEGER NOT NULL, - caste_id INTEGER NOT NULL, - tag_id INTEGER NOT NULL, - tag_position INTEGER NOT NULL, - PRIMARY KEY (caste_id, tag_id, tag_position), - FOREIGN KEY (caste_id) REFERENCES castes(id), - FOREIGN KEY (tag_id) REFERENCES ref_caste_token_tags(id), - FOREIGN KEY (caste_name_id) REFERENCES caste_names(id) -);"; - -/// `caste_secretions` -/// -/// Secretion definitions for a caste, linking to a dynamic material and a -/// dynamic body-part-group. `tissue_layer` is a textual layer identifier and -/// `secretion_trigger_id` references a trigger token table. -/// -/// Foreign keys: -/// - `caste_id` -> `castes(id)` -/// - `tag_id` -> `ref_caste_token_tags(id)` -/// - `dyn_material_id` -> `dyn_materials_in_state(id)` -/// - `dyn_body_part_group_id` -> `dyn_body_part_groups(id)` -/// - `secretion_trigger_id` -> `ref_secretion_triggers(id)` -/// -/// Primary key: -/// - composite `(caste_id, tag_id, tag_position)` -pub const CASTE_SECRETIONS_TABLE: &str = r" -CREATE TABLE caste_secretions ( - caste_id INTEGER NOT NULL, - tag_id INTEGER NOT NULL, - tag_position INTEGER NOT NULL, - dyn_material_id INTEGER NOT NULL, - dyn_body_part_group_id INTEGER NOT NULL, - tissue_layer TEXT NOT NULL, - secretion_trigger_id INTEGER NOT NULL, - PRIMARY KEY (caste_id, tag_id, tag_position), - FOREIGN KEY (caste_id) REFERENCES castes(id), - FOREIGN KEY (tag_id) REFERENCES ref_caste_token_tags(id), - FOREIGN KEY (dyn_material_id) REFERENCES dyn_materials_in_state(id), - FOREIGN KEY (dyn_body_part_group_id) REFERENCES dyn_body_part_groups(id), - FOREIGN KEY (secretion_trigger_id) REFERENCES ref_secretion_triggers(id) -);"; - -/// `caste_specific_foods` -/// -/// Specific food entries for a caste referencing an object type and identifier. -/// -/// Foreign keys: -/// - `caste_id` -> `castes(id)` -/// - `tag_id` -> `ref_caste_token_tags(id)` -/// - `ref_object_type_id` -> `ref_object_types(id)` -/// -/// Primary key: -/// - composite `(caste_id, tag_id, tag_position)` -pub const CASTE_SPECIFIC_FOODS_TABLE: &str = r" -CREATE TABLE caste_specific_foods ( - caste_id INTEGER NOT NULL, - tag_id INTEGER NOT NULL, - tag_position INTEGER NOT NULL, - ref_object_type_id INTEGER NOT NULL, - object_identifier TEXT NOT NULL, - PRIMARY KEY (caste_id, tag_id, tag_position), - FOREIGN KEY (caste_id) REFERENCES castes(id), - FOREIGN KEY (tag_id) REFERENCES ref_caste_token_tags(id), - FOREIGN KEY (ref_object_type_id) REFERENCES ref_object_types(id) -);"; diff --git a/sqlite_lib/src/db_init/color.rs b/sqlite_lib/src/db_init/color.rs deleted file mode 100644 index e2622b21..00000000 --- a/sqlite_lib/src/db_init/color.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! Color table DDL and documentation. - -/// `colors` table DDL. -/// -/// Columns: -/// - `id` (INTEGER PRIMARY KEY) -/// - `foreground` (INTEGER, default 0) — integer color value for the foreground -/// - `background` (INTEGER, default 0) — integer color value for the background -/// - `brightness` (INTEGER, default 0) — brightness or intensity as an integer -pub const COLORS_TABLE: &str = r" - CREATE TABLE colors ( - id INTEGER PRIMARY KEY, - foreground INTEGER DEFAULT 0, - background INTEGER DEFAULT 0, - brightness INTEGER DEFAULT 0 - );"; diff --git a/sqlite_lib/src/db_init/creature.rs b/sqlite_lib/src/db_init/creature.rs deleted file mode 100644 index 2f998aa1..00000000 --- a/sqlite_lib/src/db_init/creature.rs +++ /dev/null @@ -1,55 +0,0 @@ -//! Creature table DDL and documentation. - -/// `creatures` table -/// -/// Purpose: -/// - Store creature-specific scalar attributes parsed from raw definitions. -/// -/// Primary key: -/// - `id` (INTEGER PRIMARY KEY) -/// -/// Columns / mappings: -/// - `id` : shared PK and FK to `raw_objects(id)` -/// - `frequency` : INTEGER DEFAULT 50 — maps to `Creature.frequency` -/// - `population_min` / `population_max` : map to `Creature.population_number[0/1]` -/// - `cluster_min` / `cluster_max` : map to `Creature.cluster_number[0/1]` -/// - `underground_depth_min` / `underground_depth_max` : map to `Creature.underground_depth[0/1]` -/// -/// Foreign keys: -/// - `id` -> `raw_objects(id)` -pub const CREATURES_TABLE: &str = r" - CREATE TABLE creatures ( - id INTEGER PRIMARY KEY, - frequency INTEGER DEFAULT 50, - population_min INTEGER, - population_max INTEGER, - cluster_min INTEGER, - cluster_max INTEGER, - underground_depth_min INTEGER, - underground_depth_max INTEGER, - FOREIGN KEY (id) REFERENCES raw_objects(id) - );"; - -/// `creature_biomes` table -/// -/// Purpose: -/// - Many-to-many relationship between creatures and biome tokens. -/// -/// Primary key: -/// - composite `(creature_id, biome_tag_id)` -/// -/// Columns: -/// - `creature_id` (INTEGER NOT NULL) — FK to `creatures(id)` -/// - `biome_tag_id` (INTEGER NOT NULL) — FK to `ref_biomes(id)` -/// -/// Foreign keys: -/// - `creature_id` -> `creatures(id)` -/// - `biome_tag_id` -> `ref_biomes(id)` -pub const CREATURE_BIOMES_TABLE: &str = r" - CREATE TABLE creature_biomes ( - creature_id INTEGER NOT NULL, - biome_tag_id INTEGER NOT NULL, - PRIMARY KEY (creature_id, biome_tag_id), - FOREIGN KEY (creature_id) REFERENCES creatures(id), - FOREIGN KEY (biome_tag_id) REFERENCES ref_biome_token_tags(id) - );"; diff --git a/sqlite_lib/src/db_init/creature_variation.rs b/sqlite_lib/src/db_init/creature_variation.rs deleted file mode 100644 index 8f6e9e9c..00000000 --- a/sqlite_lib/src/db_init/creature_variation.rs +++ /dev/null @@ -1,30 +0,0 @@ -pub const APPLIED_CREATURE_VARIATIONS_TABLE: &str = r" -CREATE TABLE applied_creature_variations ( - id INTEGER PRIMARY KEY, - - -- The variation identifier (e.g., 'ANIMAL_PERSON', 'GIANT') - identifier TEXT NOT NULL, - - -- Parent Linkage (Nullable FKs) - creature_id INTEGER, - caste_id INTEGER, - - FOREIGN KEY (creature_id) REFERENCES creatures(id), - FOREIGN KEY (caste_id) REFERENCES castes(id), - - -- SQL constraint: Ensure it attaches to exactly one parent type - CONSTRAINT check_one_parent CHECK ( - (creature_id IS NOT NULL AND caste_id IS NULL) OR - (creature_id IS NULL AND caste_id IS NOT NULL) - ) -);"; - -pub const APPLIED_CREATURE_VARIATION_ARGUMENTS_TABLE: &str = r" -CREATE TABLE caste_applied_variation_args ( - variation_application_id INTEGER NOT NULL, - argument_value TEXT, - argument_order INTEGER NOT NULL, -- To reconstruct the list in order - - PRIMARY KEY (variation_application_id, argument_order), - FOREIGN KEY (variation_application_id) REFERENCES applied_creature_variations(id) -);"; diff --git a/sqlite_lib/src/db_init/initialize.rs b/sqlite_lib/src/db_init/initialize.rs deleted file mode 100644 index 82be6fed..00000000 --- a/sqlite_lib/src/db_init/initialize.rs +++ /dev/null @@ -1,154 +0,0 @@ -//! The initialization function - -use turso::Connection; - -/// Initialize the database schema. -/// -/// There are no data integrity checks, so it's possible to end up in an inconsistent state. -/// -/// # Errors -/// Returns an error if any of the SQL statements fail. -pub async fn initialize_database( - db: &turso::Database, -) -> Result<(), std::boxed::Box> { - let conn = db.connect()?; - // Enable foreign key constraints - conn.execute("PRAGMA foreign_keys = ON;", ()).await?; - - // Apply the table structure - // Reference tables - conn.execute(super::reference::REF_BIOMES_TABLE, ()).await?; - conn.execute(super::reference::REF_CASTE_TOKEN_TAGS, ()) - .await?; - conn.execute(super::reference::REF_CONDITION_TOKEN_TAGS, ()) - .await?; - conn.execute( - super::reference::REF_CREATURE_EFFECT_PROPERTY_TOKEN_TAGS, - (), - ) - .await?; - conn.execute(super::reference::REF_CREATURE_EFFECT_TOKEN_TAGS, ()) - .await?; - conn.execute(super::reference::REF_CREATURE_TOKEN_TAGS, ()) - .await?; - conn.execute(super::reference::REF_CREATURE_VARIATION_TOKEN_TAGS, ()) - .await?; - conn.execute(super::reference::REF_ENTITY_TOKEN_TAGS, ()) - .await?; - conn.execute(super::reference::REF_LAIR_TOKEN_TAGS, ()) - .await?; - conn.execute(super::reference::REF_MODULE_LOCATIONS_TABLE, ()) - .await?; - conn.execute(super::reference::REF_OBJECT_TYPE_TABLE, ()) - .await?; - conn.execute(super::reference::REF_SECRETION_TRIGGERS, ()) - .await?; - - // Reference data - super::insert_ref_biome_tags(&conn).await?; - super::insert_ref_caste_tags(&conn).await?; - super::insert_ref_condition_tags(&conn).await?; - super::insert_ref_creature_effect_property_tags(&conn).await?; - super::insert_ref_creature_effect_tags(&conn).await?; - super::insert_ref_creature_tags(&conn).await?; - super::insert_ref_creature_variation_tags(&conn).await?; - super::insert_ref_entity_tags(&conn).await?; - super::insert_ref_lair_tags(&conn).await?; - super::insert_ref_secretion_triggers(&conn).await?; - super::insert_ref_object_types(&conn).await?; - - tracing::info!("reference tables created"); - - // Metadata tables - conn.execute(super::metadata::RAW_MODULES_TABLE, ()).await?; - conn.execute(super::metadata::RAW_FILES_TABLE, ()).await?; - conn.execute(super::metadata::RAW_OBJECTS_TABLE, ()).await?; - - tracing::info!("metadata tables created"); - - // Dynamic flags/tags tables - conn.execute(super::misc::MATERIALS_IN_STATE_TABLE, ()) - .await?; - conn.execute(super::misc::ITEMS_OF_MATERIAL_TABLE, ()) - .await?; - conn.execute(super::misc::CREATURE_CASTE_TABLE, ()).await?; - conn.execute(super::misc::NAMES_TABLE, ()).await?; - conn.execute(super::misc::BODY_PART_GROUPS_TABLE, ()) - .await?; - - tracing::info!("dynamic flags/tags tables created"); - // Parsed object tables - conn.execute(super::tile::TILES_TABLE, ()).await?; - conn.execute(super::color::COLORS_TABLE, ()).await?; - conn.execute( - super::creature_variation::APPLIED_CREATURE_VARIATIONS_TABLE, - (), - ) - .await?; - conn.execute( - super::creature_variation::APPLIED_CREATURE_VARIATION_ARGUMENTS_TABLE, - (), - ) - .await?; - - tracing::info!("other object tables created"); - - conn.execute(super::creature::CREATURES_TABLE, ()).await?; - conn.execute(super::creature::CREATURE_BIOMES_TABLE, ()) - .await?; - - tracing::info!("creature tables created"); - - create_caste_tables(&conn).await?; - - tracing::info!("caste tables created"); - - // Set the schema version to the latest version - let update_user_version = format!("PRAGMA user_version = {}", super::LATEST_SCHEMA_VERSION); - - tracing::info!("user_version updated"); - conn.execute(&update_user_version, ()).await?; - - Ok(()) -} - -/// Run the SQL to create the various tables for storing `[dfraw_parser::Caste]` data -/// -/// # Error -/// -/// Passes database errors along -async fn create_caste_tables( - conn: &Connection, -) -> Result<(), std::boxed::Box> { - conn.execute(super::caste::CASTES_TABLE, ()).await?; - conn.execute(super::caste::CASTE_TAGS_TABLE, ()).await?; - conn.execute(super::caste::CASTE_VALUE_TAGS_TABLE, ()) - .await?; - conn.execute(super::caste::CASTE_ATTACKS_TABLE, ()).await?; - conn.execute(super::caste::CASTE_ATTACK_TRIGGERS_TABLE, ()) - .await?; - conn.execute(super::caste::CASTE_BODY_DETAIL_PLANS_TABLE, ()) - .await?; - conn.execute(super::caste::CASTE_BODY_DETAIL_PLAN_ARGS_TABLE, ()) - .await?; - conn.execute(super::caste::CASTE_MATERIAL_TAGS_TABLE, ()) - .await?; - conn.execute(super::caste::CASTE_ITEM_TAGS_TABLE, ()) - .await?; - conn.execute(super::caste::CASTE_GAITS, ()).await?; - conn.execute(super::caste::CASTE_TILES_TABLE, ()).await?; - conn.execute(super::caste::CASTE_CREATURE_CASTE_TAGS_TABLE, ()) - .await?; - conn.execute(super::caste::CASTE_LAIRS_TABLE, ()).await?; - conn.execute(super::caste::CASTE_NAMES, ()).await?; - conn.execute(super::caste::CASTE_PROFESSION_NAMES, ()) - .await?; - conn.execute(super::caste::CASTE_SECRETIONS_TABLE, ()) - .await?; - conn.execute(super::caste::CASTE_SPECIFIC_FOODS_TABLE, ()) - .await?; - conn.execute(super::caste::CASTE_COLOR_TAGS_TABLE, ()) - .await?; - - Ok(()) -} diff --git a/sqlite_lib/src/db_init/metadata.rs b/sqlite_lib/src/db_init/metadata.rs deleted file mode 100644 index 8a11a12a..00000000 --- a/sqlite_lib/src/db_init/metadata.rs +++ /dev/null @@ -1,83 +0,0 @@ -//! Metadata table DDL and documentation. -//! -//! Defines tables that store metadata about parsed raw files, objects and -//! modules. - -/// `raw_files` -/// -/// Stores information about individual raw input files parsed by the system. -/// -/// Columns: -/// - `id` (INTEGER PRIMARY KEY) -/// - `module_id` (INTEGER NOT NULL) — FK to `raw_modules(id)`; the parent module -/// - `path` (TEXT NOT NULL) — file system path or archive path for the raw file -/// - `identifier` (TEXT NOT NULL) — raw file identifier (e.g., `CREATURE`) -/// -/// Foreign keys: -/// - `module_id` -> `raw_modules(id)` -pub const RAW_FILES_TABLE: &str = r" -CREATE TABLE raw_files ( - id INTEGER PRIMARY KEY, - module_id INTEGER NOT NULL, - path TEXT NOT NULL, - identifier TEXT NOT NULL, - FOREIGN KEY (module_id) REFERENCES raw_modules(id) -);"; - -/// `raw_objects` -/// -/// Represents parsed objects extracted from raw files. Each parsed object has a -/// stable `object_id` and an internal numeric `id` used as the primary key. -/// -/// Columns / semantics: -/// - `id` (INTEGER PRIMARY KEY) — numeric identity for this parsed object -/// - `object_id` (TEXT NOT NULL UNIQUE) — stable object id from the parsed data -/// - `identifier` (TEXT NOT NULL) — token identifier (e.g., `DWARF`) -/// - `name` (TEXT) — optional human readable name -/// - `type_id` (INTEGER NOT NULL) — FK to `ref_object_types(id)` indicating the object type -/// - `file_id` (INTEGER NOT NULL) — FK to `raw_files(id)` indicating source file -/// -/// Foreign keys: -/// - `type_id` -> `ref_object_types(id)` -/// - `file_id` -> `raw_files(id)` -pub const RAW_OBJECTS_TABLE: &str = r" -CREATE TABLE raw_objects ( - id INTEGER PRIMARY KEY, - object_id TEXT NOT NULL UNIQUE, - identifier TEXT NOT NULL, - name TEXT, - type_id INTEGER NOT NULL, - file_id INTEGER NOT NULL, - FOREIGN KEY (type_id) REFERENCES ref_object_types(id), - FOREIGN KEY (file_id) REFERENCES raw_files(id) -);"; - -/// `raw_modules` -/// -/// Represents modules/packages that provide raw data. Modules are uniquely -/// identified by their `object_id` and constrained so that a particular version -/// from a specific location is unique. -/// -/// Columns: -/// - `id` (INTEGER PRIMARY KEY) -/// - `object_id` (TEXT NOT NULL UNIQUE) — module object id (maps to module identity) -/// - `name` (TEXT NOT NULL) — module name -/// - `version` (TEXT NOT NULL) — module version -/// - `location_id` (INTEGER NOT NULL) — FK to `ref_module_locations(id)` -/// -/// Foreign keys: -/// - `location_id` -> `ref_module_locations(id)` -/// -/// Constraints: -/// - `UNIQUE (name, version, location_id)` — ensures a specific version of a module -/// at a given location only exists once. -pub const RAW_MODULES_TABLE: &str = r" -CREATE TABLE raw_modules ( - id INTEGER PRIMARY KEY, - object_id TEXT NOT NULL UNIQUE, - name TEXT NOT NULL, - version TEXT NOT NULL, - location_id INTEGER NOT NULL, - FOREIGN KEY (location_id) REFERENCES ref_module_locations(id), - UNIQUE (name, version, location_id) -);"; diff --git a/sqlite_lib/src/db_init/misc.rs b/sqlite_lib/src/db_init/misc.rs deleted file mode 100644 index 6fcd5d07..00000000 --- a/sqlite_lib/src/db_init/misc.rs +++ /dev/null @@ -1,83 +0,0 @@ -//! Misc dynamic-table DDL and documentation. - -/// `dyn_materials_in_state` -/// -/// Represents dynamic materials in a specific state (e.g., a material token -/// and whether it is `solid`, `liquid`, etc.). -/// -/// Columns: -/// - `id` (INTEGER PRIMARY KEY) -/// - `material_identifier` (TEXT NOT NULL) — material token/identifier -/// - `state` (TEXT DEFAULT 'solid') — material state string -pub const MATERIALS_IN_STATE_TABLE: &str = r" -CREATE TABLE dyn_materials_in_state ( - id INTEGER PRIMARY KEY, - material_identifier TEXT NOT NULL, - state TEXT DEFAULT 'solid' -);"; - -/// `dyn_items_of_material` -/// -/// Represents dynamic item definitions that are associated with a specific -/// material. Useful for modelling item variants created from a material. -/// -/// Columns: -/// - `id` (INTEGER PRIMARY KEY) -/// - `item_identifier` (TEXT NOT NULL) — token identifying the item -/// - `material_identifier` (TEXT NOT NULL) — token identifying the material -pub const ITEMS_OF_MATERIAL_TABLE: &str = r" -CREATE TABLE dyn_items_of_material ( - id INTEGER PRIMARY KEY, - item_identifier TEXT NOT NULL, - material_identifier TEXT NOT NULL -);"; - -/// `dyn_creature_caste_tags` -/// -/// Dynamic creature/caste tag registry used by caste-related tag rows that -/// refer to a creature/caste by a dynamic identifier pair. -/// -/// Columns: -/// - `id` (INTEGER PRIMARY KEY) -/// - `creature_identifier` (TEXT NOT NULL) — creature token / identifier -/// - `caste_identifier` (TEXT NOT NULL) — caste token / identifier -pub const CREATURE_CASTE_TABLE: &str = r" -CREATE TABLE dyn_creature_caste_tags ( - id INTEGER PRIMARY KEY, - creature_identifier TEXT NOT NULL, - caste_identifier TEXT NOT NULL -);"; - -/// `dyn_names` -/// -/// Stores dynamic name records used by caste and other tables that reference -/// names (singular, plural, adjective). -/// -/// Columns: -/// - `id` (INTEGER PRIMARY KEY) -/// - `singular` (TEXT NOT NULL) — singular form of the name -/// - `plural` (TEXT) — optional plural form -/// - `adjective` (TEXT) — optional adjective form -pub const NAMES_TABLE: &str = r" -CREATE TABLE dyn_names ( - id INTEGER PRIMARY KEY, - singular TEXT NOT NULL, - plural TEXT, - adjective TEXT -);"; - -/// `dyn_body_part_groups` -/// -/// Represents dynamic body-part group selectors and their associated token(s). -/// Each row maps a selector string to a specific body part token. -/// -/// Columns: -/// - `id` (INTEGER PRIMARY KEY) -/// - `body_part_selector` (TEXT NOT NULL) — selector or group token -/// - `body_part` (TEXT NOT NULL) — resolved body part token/key -pub const BODY_PART_GROUPS_TABLE: &str = r" -CREATE TABLE dyn_body_part_groups ( - id INTEGER PRIMARY KEY, - body_part_selector TEXT NOT NULL, - body_part TEXT NOT NULL -);"; diff --git a/sqlite_lib/src/db_init/mod.rs b/sqlite_lib/src/db_init/mod.rs deleted file mode 100644 index 086d6258..00000000 --- a/sqlite_lib/src/db_init/mod.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! Contains the SQL statements used to initialize the database. - -mod caste; -mod color; -mod creature; -mod creature_variation; -mod initialize; -mod metadata; -mod misc; -mod reference; -mod reference_data; -mod tile; - -pub use initialize::initialize_database; -use reference_data::biome_tags::insert_ref_biome_tags; -use reference_data::caste_tags::insert_ref_caste_tags; -use reference_data::condition_tags::insert_ref_condition_tags; -use reference_data::creature_effect_property_tags::insert_ref_creature_effect_property_tags; -use reference_data::creature_effect_tags::insert_ref_creature_effect_tags; -use reference_data::creature_tags::insert_ref_creature_tags; -use reference_data::creature_variation_tags::insert_ref_creature_variation_tags; -use reference_data::entity_tags::insert_ref_entity_tags; -use reference_data::lair_flags::insert_ref_lair_tags; -use reference_data::object_type::insert_ref_object_types; -use reference_data::secretion_triggers::insert_ref_secretion_triggers; - -/// The latest schema version of the database. -/// -/// # History -/// - 1: Initial version -pub const LATEST_SCHEMA_VERSION: u32 = 1; diff --git a/sqlite_lib/src/db_init/reference.rs b/sqlite_lib/src/db_init/reference.rs deleted file mode 100644 index cb449d18..00000000 --- a/sqlite_lib/src/db_init/reference.rs +++ /dev/null @@ -1,139 +0,0 @@ -#![allow(dead_code)] -//! Reference table DDL and documentation. -//! -//! This module defines the SQL DDL for reference tables used across all raws. -//! These tables enumerate known tokens or categories that can be referenced -//! by other tables (for example, object types, biomes, module locations, and -//! token tags). - -/// `ref_object_types` -/// -/// Purpose: -/// - Enumerates object types parsed from raw input (for example, `CREATURE`, -/// `ITEM_WEAPON`, etc.). The table maps a textual token to a numeric id used -/// by other tables to indicate an object's type. -/// -/// Columns: -/// - `id` (INTEGER PRIMARY KEY) -/// - `token` (TEXT NOT NULL UNIQUE) — canonical token for the object type -/// -/// Notes: -/// - The `token` is unique so other tables can reference the type via `id`. -pub const REF_OBJECT_TYPE_TABLE: &str = r" - CREATE TABLE ref_object_types ( - id INTEGER PRIMARY KEY, - token TEXT NOT NULL UNIQUE - );"; - -/// `ref_module_locations` -/// -/// Purpose: -/// - Represents the location/source category of a module (for example, -/// official vanilla data vs user mods). Useful for grouping modules by where -/// they came from and for reconstructing module file paths. -/// -/// Columns: -/// - `id` (INTEGER PRIMARY KEY) -/// - `token` (TEXT NOT NULL UNIQUE) — short token for the location (e.g., `Vanilla`, `Mods`) -/// - `path_fragment` (TEXT) — optional path fragment used to build file paths -pub const REF_MODULE_LOCATIONS_TABLE: &str = r" - CREATE TABLE ref_module_locations ( - id INTEGER PRIMARY KEY, - token TEXT NOT NULL UNIQUE, - path_fragment TEXT - );"; - -/// `ref_biomes` -/// -/// Purpose: -/// - Enumerates biome tokens referenced by creature/terrain metadata. -/// -/// Columns: -/// - `id` (INTEGER PRIMARY KEY) -/// - `token` (TEXT NOT NULL UNIQUE) — biome token (e.g., `GLACIER`, `ANY_LAND`) -/// - `name` (TEXT NOT NULL) — human-readable biome name -pub const REF_BIOMES_TABLE: &str = r" - CREATE TABLE ref_biome_token_tags ( - id INTEGER PRIMARY KEY, - token TEXT NOT NULL UNIQUE - );"; - -/// `ref_caste_token_tags` -/// -/// Purpose: -/// - Reference table for caste token tags. These tokens represent boolean or -/// enumerated caste properties (for example, `AMPHIBIOUS`, `FLIER`, `WEBIMMUNE`). -/// -/// Columns: -/// - `id` (INTEGER PRIMARY KEY) -/// - `token` (TEXT NOT NULL UNIQUE) — caste tag token -pub const REF_CASTE_TOKEN_TAGS: &str = r" - CREATE TABLE ref_caste_token_tags ( - id INTEGER PRIMARY KEY, - token TEXT NOT NULL UNIQUE - );"; - -/// `ref_lair_token_tags` -/// -/// Purpose: -/// - Reference tokens for lair types used by lair-related caste tags. -/// -/// Columns: -/// - `id` (INTEGER PRIMARY KEY) -/// - `token` (TEXT NOT NULL UNIQUE) — lair token -pub const REF_LAIR_TOKEN_TAGS: &str = r" - CREATE TABLE ref_lair_token_tags ( - id INTEGER PRIMARY KEY, - token TEXT NOT NULL UNIQUE - );"; - -/// `ref_secretion_triggers` -/// -/// Purpose: -/// - Tokens representing secretion trigger types referenced by secretion -/// definitions (used by caste secretion rows). -/// -/// Columns: -/// - `id` (INTEGER PRIMARY KEY) -/// - `token` (TEXT NOT NULL UNIQUE) — secretion trigger token -pub const REF_SECRETION_TRIGGERS: &str = r" - CREATE TABLE ref_secretion_triggers ( - id INTEGER PRIMARY KEY, - token TEXT NOT NULL UNIQUE - );"; - -pub const REF_CREATURE_TOKEN_TAGS: &str = r" - CREATE TABLE ref_creature_token_tags ( - id INTEGER PRIMARY KEY, - token TEXT NOT NULL UNIQUE - );"; - -pub const REF_CREATURE_VARIATION_TOKEN_TAGS: &str = r" - CREATE TABLE ref_creature_variation_token_tags ( - id INTEGER PRIMARY KEY, - token TEXT NOT NULL UNIQUE - );"; - -pub const REF_ENTITY_TOKEN_TAGS: &str = r" - CREATE TABLE ref_entity_token_tags ( - id INTEGER PRIMARY KEY, - token TEXT NOT NULL UNIQUE - );"; - -pub const REF_CONDITION_TOKEN_TAGS: &str = r" - CREATE TABLE ref_condition_token_tags ( - id INTEGER PRIMARY KEY, - token TEXT NOT NULL UNIQUE - );"; - -pub const REF_CREATURE_EFFECT_TOKEN_TAGS: &str = r" - CREATE TABLE ref_creature_effect_token_tags ( - id INTEGER PRIMARY KEY, - token TEXT NOT NULL UNIQUE - );"; - -pub const REF_CREATURE_EFFECT_PROPERTY_TOKEN_TAGS: &str = r" - CREATE TABLE ref_creature_effect_property_token_tags ( - id INTEGER PRIMARY KEY, - token TEXT NOT NULL UNIQUE - );"; diff --git a/sqlite_lib/src/db_init/reference_data/biome_tags.rs b/sqlite_lib/src/db_init/reference_data/biome_tags.rs deleted file mode 100644 index 5be35eba..00000000 --- a/sqlite_lib/src/db_init/reference_data/biome_tags.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::util::build_batch_insert; -use dfraw_parser::tags::BiomeTag; -use strum::IntoEnumIterator; -use turso::Connection; - -/// Inserts all the values for `[BiomeTag]` into the `ref_biome_token_tags` table -/// -/// # Errors -/// -/// Will error if there's a database error. -pub async fn insert_ref_biome_tags(conn: &Connection) -> Result<(), Box> { - // collect token strings then build a batched INSERT using the util helper - let mut values: Vec<&str> = Vec::new(); - - for tag in BiomeTag::iter() { - let Some(token) = tag.get_key() else { - continue; - }; - values.push(token); - } - - let batch_sql = build_batch_insert("ref_biome_token_tags", "token", &values); - - if !batch_sql.is_empty() { - conn.execute_batch(&batch_sql).await?; - } - - let mut count_rows = conn - .query("SELECT COUNT(*) FROM ref_biome_token_tags;", ()) - .await?; - let total_biome_tags: u64 = count_rows - .next() - .await? - .ok_or("Unable to verify count of biome tags")? - .get(0)?; - - tracing::info!("Inserted {total_biome_tags} tokens into `ref_biome_token_tags` table"); - - Ok(()) -} diff --git a/sqlite_lib/src/db_init/reference_data/caste_tags.rs b/sqlite_lib/src/db_init/reference_data/caste_tags.rs deleted file mode 100644 index af2f6195..00000000 --- a/sqlite_lib/src/db_init/reference_data/caste_tags.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::util::build_batch_insert; -use dfraw_parser::tags::CasteTag; -use strum::IntoEnumIterator; -use turso::Connection; - -/// Inserts all the values for `[CasteTag]` into the `ref_caste_token_tags` table -/// -/// # Errors -/// -/// Will error if there's a database error. -pub async fn insert_ref_caste_tags(conn: &Connection) -> Result<(), Box> { - // collect token strings then build a batched INSERT using the util helper - let mut values: Vec<&str> = Vec::new(); - - for caste_token in CasteTag::iter() { - let Some(token) = caste_token.get_key() else { - continue; - }; - values.push(token); - } - - let batch_sql = build_batch_insert("ref_caste_token_tags", "token", &values); - - if !batch_sql.is_empty() { - conn.execute_batch(&batch_sql).await?; - } - - let mut count_rows = conn - .query("SELECT COUNT(*) FROM ref_caste_token_tags;", ()) - .await?; - let total_caste_tags: u64 = count_rows - .next() - .await? - .ok_or("Unable to verify count of caste tags")? - .get(0)?; - - tracing::info!("Inserted {total_caste_tags} tokens into `ref_caste_token_tags` table"); - - Ok(()) -} diff --git a/sqlite_lib/src/db_init/reference_data/condition_tags.rs b/sqlite_lib/src/db_init/reference_data/condition_tags.rs deleted file mode 100644 index b35913db..00000000 --- a/sqlite_lib/src/db_init/reference_data/condition_tags.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::util::build_batch_insert; -use dfraw_parser::tags::ConditionTag; -use strum::IntoEnumIterator; -use turso::Connection; - -/// Inserts all the values for `[ConditionTag]` into the `ref_condition_token_tags` table -/// -/// # Errors -/// -/// Will error if there's a database error. -pub async fn insert_ref_condition_tags( - conn: &Connection, -) -> Result<(), Box> { - // collect token strings then build a batched INSERT using the util helper - let mut values: Vec<&str> = Vec::new(); - - for cond_tag in ConditionTag::iter() { - let Some(token) = cond_tag.get_key() else { - continue; - }; - values.push(token); - } - - let batch_sql = build_batch_insert("ref_condition_token_tags", "token", &values); - - if !batch_sql.is_empty() { - conn.execute_batch(&batch_sql).await?; - } - - let mut count_rows = conn - .query("SELECT COUNT(*) FROM ref_condition_token_tags;", ()) - .await?; - let total_condition_tags: u64 = count_rows - .next() - .await? - .ok_or("Unable to verify count of condition tags")? - .get(0)?; - - tracing::info!("Inserted {total_condition_tags} tokens into `ref_condition_token_tags` table"); - - Ok(()) -} diff --git a/sqlite_lib/src/db_init/reference_data/creature_effect_property_tags.rs b/sqlite_lib/src/db_init/reference_data/creature_effect_property_tags.rs deleted file mode 100644 index 45737c3d..00000000 --- a/sqlite_lib/src/db_init/reference_data/creature_effect_property_tags.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::util::build_batch_insert; -use dfraw_parser::tags::CreatureEffectPropertyTag; -use strum::IntoEnumIterator; -use turso::Connection; - -/// Inserts all the values for `[CreatureEffectPropertyTag]` into the `ref_creature_effect_property_token_tags` table -/// -/// # Errors -/// -/// Will error if there's a database error. -pub async fn insert_ref_creature_effect_property_tags( - conn: &Connection, -) -> Result<(), Box> { - // collect token strings then build a batched INSERT using the util helper - let mut values: Vec<&str> = Vec::new(); - - for tag in CreatureEffectPropertyTag::iter() { - let Some(token) = tag.get_key() else { - continue; - }; - values.push(token); - } - - let batch_sql = build_batch_insert("ref_creature_effect_property_token_tags", "token", &values); - - if !batch_sql.is_empty() { - conn.execute_batch(&batch_sql).await?; - } - - let mut count_rows = conn - .query( - "SELECT COUNT(*) FROM ref_creature_effect_property_token_tags;", - (), - ) - .await?; - let total_creature_effect_property_tags: u64 = count_rows - .next() - .await? - .ok_or("Unable to verify count of creature effect property tags")? - .get(0)?; - - tracing::info!( - "Inserted {total_creature_effect_property_tags} tokens into `ref_creature_effect_property_token_tags` table" - ); - - Ok(()) -} diff --git a/sqlite_lib/src/db_init/reference_data/creature_effect_tags.rs b/sqlite_lib/src/db_init/reference_data/creature_effect_tags.rs deleted file mode 100644 index 129d6f47..00000000 --- a/sqlite_lib/src/db_init/reference_data/creature_effect_tags.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::util::build_batch_insert; -use dfraw_parser::tags::CreatureEffectTag; -use strum::IntoEnumIterator; -use turso::Connection; - -/// Inserts all the values for `[CreatureEffectTag]` into the `ref_creature_effect_token_tags` table -/// -/// # Errors -/// -/// Will error if there's a database error. -pub async fn insert_ref_creature_effect_tags( - conn: &Connection, -) -> Result<(), Box> { - // collect token strings then build a batched INSERT using the util helper - let mut values: Vec<&str> = Vec::new(); - - for tag in CreatureEffectTag::iter() { - let Some(token) = tag.get_key() else { - continue; - }; - values.push(token); - } - - let batch_sql = build_batch_insert("ref_creature_effect_token_tags", "token", &values); - - if !batch_sql.is_empty() { - conn.execute_batch(&batch_sql).await?; - } - - let mut count_rows = conn - .query("SELECT COUNT(*) FROM ref_creature_effect_token_tags;", ()) - .await?; - let total_creature_effect_tags: u64 = count_rows - .next() - .await? - .ok_or("Unable to verify count of creature effect tags")? - .get(0)?; - - tracing::info!( - "Inserted {total_creature_effect_tags} tokens into `ref_creature_effect_token_tags` table" - ); - - Ok(()) -} diff --git a/sqlite_lib/src/db_init/reference_data/creature_tags.rs b/sqlite_lib/src/db_init/reference_data/creature_tags.rs deleted file mode 100644 index 808ee514..00000000 --- a/sqlite_lib/src/db_init/reference_data/creature_tags.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::util::build_batch_insert; -use dfraw_parser::tags::CreatureTag; -use strum::IntoEnumIterator; -use turso::Connection; - -/// Inserts all the values for `[CreatureTag]` into the `ref_creature_token_tags` table -/// -/// # Errors -/// -/// Will error if there's a database error. -pub async fn insert_ref_creature_tags(conn: &Connection) -> Result<(), Box> { - // collect token strings then build a batched INSERT using the util helper - let mut values: Vec<&str> = Vec::new(); - - for creature_token in CreatureTag::iter() { - let Some(token) = creature_token.get_key() else { - continue; - }; - values.push(token); - } - - let batch_sql = build_batch_insert("ref_creature_token_tags", "token", &values); - - if !batch_sql.is_empty() { - conn.execute_batch(&batch_sql).await?; - } - - let mut count_rows = conn - .query("SELECT COUNT(*) FROM ref_creature_token_tags;", ()) - .await?; - let total_creature_tags: u64 = count_rows - .next() - .await? - .ok_or("Unable to verify count of creature tags")? - .get(0)?; - - tracing::info!("Inserted {total_creature_tags} tokens into `ref_creature_token_tags` table"); - - Ok(()) -} diff --git a/sqlite_lib/src/db_init/reference_data/creature_variation_tags.rs b/sqlite_lib/src/db_init/reference_data/creature_variation_tags.rs deleted file mode 100644 index edd34cbb..00000000 --- a/sqlite_lib/src/db_init/reference_data/creature_variation_tags.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::util::build_batch_insert; -use dfraw_parser::tags::CreatureVariationTag; -use strum::IntoEnumIterator; -use turso::Connection; - -/// Inserts all the values for `[CreatureVariationTag]` into the `ref_creature_variation_token_tags` table -/// -/// # Errors -/// -/// Will error if there's a database error. -pub async fn insert_ref_creature_variation_tags( - conn: &Connection, -) -> Result<(), Box> { - // collect token strings then build a batched INSERT using the util helper - let mut values: Vec<&str> = Vec::new(); - - for tag in CreatureVariationTag::iter() { - let Some(token) = tag.get_key() else { - continue; - }; - values.push(token); - } - - let batch_sql = build_batch_insert("ref_creature_variation_token_tags", "token", &values); - - if !batch_sql.is_empty() { - conn.execute_batch(&batch_sql).await?; - } - - let mut count_rows = conn - .query( - "SELECT COUNT(*) FROM ref_creature_variation_token_tags;", - (), - ) - .await?; - let total_creature_variation_tags: u64 = count_rows - .next() - .await? - .ok_or("Unable to verify count of creature variation tags")? - .get(0)?; - - tracing::info!( - "Inserted {total_creature_variation_tags} tokens into `ref_creature_variation_token_tags` table" - ); - - Ok(()) -} diff --git a/sqlite_lib/src/db_init/reference_data/entity_tags.rs b/sqlite_lib/src/db_init/reference_data/entity_tags.rs deleted file mode 100644 index 1b30bf8f..00000000 --- a/sqlite_lib/src/db_init/reference_data/entity_tags.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::util::build_batch_insert; -use dfraw_parser::tags::EntityTag; -use strum::IntoEnumIterator; -use turso::Connection; - -/// Inserts all the values for `[EntityTag]` into the `ref_entity_token_tags` table -/// -/// # Errors -/// -/// Will error if there's a database error. -pub async fn insert_ref_entity_tags(conn: &Connection) -> Result<(), Box> { - // collect token strings then build a batched INSERT using the util helper - let mut values: Vec<&str> = Vec::new(); - - for tag in EntityTag::iter() { - let Some(token) = tag.get_key() else { - continue; - }; - values.push(token); - } - - let batch_sql = build_batch_insert("ref_entity_token_tags", "token", &values); - - if !batch_sql.is_empty() { - conn.execute_batch(&batch_sql).await?; - } - - let mut count_rows = conn - .query("SELECT COUNT(*) FROM ref_entity_token_tags;", ()) - .await?; - let total_entity_tags: u64 = count_rows - .next() - .await? - .ok_or("Unable to verify count of entity tags")? - .get(0)?; - - tracing::info!("Inserted {total_entity_tags} tokens into `ref_entity_token_tags` table"); - - Ok(()) -} diff --git a/sqlite_lib/src/db_init/reference_data/lair_flags.rs b/sqlite_lib/src/db_init/reference_data/lair_flags.rs deleted file mode 100644 index f56cf231..00000000 --- a/sqlite_lib/src/db_init/reference_data/lair_flags.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::util::build_batch_insert; -use turso::Connection; - -/// Inserts all the known values for lair tags. -/// -/// # Errors -/// -/// Will error if there's a database error. -pub async fn insert_ref_lair_tags(conn: &Connection) -> Result<(), Box> { - let tokens = [ - "SIMPLE_BURROW", - "SIMPLE_MOUND", - "WILDERNESS_LOCATION", - "SHRINE", - "LABYRINTH", - ]; - let batch_sql = build_batch_insert("ref_lair_token_tags", "token", &tokens); - - if !batch_sql.is_empty() { - conn.execute_batch(&batch_sql).await?; - } - - let mut count_rows = conn - .query("SELECT COUNT(*) FROM ref_lair_token_tags;", ()) - .await?; - let total_lair_tags: u64 = count_rows - .next() - .await? - .ok_or("Unable to verify count of lair_tags")? - .get(0)?; - - tracing::info!("Inserted {total_lair_tags} tokens into `ref_lair_token_tags` table"); - - Ok(()) -} diff --git a/sqlite_lib/src/db_init/reference_data/mod.rs b/sqlite_lib/src/db_init/reference_data/mod.rs deleted file mode 100644 index fe45d718..00000000 --- a/sqlite_lib/src/db_init/reference_data/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub mod biome_tags; -pub mod caste_tags; -pub mod condition_tags; -pub mod creature_effect_property_tags; -pub mod creature_effect_tags; -pub mod creature_tags; -pub mod creature_variation_tags; -pub mod entity_tags; -pub mod lair_flags; -pub mod object_type; -pub mod secretion_triggers; diff --git a/sqlite_lib/src/db_init/reference_data/object_type.rs b/sqlite_lib/src/db_init/reference_data/object_type.rs deleted file mode 100644 index 06cba1f5..00000000 --- a/sqlite_lib/src/db_init/reference_data/object_type.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::util::build_batch_insert; -use dfraw_parser::metadata::ObjectType; -use strum::IntoEnumIterator; -use turso::Connection; - -/// Inserts all the values for `[ObjectType]` into the `ref_object_types` table -/// -/// # Errors -/// -/// Will error if there's a database error. -pub async fn insert_ref_object_types(conn: &Connection) -> Result<(), Box> { - // collect token strings then build a batched INSERT using the util helper - let mut values: Vec<&str> = Vec::new(); - - for object_type in ObjectType::iter() { - let Some(token) = object_type.get_key() else { - continue; - }; - values.push(token); - } - - let batch_sql = build_batch_insert("ref_object_types", "token", &values); - - if !batch_sql.is_empty() { - conn.execute_batch(&batch_sql).await?; - } - - let mut count_rows = conn - .query("SELECT COUNT(*) FROM ref_object_types;", ()) - .await?; - let total_object_type_tags: u64 = count_rows - .next() - .await? - .ok_or("Unable to verify count of object_type_tags")? - .get(0)?; - - tracing::info!("Inserted {total_object_type_tags} tokens into `ref_object_types` table"); - - Ok(()) -} diff --git a/sqlite_lib/src/db_init/reference_data/secretion_triggers.rs b/sqlite_lib/src/db_init/reference_data/secretion_triggers.rs deleted file mode 100644 index 06e247a7..00000000 --- a/sqlite_lib/src/db_init/reference_data/secretion_triggers.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::util::build_batch_insert; -use turso::Connection; - -/// Inserts all the known values for secretion triggers. -/// -/// # Errors -/// -/// Will error if there's a database error. -pub async fn insert_ref_secretion_triggers( - conn: &Connection, -) -> Result<(), Box> { - let tokens = ["CONTINUOUS", "EXERTION", "EXTREME_EMOTION"]; - let batch_sql = build_batch_insert("ref_secretion_triggers", "token", &tokens); - - if !batch_sql.is_empty() { - conn.execute_batch(&batch_sql).await?; - } - - let mut count_rows = conn - .query("SELECT COUNT(*) FROM ref_secretion_triggers;", ()) - .await?; - let total_secretion_triggers: u64 = count_rows - .next() - .await? - .ok_or("Unable to verify count of secretion_triggers")? - .get(0)?; - - tracing::info!( - "Inserted {total_secretion_triggers} tokens into `ref_secretion_triggers` table" - ); - - Ok(()) -} diff --git a/sqlite_lib/src/db_init/tile.rs b/sqlite_lib/src/db_init/tile.rs deleted file mode 100644 index 7e282081..00000000 --- a/sqlite_lib/src/db_init/tile.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! Tile table DDL and documentation. - -/// `tiles` -/// -/// Columns and semantics: -/// - `id` (INTEGER PRIMARY KEY) — numeric identifier for the tile record. -/// - `character` (TEXT) — primary character/token for the tile (e.g., `[CASTE_TILE]`). -/// - `alt_character` (TEXT) — alternate character/token (e.g., `[CASTE_ALTTILE]`). -/// - `color` (TEXT) — color token or descriptor (e.g., `[CASTE_COLOR]`). -/// - `glow_character` (TEXT) — glyph used when the tile is glowing (e.g., `[CASTE_GLOWTILE]`). -/// - `glow_color` (TEXT) — color applied when the tile is glowing (e.g., `[CASTE_GLOWCOLOR]`). -pub const TILES_TABLE: &str = r" - CREATE TABLE tiles ( - id INTEGER PRIMARY KEY, - character TEXT, - alt_character TEXT, - color TEXT, - glow_character TEXT, - glow_color TEXT - );"; diff --git a/sqlite_lib/src/lib.rs b/sqlite_lib/src/lib.rs index be4f44b9..a80efad8 100644 --- a/sqlite_lib/src/lib.rs +++ b/sqlite_lib/src/lib.rs @@ -5,7 +5,12 @@ //! This is an experimental library, it is not yet ready for production use. Currently it only will create a database and may function to insert //! or read data but has not been thoroughly tested. -pub mod client; -pub(crate) mod db_init; -pub mod queries; -pub mod util; +mod db; +pub(crate) mod search_helpers; + +pub use db::client::DbClient; +pub use db::client_options::ClientOptions; +pub use db::graphics_data::*; +pub use db::metadata; +pub use db::search_query::SearchQuery; +pub use db::search_results::*; diff --git a/sqlite_lib/src/queries/caste/get.rs b/sqlite_lib/src/queries/caste/get.rs deleted file mode 100644 index 3f690ae7..00000000 --- a/sqlite_lib/src/queries/caste/get.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! GET helper methods for the `[Caste]` struct. - -use turso::{Connection, params}; - -use crate::{client::DbClient, queries::caste::GET_CASTE_NAME_ID_BY_CASTE_ID_AND_POSITION}; - -impl DbClient {} - -/// Get the `caste_name` record for a specific caste id and `tag_position` -/// -/// # Errors -/// -/// Passes along database errors -pub async fn get_caste_name_id_by_caste_id_and_tag_position( - conn: &Connection, - caste_id: i64, - tag_position: i64, -) -> Result, Box> { - let mut id_rows = conn - .query( - GET_CASTE_NAME_ID_BY_CASTE_ID_AND_POSITION, - params![caste_id, tag_position], - ) - .await?; - - match id_rows.next().await? { - Some(row) => { - let id: i64 = row.get(0)?; - Ok(Some(id)) - } - None => Ok(None), // Return None if no row exists - } -} diff --git a/sqlite_lib/src/queries/caste/get_sql.rs b/sqlite_lib/src/queries/caste/get_sql.rs deleted file mode 100644 index b0be0ce0..00000000 --- a/sqlite_lib/src/queries/caste/get_sql.rs +++ /dev/null @@ -1,99 +0,0 @@ -//! SQL for GET operations on the caste tables. - -use const_format::concatcp; - -/// Query to get all castes -pub const GET_ALL_CASTES: &str = r" -SELECT - -- 1. Identity & Scalars - c.id, - c.creature_id, - c.identifier AS caste_identifier, - c.description, - c.baby_name_singular, - c.caste_name_singular, - c.pet_value, - c.pop_ratio, - - -- 2. Visuals (1:1 Relationship from caste_tiles) - t.character AS tile_char, - t.alt_character AS tile_alt_char, - t.color AS tile_color, - t.glow_color, - - -- 3. Flags (Aggregated Lists of Strings) - (SELECT json_group_array(ref.token) - FROM caste_flags cf - JOIN ref_caste_flag_types ref ON cf.flag_id = ref.id - WHERE cf.caste_id = c.id - ) AS flags, - - (SELECT json_group_array(class_name) - FROM caste_creature_classes ccc - WHERE ccc.caste_id = c.id - ) AS classes, - - -- 4. (Aggregated Lists of Objects) - (SELECT json_group_array(json_object( - 'years', years, - 'days', days, - 'size', size_cm3 - )) - FROM caste_body_sizes bs - WHERE bs.caste_id = c.id - ) AS body_sizes, - - (SELECT json_group_array(json_object( - 'type', gait_type, - 'max_speed', max_speed, - 'start_speed', start_speed, - 'energy', energy_usage - )) - FROM caste_gaits cg - WHERE cg.caste_id = c.id - ) AS gaits, - - -- 7. (Aggregated Lists of Complex Objects) - (SELECT json_group_array(json_object( - 'variation', av.identifier, - 'order', av.sort_order, - 'args', ( - SELECT json_group_array(arg.argument_value) - FROM applied_variation_args arg - WHERE arg.variation_id = av.id - ORDER BY arg.argument_order - ) - )) - FROM applied_variations av - WHERE av.caste_id = c.id - ORDER BY av.sort_order - ) AS variations - -FROM castes c -LEFT JOIN caste_tiles t ON c.id = t.caste_id"; - -/// Puts the creature id paramter as '?1' -pub const GET_BY_CREATURE_ID: &str = concatcp!(GET_ALL_CASTES, " WHERE c.creature_id = ?1"); - -/// Puts the caste id parameter as '?1' -pub const GET_BY_ID: &str = concatcp!(GET_ALL_CASTES, " WHERE c.id = ?1"); - -/// Get the id for a specific `[dfraw_parser::Caste]` -/// -/// Uses parameters: -/// -/// 1. parent creature id -/// 2. caste identifier -pub const GET_ID_BY_CREATURE_AND_IDENTIFIER: &str = r" -SELECT id FROM castes -WHERE creature_id = ?1 AND identifier = ?2;"; - -/// Get body detail plan id for specific caste and plan name -pub const GET_BODY_DETAIL_PLAN_BY_CASTE_ID_AND_NAME: &str = r" -SELECT id FROM caste_body_detail_plans -WHERE caste_id = ?1 AND name = ?2;"; - -/// Get caste name reocrd id for specific caste id and tag position -pub const GET_CASTE_NAME_ID_BY_CASTE_ID_AND_POSITION: &str = r" -SELECT id FROM caste_names -WHERE caste_id = ?1 AND tag_position = ?2;"; diff --git a/sqlite_lib/src/queries/caste/insert.rs b/sqlite_lib/src/queries/caste/insert.rs deleted file mode 100644 index 79f550c5..00000000 --- a/sqlite_lib/src/queries/caste/insert.rs +++ /dev/null @@ -1,1191 +0,0 @@ -//! Insert helper methods for the `[Caste]` struct. -use std::vec; - -use dfraw_parser::Caste; -use dfraw_parser::tags::CasteTag; -use turso::params; - -use crate::client::DbClient; -use crate::queries::caste::get::get_caste_name_id_by_caste_id_and_tag_position; -use crate::queries::caste::{TagValue, insert_value_tag}; -use crate::queries::color::get::get_or_insert_color; -use crate::queries::misc::get::{ - get_or_insert_dynamic_creature_caste_tag, get_or_insert_dynamic_name, get_ref_lair_token_id, - get_ref_object_type_id, -}; - -impl DbClient { - /// Inserts a `[Caste]` into the database. - /// - /// # Parameters - /// - /// - `caste`: the `[Caste]` to insert - /// - `creature_id`: the id of the parent `[dfraw_parser::Creature]` in the creatures table - /// - /// # Errors - /// - /// - Will error if a database interaction fails. - /// - Will error if the index of a token in a caste is outside of bounds for `usize` to `i64` conversion - pub async fn insert_caste( - &self, - caste: &Caste, - creature_id: i64, - ) -> Result<(), Box> { - // First, we need a `caste_id` to work with, so basic info insertion - let conn = self.get_connection()?; - - conn.execute( - super::INSERT_IDENTITY, - params![creature_id, caste.get_identifier()], - ) - .await?; - // Grab the id for what we inserted - let mut id_rows = conn - .query( - super::GET_ID_BY_CREATURE_AND_IDENTIFIER, - params![creature_id, caste.get_identifier()], - ) - .await?; - - let caste_id: i64 = id_rows - .next() - .await? - .ok_or("No ID found after caste identity insertion")? - .get(0)?; - - // Now we will loop through all the tags in the caste and insert them appropriately - for (tag_position, tag) in caste.get_tags().iter().enumerate() { - let token_str = tag.get_key().ok_or("Unmapped token found")?; - let tag_id = self.get_caste_flag_id_by_token(token_str).await?; - let position = i64::try_from(tag_position)?; - - insert_caste_tag(&conn, caste_id, tag_id, position, tag).await?; - } - - Ok(()) - } -} - -/// Insert a caste tag -/// -/// # Errors -/// -/// Will error if there's a database interaction error -#[allow(clippy::too_many_lines, clippy::large_stack_frames)] -pub async fn insert_caste_tag( - conn: &turso::Connection, - caste_id: i64, - tag_id: i64, - tag_position: i64, - tag: &CasteTag, -) -> Result<(), Box> { - match tag { - // Case A: Simple Tags (flags) - CasteTag::AdoptsOwner - | CasteTag::AlcoholDependent - | CasteTag::AllActive - | CasteTag::AmbushPredator - | CasteTag::Amphibious - | CasteTag::ApplyCurrentCreatureVariation - | CasteTag::Aquatic - | CasteTag::ArenaRestricted - | CasteTag::AtPeaceWithWildlife - | CasteTag::Benign - | CasteTag::BloodSucker - | CasteTag::BoneCarn - | CasteTag::CanLearn - | CasteTag::CanSpeak - | CasteTag::CannotClimb - | CasteTag::CannotJump - | CasteTag::CannotUndead - | CasteTag::CanOpenDoors - | CasteTag::Carnivore - | CasteTag::CaveAdaptation - | CasteTag::CommonDomestic - | CasteTag::ConvertedSpouse - | CasteTag::CookableLive - | CasteTag::Crazed - | CasteTag::Crepuscular - | CasteTag::CuriousBeastEater - | CasteTag::CuriousBeastGuzzler - | CasteTag::CuriousBeastItem - | CasteTag::Demon - | CasteTag::DieWhenVerminBite - | CasteTag::Diurnal - | CasteTag::DiveHuntsVermin - | CasteTag::Equips - | CasteTag::Extravision - | CasteTag::FeatureAttackGroup - | CasteTag::FeatureBeast - | CasteTag::Female - | CasteTag::FireImmune - | CasteTag::FireImmuneSuper - | CasteTag::FishItem - | CasteTag::FleeQuick - | CasteTag::Flier - | CasteTag::GetsInfectionsFromRot - | CasteTag::GetsWoundInfections - | CasteTag::HasNerves - | CasteTag::HasShell - | CasteTag::HuntsVermin - | CasteTag::Immobile - | CasteTag::ImmobileLand - | CasteTag::Immolate - | CasteTag::Intelligent - | CasteTag::LairHunter - | CasteTag::LargePredator - | CasteTag::LaysEggs - | CasteTag::LightGen - | CasteTag::LikesFighting - | CasteTag::Lisp - | CasteTag::LockPicker - | CasteTag::Magical - | CasteTag::MagmaVision - | CasteTag::Male - | CasteTag::MannerismLaugh - | CasteTag::MannerismSmile - | CasteTag::MannerismWalk - | CasteTag::MannerismSit - | CasteTag::MannerismBreath - | CasteTag::MannerismPosture - | CasteTag::MannerismStretch - | CasteTag::MannerismEyelids - | CasteTag::Matutinal - | CasteTag::Meanderer - | CasteTag::Megabeast - | CasteTag::Mischievous - | CasteTag::Mount - | CasteTag::MountExotic - | CasteTag::MultipartFullVision - | CasteTag::MultipleLitterRare - | CasteTag::Natural - | CasteTag::NightCreatureBogeyman - | CasteTag::NightCreatureExperimenter - | CasteTag::NightCreatureHunter - | CasteTag::NightCreatureNightmare - | CasteTag::NoConnectionsForMovement - | CasteTag::NoDizziness - | CasteTag::NoDrink - | CasteTag::NoEat - | CasteTag::NoFall - | CasteTag::NoFevers - | CasteTag::NoGender - | CasteTag::NoPhysicalAttributeGain - | CasteTag::NoPhysicalAttributeRust - | CasteTag::NoSleep - | CasteTag::NoSpring - | CasteTag::NoSummer - | CasteTag::NoThoughtCenterForMovement - | CasteTag::NoUnitTypeColor - | CasteTag::NoVegetationDisturbance - | CasteTag::NoWinter - | CasteTag::NoBones - | CasteTag::NoBreathe - | CasteTag::Nocturnal - | CasteTag::NoEmotion - | CasteTag::NoExert - | CasteTag::NoFear - | CasteTag::NoMeat - | CasteTag::NoNausea - | CasteTag::NoPain - | CasteTag::NoSkin - | CasteTag::NoSkull - | CasteTag::NoSmellyRot - | CasteTag::NoStuckIns - | CasteTag::NoStun - | CasteTag::NotButcherable - | CasteTag::NotLiving - | CasteTag::NoThought - | CasteTag::OpposedToLife - | CasteTag::OutsiderControllable - | CasteTag::PackAnimal - | CasteTag::ParalyzeImmune - | CasteTag::PatternFlier - | CasteTag::Pearl - | CasteTag::Pet - | CasteTag::PetExotic - | CasteTag::Power - | CasteTag::RemainsOnVerminBiteDeath - | CasteTag::RemainsUndetermined - | CasteTag::ReturnsVerminKillsToOwner - | CasteTag::SemiMegabeast - | CasteTag::SlowLearner - | CasteTag::SmallRemains - | CasteTag::SpouseConversionTarget - | CasteTag::SpouseConverter - | CasteTag::SpreadEvilSpheresIfRuler - | CasteTag::StanceClimber - | CasteTag::StandardGrazer - | CasteTag::StrangeMoods - | CasteTag::Supernatural - | CasteTag::SwimsInnate - | CasteTag::SwimsLearned - | CasteTag::ThickWeb - | CasteTag::Titan - | CasteTag::Trainable - | CasteTag::TrainableHunting - | CasteTag::TrainableWar - | CasteTag::Trances - | CasteTag::TrapAvoid - | CasteTag::UnderSwim - | CasteTag::UniqueDemon - | CasteTag::Vegetation - | CasteTag::VerminHateable - | CasteTag::VerminMicro - | CasteTag::VerminNoFish - | CasteTag::VerminNoRoam - | CasteTag::VerminNoTrap - | CasteTag::VerminHunter - | CasteTag::Vespertine - | CasteTag::WagonPuller - | CasteTag::WebImmune - | CasteTag::Unknown - | CasteTag::NightCreature - | CasteTag::NotFireImmune - | CasteTag::HasBlood - | CasteTag::Grasp - | CasteTag::RaceGait - | CasteTag::CannotBreatheWater - | CasteTag::NaturalAnimal - | CasteTag::CuriousBeast - | CasteTag::CannotBreatheAir => { - conn.execute(super::INSERT_TAG, params![caste_id, tag_id, tag_position]) - .await?; - } - CasteTag::BuildingDestroyer { - door_and_furniture_focused, - } => { - let values = vec![TagValue::Bool(*door_and_furniture_focused)]; - - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - // B.1: String flags - CasteTag::AltTile { tile } - | CasteTag::GlowTile { tile } - | CasteTag::SoldierTile { tile } - | CasteTag::SoldierAltTile { tile } - | CasteTag::Tile { tile } => { - let values = vec![TagValue::String(String::from(tile.value))]; - - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::Webber { material } => { - let values: Vec = material - .iter() - .map(|v| TagValue::String(String::from(v))) - .collect(); - - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::Extract { material } => { - let values = vec![TagValue::String(material.clone())]; - - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::BodyPartAddType { body_part_type } - | CasteTag::BodyPartRemoveType { body_part_type } => { - let values = vec![TagValue::String(body_part_type.clone())]; - - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::CreatureVariationAddTag { tag } - | CasteTag::CreatureVariationRemoveTag { tag } => { - let values = vec![TagValue::String(tag.clone())]; - - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::BodyGloss { gloss } => { - let values = vec![TagValue::String(gloss.clone())]; - - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::CanDoInteraction { interaction } => { - let values = vec![TagValue::String(interaction.clone())]; - - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::CreatureClass { class } => { - let values = vec![TagValue::String(class.clone())]; - - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::Description { description } => { - let values = vec![TagValue::String(description.clone())]; - - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::Gait { gait_values } => { - //todo: this needs updated to insert into the `caste_gaits` table - let values: Vec = gait_values - .iter() - .map(|v| TagValue::String(String::from(v))) - .collect(); - - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::Gnawer { verb } => { - let values = vec![TagValue::String(verb.clone())]; - - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::GobbleVerminClass { vermin_class } => { - let values = vec![TagValue::String(vermin_class.clone())]; - - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::Habit { habit } => { - let values = vec![TagValue::String(habit.clone())]; - - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::ExtraButcherObjectShape { shape } => { - let values = vec![TagValue::String(shape.clone())]; - - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::LairCharacteristic { characteristic } => { - let values = vec![TagValue::String(characteristic.clone())]; - - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::LairHunterSpeech { speech_file } | CasteTag::SlainSpeech { speech_file } => { - let values = vec![TagValue::String(speech_file.clone())]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::MannerismNose { nose } => { - let values = vec![TagValue::String(nose.clone())]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::MannerismEar { ear } => { - let values = vec![TagValue::String(ear.clone())]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::MannerismHead { head } => { - let values = vec![TagValue::String(head.clone())]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::MannerismEyes { eyes } => { - let values = vec![TagValue::String(eyes.clone())]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::MannerismMouth { mouth } => { - let values = vec![TagValue::String(mouth.clone())]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::MannerismHair { hair } => { - let values = vec![TagValue::String(hair.clone())]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::MannerismKnuckles { knuckles } => { - let values = vec![TagValue::String(knuckles.clone())]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::MannerismLips { lips } => { - let values = vec![TagValue::String(lips.clone())]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::MannerismCheek { cheek } => { - let values = vec![TagValue::String(cheek.clone())]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::MannerismNails { nails } => { - let values = vec![TagValue::String(nails.clone())]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::MannerismFeet { feet } => { - let values = vec![TagValue::String(feet.clone())]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::MannerismArms { arms } => { - let values = vec![TagValue::String(arms.clone())]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::MannerismHands { hands } => { - let values = vec![TagValue::String(hands.clone())]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::MannerismTongue { tongue } => { - let values = vec![TagValue::String(tongue.clone())]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::MannerismLeg { leg } => { - let values = vec![TagValue::String(leg.clone())]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::ModValue { value } => { - let values = vec![TagValue::String(value.clone())]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::OdorString { odor_string } => { - let values = vec![TagValue::String(odor_string.clone())]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::RemainsColor { remains_color } => { - let values = vec![TagValue::String(remains_color.clone())]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - // B.2: Integer flags - CasteTag::Baby { age } | CasteTag::Child { age } => { - let values = vec![TagValue::Int(i64::from(*age))]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::BeachFrequency { frequency } => { - let values = vec![TagValue::Int(i64::from(*frequency))]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::ChangeBodySizePercent { percent } => { - let values = vec![TagValue::Int(i64::from(*percent))]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::Difficulty { difficulty } => { - let values = vec![TagValue::Int(i64::from(*difficulty))]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::EggSize { size } => { - let values = vec![TagValue::Int(i64::from(*size))]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::FixedTemp { temperature } => { - let values = vec![TagValue::Int(i64::from(*temperature))]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::GravitateBodySize { target } => { - let values = vec![TagValue::Int(i64::from(*target))]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::Grazer { grazer } => { - let values = vec![TagValue::Int(i64::from(*grazer))]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::HabitNumber { number } => { - let values = vec![TagValue::Int(i64::from(u32::from(number)))]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::Homeotherm { temperature } => { - let values = vec![TagValue::Int(i64::from(*temperature))]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::GrassTrample { trample } => { - let values = vec![TagValue::Int(i64::from(*trample))]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::LowLightVision { vision } => { - let values = vec![TagValue::Int(i64::from(*vision))]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::OdorLevel { odor_level } => { - let values = vec![TagValue::Int(i64::from(*odor_level))]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::PenetratePower { penetrate_power } => { - let values = vec![TagValue::Int(i64::from(*penetrate_power))]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::PetValue { pet_value } => { - let values = vec![TagValue::Int(i64::from(*pet_value))]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::PetValueDivisor { divisor } => { - let values = vec![TagValue::Int(i64::from(*divisor))]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::PopulationRatio { pop_ratio } => { - let values = vec![TagValue::Int(i64::from(*pop_ratio))]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::ProneToRage { rage_chance } => { - let values = vec![TagValue::Int(i64::from(*rage_chance))]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::ItemCorpseQuality { quality } => { - let values = vec![TagValue::Int(i64::from(*quality))]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::SkillLearnRates { rate } => { - let values = vec![TagValue::Int(i64::from(*rate))]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::TradeCapacity { capacity } => { - let values = vec![TagValue::Int(i64::from(*capacity))]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::ViewRange { view_range } => { - let values = vec![TagValue::Int(i64::from(*view_range))]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - // B.3: Range flags - CasteTag::ClutchSize { min, max } - | CasteTag::LitterSize { min, max } - | CasteTag::MaxAge { min, max } => { - let values = vec![ - TagValue::Int(i64::from(*min)), - TagValue::Int(i64::from(*max)), - ]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::GeneralMaterialForceMultiplier { value_a, value_b } => { - let values = vec![ - TagValue::Int(i64::from(*value_a)), - TagValue::Int(i64::from(*value_b)), - ]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - // B.4: 7-spread range flags - CasteTag::MentalAttributeRange { attribute, ranges } - | CasteTag::PhysicalAttributeRange { attribute, ranges } => { - let values = vec![ - TagValue::String(attribute.clone()), - TagValue::Int(ranges[0].into()), - TagValue::Int(ranges[1].into()), - TagValue::Int(ranges[2].into()), - TagValue::Int(ranges[3].into()), - TagValue::Int(ranges[4].into()), - TagValue::Int(ranges[5].into()), - TagValue::Int(ranges[6].into()), - ]; - - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::BodyAppearanceModifier { attribute, values } => { - let mut vals: Vec = Vec::with_capacity(8); - vals.push(TagValue::String(attribute.clone())); - for v in values { - vals.push(TagValue::Int(i64::from(*v))); - } - insert_value_tag(conn, caste_id, tag_id, tag_position, &vals).await?; - } - // B.5: String - Integer flags - CasteTag::SyndromeDilutionFactor { - syndrome, - percentage, - } => { - let values = vec![ - TagValue::String(syndrome.clone()), - TagValue::Int(i64::from(*percentage)), - ]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::Tendons { - material, - healing_rate, - } - | CasteTag::Ligaments { - material, - healing_rate, - } => { - // store as value-tag so dynamic material handling is not bypassed here - let mut values: Vec = material - .iter() - .map(|v| TagValue::String(String::from(v))) - .collect(); - - values.push(TagValue::Int(i64::from(*healing_rate))); - - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::NaturalSkill { skill, level } => { - let values = vec![ - TagValue::String(skill.clone()), - TagValue::Int(i64::from(*level)), - ]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::MentalAttributeCapPercentage { - attribute, - percentage, - } - | CasteTag::PhysicalAttributeCapPercentage { - attribute, - percentage, - } => { - let values = vec![ - TagValue::String(attribute.clone()), - TagValue::Int(i64::from(*percentage)), - ]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::Milkable { - material, - frequency, - } => { - // store as value-tag to unify value insertion - let mut values: Vec = material - .iter() - .map(|v| TagValue::String(String::from(v))) - .collect(); - - values.push(TagValue::Int(i64::from(*frequency))); - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - // B.6: String - String flags - CasteTag::MannerismFingers { finger, fingers } => { - let values = vec![ - TagValue::String(finger.clone()), - TagValue::String(fingers.clone()), - ]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::SpecificFood { - food_type, - identifier, - } => { - let Some(food_type_token) = food_type.get_key() else { - return Err("Failed to get token for food type".into()); - }; - let food_type_object_type_id = get_ref_object_type_id(conn, food_type_token).await?; - - conn.execute( - super::INSERT_SPECIFIC_FOOD_TAG, - params![ - caste_id, - tag_id, - tag_position, - food_type_object_type_id, - identifier.as_str() - ], - ) - .await?; - } - // Case C: Properties in special tables - // CasteTag::Attack { name, body_part } => { - // conn.execute( - // super::INSERT_ATTACK_TAG, - // params![caste_id, tag_position, name.as_str(), body_part.as_str()], - // ) - // .await?; - // } - CasteTag::AttackTrigger { - population, - exported_wealth, - created_wealth, - } => { - conn.execute( - super::INSERT_ATTACK_TRIGGER_TAG, - params![ - caste_id, - tag_position, - population, - exported_wealth, - created_wealth - ], - ) - .await?; - } - CasteTag::BodyDetailPlan { - body_plan, - arguments, - } => { - // Must insert body plan identity and get id first - conn.execute( - super::INSERT_BODY_DETAIL_PLAN_IDENTITY_TAG, - params![caste_id, tag_position, body_plan.as_str()], - ) - .await?; - // Grab the id for what we inserted - let mut id_rows = conn - .query( - super::GET_BODY_DETAIL_PLAN_BY_CASTE_ID_AND_NAME, - params![caste_id, body_plan.as_str()], - ) - .await?; - - let body_detail_plan_id: i64 = id_rows - .next() - .await? - .ok_or("No ID found after caste identity insertion")? - .get(0)?; - - let mut batch_insert_sql = String::new(); - for (idx, argument) in arguments.iter().enumerate() { - let argument_index = i64::try_from(idx)?; - let insert_sql = format!( - "INSERT INTO caste_body_detail_plan_args (body_detail_plan_id, argument_index, argument) - VALUES ({body_detail_plan_id}, {argument_index}, '{argument}');" - ); - batch_insert_sql.push_str(&insert_sql); - } - - conn.execute_batch(&batch_insert_sql).await?; - } - // CasteTag::ItemCorpse { item, material } - // | CasteTag::ExtraButcherObjectItem { item, material } - // | CasteTag::LaysUnusualEggs { item, material } => { - // let dyn_item_id = - // get_or_insert_dynamic_item_of_material(conn, item.as_str(), material.as_str()) - // .await?; - - // conn.execute( - // super::INSERT_ITEM_TAG, - // params![caste_id, tag_id, dyn_item_id, tag_position], - // ) - // .await?; - // } - // CasteTag::Pus { material, state } - // | CasteTag::Blood { material, state } - // | CasteTag::EggMaterial { material, state } => { - // let dyn_mat_id = get_or_insert_dynamic_material_in_state(conn, material, state).await?; - - // conn.execute( - // super::INSERT_MATERIAL_TAG, - // params![caste_id, tag_id, dyn_mat_id, tag_position], - // ) - // .await?; - // } - CasteTag::GobbleVerminCreature { - vermin_creature, - vermin_caste, - } => { - let creature_caste_tag_id = - get_or_insert_dynamic_creature_caste_tag(conn, vermin_creature, vermin_caste) - .await?; - - conn.execute( - super::INSERT_CREATURE_CASTE_TAG, - params![caste_id, tag_id, creature_caste_tag_id, tag_position], - ) - .await?; - } - CasteTag::Lair { lair, probability } => { - let ref_lair_id = get_ref_lair_token_id(conn, lair).await?; - - if let Some(id) = ref_lair_id { - conn.execute( - super::INSERT_LAIR_REF_TAG, - params![caste_id, tag_id, id, tag_position, probability], - ) - .await?; - } else { - return Err(format!("Unknown lair token encountered: '{lair}'").into()); - } - } - CasteTag::SkillRates { - improvement_rate, - decay_rate_unused, - decay_rate_rusty, - decay_rate_demotion, - } => { - let values = vec![ - TagValue::Int(i64::from(*improvement_rate)), - TagValue::Int(i64::from(*decay_rate_unused)), - TagValue::Int(i64::from(*decay_rate_rusty)), - TagValue::Int(i64::from(*decay_rate_demotion)), - ]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::SkillRustRates { - decay_rate_unused, - decay_rate_rusty, - decay_rate_demotion, - } => { - let values = vec![ - TagValue::Int(i64::from(*decay_rate_unused)), - TagValue::Int(i64::from(*decay_rate_rusty)), - TagValue::Int(i64::from(*decay_rate_demotion)), - ]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::SkillRustRate { - skill, - decay_rate_unused, - decay_rate_rusty, - decay_rate_demotion, - } => { - let values = vec![ - TagValue::String(skill.clone()), - TagValue::Int(i64::from(*decay_rate_unused)), - TagValue::Int(i64::from(*decay_rate_rusty)), - TagValue::Int(i64::from(*decay_rate_demotion)), - ]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::SkillRate { - skill, - improvement_rate, - decay_rate_unused, - decay_rate_rusty, - decay_rate_demotion, - } => { - let values = vec![ - TagValue::String(skill.clone()), - TagValue::Int(i64::from(*improvement_rate)), - TagValue::Int(i64::from(*decay_rate_unused)), - TagValue::Int(i64::from(*decay_rate_rusty)), - TagValue::Int(i64::from(*decay_rate_demotion)), - ]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::PhysicalAttributeRate { - attribute, - improvement_cost, - decay_rate_unused, - decay_rate_rusty, - decay_rate_demotion, - } - | CasteTag::MentalAttributeRate { - attribute, - improvement_cost, - decay_rate_unused, - decay_rate_rusty, - decay_rate_demotion, - } => { - let values = vec![ - TagValue::String(attribute.clone()), - TagValue::Int(i64::from(*improvement_cost)), - TagValue::Int(i64::from(*decay_rate_unused)), - TagValue::Int(i64::from(*decay_rate_rusty)), - TagValue::Int(i64::from(*decay_rate_demotion)), - ]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::Orientation { - caste, - disinterested_chance, - casual_chance, - strong_chance, - } => { - let values = vec![ - TagValue::String(caste.clone()), - TagValue::Int(i64::from(*disinterested_chance)), - TagValue::Int(i64::from(*casual_chance)), - TagValue::Int(i64::from(*strong_chance)), - ]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::Personality { - personality_trait, - low, - median, - high, - } => { - let values = vec![ - TagValue::String(personality_trait.clone()), - TagValue::Int(i64::from(*low)), - TagValue::Int(i64::from(*median)), - TagValue::Int(i64::from(*high)), - ]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - // CasteTag::PlusBodyPartGroup { - // body_part_selector, - // body_part_group, - // } => { - // let values = vec![ - // TagValue::String(body_part_selector.clone()), - // TagValue::String(body_part_group.clone()), - // ]; - // insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - // } - CasteTag::ProfessionName { - profession, - singular, - plural, - } => { - let dyn_name_id = - get_or_insert_dynamic_name(conn, singular.as_str(), plural.as_str(), None).await?; - - conn.execute( - super::INSERT_NAME_TAG, - params![caste_id, tag_id, dyn_name_id, tag_position], - ) - .await?; - - let caste_name_id = - get_caste_name_id_by_caste_id_and_tag_position(conn, caste_id, tag_position) - .await?; - - conn.execute( - super::INSERT_PROFESSION_NAME_TAG, - params![ - caste_id, - tag_id, - tag_position, - caste_name_id, - profession.as_str() - ], - ) - .await?; - } - CasteTag::Remains { singular, plural } - | CasteTag::BabyName { singular, plural } - | CasteTag::ChildName { singular, plural } => { - let dyn_name_id = - get_or_insert_dynamic_name(conn, singular.as_str(), plural.as_str(), None).await?; - - conn.execute( - super::INSERT_NAME_TAG, - params![caste_id, tag_id, dyn_name_id, tag_position], - ) - .await?; - } - CasteTag::Name { - singular, - plural, - adjective, - } => { - let dyn_name_id = - get_or_insert_dynamic_name(conn, singular, plural, Some(adjective)).await?; - - conn.execute( - super::INSERT_NAME_TAG, - params![caste_id, tag_id, dyn_name_id, tag_position], - ) - .await?; - } - // CasteTag::RelativeSize { - // body_part_selector, - // body_part, - // relative_size, - // } => { - // let values = vec![ - // TagValue::String(body_part_selector.clone()), - // TagValue::String(body_part.clone()), - // TagValue::Int(i64::from(*relative_size)), - // ]; - // insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - // } - CasteTag::RetractIntoBodyPart { - body_part_selector, - body_part, - second_person, - third_person, - second_person_cancel, - third_person_cancel, - } => { - let values = vec![ - TagValue::String(body_part_selector.clone()), - TagValue::String(body_part.clone()), - TagValue::String(second_person.clone()), - TagValue::String(third_person.clone()), - TagValue::String(second_person_cancel.clone()), - TagValue::String(third_person_cancel.clone()), - ]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - // CasteTag::RootAround { - // body_part_selector, - // body_part, - // second_person_verb, - // third_person_verb, - // } => { - // let values = vec![ - // TagValue::String(body_part_selector.clone()), - // TagValue::String(body_part.clone()), - // TagValue::String(second_person_verb.clone()), - // TagValue::String(third_person_verb.clone()), - // ]; - // insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - // } - // CasteTag::Secretion { - // material_token, - // material_state, - // body_part_selector, - // body_part, - // tissue_layer, - // trigger, - // } => { - // let dyn_mat_id = - // get_or_insert_dynamic_material_in_state(conn, material_token, material_state) - // .await?; - // let body_part_group_id = - // get_or_insert_body_part_group(conn, body_part_selector, body_part).await?; - // let trigger_id = get_ref_secretion_triggers_id(conn, trigger.as_str()).await?; - - // conn.execute( - // super::INSERT_SECRETION_TAG, - // params![ - // caste_id, - // tag_id, - // tag_position, - // dyn_mat_id, - // body_part_group_id, - // tissue_layer.as_str(), - // trigger_id - // ], - // ) - // .await?; - // } - CasteTag::SenseCreatureClass { - creature_class, - tile, - foreground, - background, - brightness, - } => { - let values = vec![ - TagValue::String(creature_class.clone()), - TagValue::String(tile.clone()), - TagValue::Int(i64::from(*foreground)), - TagValue::Int(i64::from(*background)), - TagValue::Int(i64::from(*brightness)), - ]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - // CasteTag::SetBodyPartGroup { - // body_part_selector, - // body_part, - // } => { - // let values = vec![ - // TagValue::String(body_part_selector.clone()), - // TagValue::String(body_part.clone()), - // ]; - // insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - // } - CasteTag::Sound { - sound_type, - sound_range, - sound_interval, - requires_breathing, - first_person, - third_person, - out_of_sight, - } => { - // store basic sound fields as values for now - let values = vec![ - TagValue::String(sound_type.clone()), - TagValue::Int(i64::from(*sound_range)), - TagValue::Int(i64::from(*sound_interval)), - TagValue::Bool(*requires_breathing), - TagValue::String(first_person.clone()), - TagValue::String(third_person.clone()), - TagValue::String(out_of_sight.clone()), - ]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - // CasteTag::TissueLayer { - // body_part_selector, - // body_part, - // tissue, - // location, - // } => { - // // store the arguments as values for now - // let values = vec![ - // TagValue::String(body_part_selector.clone()), - // TagValue::String(body_part.clone()), - // TagValue::String(tissue.clone()), - // TagValue::String(location.clone()), - // ]; - // insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - // } - CasteTag::TissueLayerUnder { - body_part_selector, - body_part, - tissue, - } => { - let values = vec![ - TagValue::String(body_part_selector.clone()), - TagValue::String(body_part.clone()), - TagValue::String(tissue.clone()), - ]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - // CasteTag::VerminBite { - // chance, - // verb, - // material, - // material_state, - // } => { - // // store simple parts; material handling remains more complex and is left to follow-up - // let values = vec![ - // TagValue::Int(i64::from(*chance)), - // TagValue::String(verb.clone()), - // TagValue::String(material.clone()), - // TagValue::String(material_state.clone()), - // ]; - // insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - // } - CasteTag::VisionArc { - binocular, - non_binocular, - } => { - let values = vec![ - TagValue::Int(i64::from(*binocular)), - TagValue::Int(i64::from(*non_binocular)), - ]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::SkillLearnRate { skill, rate } => { - let values = vec![ - TagValue::String(skill.clone()), - TagValue::Int(i64::from(*rate)), - ]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::BodySize { year, days, size } => { - let values = vec![ - TagValue::Int(i64::from(*year)), - TagValue::Int(i64::from(*days)), - TagValue::Int(i64::from(*size)), - ]; - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::BodyPartAppearanceModifier { quality, spread } => { - let mut values = vec![TagValue::String(quality.clone())]; - for s in spread { - values.push(TagValue::Int(i64::from(*s))); - } - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - // CasteTag::ApplyCreatureVariation { id, args } => { - // let mut values = vec![TagValue::String(id.clone())]; - // for a in args { - // values.push(TagValue::String(a.clone())); - // } - // insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - // } - CasteTag::ExtraButcherObject { - object_type, - arguments, - } => { - let mut values = vec![TagValue::String(object_type.clone())]; - for a in arguments { - values.push(TagValue::String(a.clone())); - } - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - CasteTag::GlowColor { - foreground, - background, - brightness, - } - | CasteTag::Color { - foreground, - background, - brightness, - } => { - let fg = i64::from(*foreground); - let bg = i64::from(*background); - let bright = i64::from(*brightness); - - let color_id = get_or_insert_color(conn, fg, bg, bright).await?; - - conn.execute( - super::INSERT_COLOR_TAG, - params![caste_id, tag_id, color_id, tag_position], - ) - .await?; - } - // CasteTag::InteractionDetail { args } => { - // let mut values: Vec = Vec::new(); - // for a in args { - // values.push(TagValue::String(a.clone())); - // } - // insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - // } - CasteTag::Body { body_parts } => { - // Body parts are complex structures; store tokens for now - let mut values: Vec = Vec::new(); - for bp in body_parts { - // `body_parts` is a Vec; store the string values directly - values.push(TagValue::String(bp.clone())); - } - if !values.is_empty() { - insert_value_tag(conn, caste_id, tag_id, tag_position, &values).await?; - } - } - _ => { - tracing::error!("Insert not implemented for: {tag:?}"); - } - } - - Ok(()) -} diff --git a/sqlite_lib/src/queries/caste/insert_sql.rs b/sqlite_lib/src/queries/caste/insert_sql.rs deleted file mode 100644 index 9b6b6698..00000000 --- a/sqlite_lib/src/queries/caste/insert_sql.rs +++ /dev/null @@ -1,241 +0,0 @@ -//! Insert SQL queries for the caste tables. -//! -//! These constants contain parameterized `INSERT` statements used by the -//! sqlite backend when persisting parsed caste data. Each constant documents -//! the ordered parameters (by placeholder position) expected when executing -//! the statement. - -/// Identity insertion SQL for the `castes` table. -/// -/// # Parameters (ordered) -/// -/// 1. `creature_id` - FK to `creatures.id`: the parent creature this caste belongs to. -/// 2. `identifier` - the caste's identifier string (e.g., "MALE", "FEMALE", "DEFAULT"). -pub const INSERT_IDENTITY: &str = r" - INSERT INTO castes (creature_id, identifier) - VALUES (?1, ?2)"; - -/// Insert SQL for the `caste_tags` table. -/// -/// # Parameters (ordered) -/// -/// 1. `caste_id` - FK to `castes.id`. -/// 2. `tag_id` - FK to `ref_caste_token_tags.id`. -/// 3. `tag_position` - index/position of this tag within the caste. -pub const INSERT_TAG: &str = r" - INSERT INTO caste_tags (caste_id, tag_id, tag_position) - VALUES (?1, ?2, ?3)"; - -/// Insert SQL for the `caste_value_flags` table. -/// -/// This table stores flag values that can be represented as a mix of a boolean, -/// up to seven strings, and up to seven integers. -/// -/// # Parameters (ordered) -/// -/// 1. `caste_id` - FK to `castes.id`. -/// 2. `tag_id` - FK to `ref_caste_token_tags.id`. -/// 3. `tag_position` - index/position of this tag within the caste. -/// 4. `value_bit` - boolean / bit value (stored as integer). -/// 5. `value_string1` -/// 6. `value_string2` -/// 7. `value_string3` -/// 8. `value_string4` -/// 9. `value_string5` -/// 10. `value_string6` -/// 11. `value_string7` -/// 12. `value_int1` -/// 13. `value_int2` -/// 14. `value_int3` -/// 15. `value_int4` -/// 16. `value_int5` -/// 17. `value_int6` -/// 18. `value_int7` -pub const INSERT_VALUE_TAG: &str = r" - INSERT INTO caste_value_flags ( - caste_id, tag_id, tag_position, - value_bit, - value_string1, value_string2, value_string3, value_string4, value_string5, value_string6, value_string7, - value_int1, value_int2, value_int3, value_int4, value_int5, value_int6, value_int7 - ) - VALUES ( - ?1, ?2, ?3, - ?4, - ?5, ?6, ?7, ?8, ?9, ?10, ?11, - ?12, ?13, ?14, ?15, ?16, ?17, ?18 - )"; - -/// Insert SQL for the `caste_attacks` table. -/// -/// # Parameters (ordered) -/// -/// 1. `caste_id` - FK to `castes.id`. -/// 2. `tag_position` - index/position of this attack tag. -/// 3. `name` - attack name (e.g., "BITE"). -/// 4. `body_part` - body part token (e.g., "`BY_TOKEN:MOUTH`"). -pub const INSERT_ATTACK_TAG: &str = r" - INSERT INTO caste_attacks (caste_id, tag_position, name, body_part) - VALUES (?1, ?2, ?3, ?4)"; - -/// Insert SQL for the `caste_attack_triggers` table. -/// -/// # Parameters (ordered) -/// -/// 1. `caste_id` - FK to `castes.id`. -/// 2. `tag_position` - index/position of this trigger tag. -/// 3. `population` - integer value (default 0). -/// 4. `exported_wealth` - integer value (default 0). -/// 5. `created_wealth` - integer value (default 0). -pub const INSERT_ATTACK_TRIGGER_TAG: &str = r" - INSERT INTO caste_attack_triggers (caste_id, tag_position, population, exported_wealth, created_wealth) - VALUES (?1, ?2, ?3, ?4, ?5)"; - -/// Insert SQL for the `caste_blood` table. -/// -/// # Parameters (ordered) -/// -/// 1. `caste_id` - FK to `castes.id`. -/// 2. `tag_position` - index/position of this blood tag. -/// 3. `material` - dynamic material identifier. -/// 4. `state` - material state string (e.g., "liquid"). -pub const INSERT_BLOOD_TAG: &str = r" - INSERT INTO caste_blood (caste_id, tag_position, material, state) - VALUES (?1, ?2, ?3, ?4)"; - -/// Insert SQL for the `caste_body_detail_plans` table (identity row). -/// -/// # Parameters (ordered) -/// -/// 1. `caste_id` - FK to `castes.id`. -/// 2. `tag_position` - index/position of this body detail plan tag. -/// 3. `name` - plan name. -pub const INSERT_BODY_DETAIL_PLAN_IDENTITY_TAG: &str = r" - INSERT INTO caste_body_detail_plans (caste_id, tag_position, name) - VALUES (?1, ?2, ?3)"; - -/// Insert SQL for the `caste_body_detail_plan_args` table (plan arguments). -/// -/// # Parameters (ordered) -/// -/// 1. `body_detail_plan_id` - FK to `caste_body_detail_plans.id`. -/// 2. `argument_index` - position of the argument within the plan. -/// 3. `argument` - argument text. -pub const INSERT_BODY_DETAIL_PLAN_ARGUMENT_TAG: &str = r" - INSERT INTO caste_body_detail_plan_args (body_detail_plan_id, argument_index, argument) - VALUES (?1, ?2, ?3)"; - -/// Insert SQL for the `caste_color_tags` table. -/// -/// # Parameters (ordered) -/// -/// 1. `caste_id` - FK to `castes.id`. -/// 2. `tag_id` - FK to `ref_caste_token_tags.id`. -/// 3. `color_id` - FK to `colors.id`. -/// 4. `tag_position` - index/position of this tag. -pub const INSERT_COLOR_TAG: &str = r" - INSERT INTO caste_color_tags (caste_id, tag_id, color_id, tag_position) - VALUES (?1, ?2, ?3, ?4)"; - -/// Insert SQL for the `caste_item_tags` table. -/// -/// # Parameters (ordered) -/// -/// 1. `caste_id` - FK to `castes.id`. -/// 2. `tag_id` - FK to `ref_caste_token_tags.id`. -/// 3. `tag_position` - index/position of this tag. -/// 4. `dyn_item_id` - FK to `dyn_items_of_material.id`. -pub const INSERT_ITEM_TAG: &str = r" - INSERT INTO caste_item_tags (caste_id, tag_id, tag_position, dyn_item_id) - VALUES (?1, ?2, ?3, ?4)"; - -/// Insert SQL for the `caste_material_tags` table. -/// -/// # Parameters (ordered) -/// -/// 1. `caste_id` - FK to `castes.id`. -/// 2. `tag_id` - FK to `ref_caste_token_tags.id`. -/// 3. `tag_position` - index/position of this tag. -/// 4. `dyn_material_id` - FK to `dyn_materials_in_state.id`. -pub const INSERT_MATERIAL_TAG: &str = r" - INSERT INTO caste_material_tags (caste_id, tag_id, tag_position, dyn_material_id) - VALUES (?1, ?2, ?3, ?4)"; - -/// Insert SQL for the `caste_creature_caste_tags` table. -/// -/// # Parameters (ordered) -/// -/// 1. `caste_id` - FK to `castes.id`. -/// 2. `tag_id` - FK to `ref_caste_token_tags.id`. -/// 3. `dyn_creature_caste_id` - FK to `dyn_creature_caste_tags.id`. -/// 4. `tag_position` - index/position of this tag. -pub const INSERT_CREATURE_CASTE_TAG: &str = r" - INSERT INTO caste_creature_caste_tags (caste_id, tag_id, dyn_creature_caste_id, tag_position) - VALUES (?1, ?2, ?3, ?4)"; - -/// Insert SQL for the `caste_lairs` table (lair references). -/// -/// # Parameters (ordered) -/// -/// 1. `caste_id` - FK to `castes.id`. -/// 2. `tag_id` - FK to `ref_caste_token_tags.id`. -/// 3. `tag_position` - index/position of this tag. -/// 4. `lair_id` - FK to `ref_lair_token_flags.id` (the lair token). -/// 5. `probability` - integer probability value. -pub const INSERT_LAIR_REF_TAG: &str = r" - INSERT INTO caste_lair_tags (caste_id, tag_id, tag_position, lair_id, probability) - VALUES (?1, ?2, ?3, ?4, ?5)"; - -/// Insert SQL for the `caste_names` table. -/// -/// # Parameters (ordered) -/// -/// 1. `caste_id` - FK to `castes.id`. -/// 2. `tag_id` - FK to `ref_caste_token_tags.id`. -/// 3. `tag_position` - index/position of this tag. -/// 4. `name_id` - FK to `dyn_names.id`. -pub const INSERT_NAME_TAG: &str = r" - INSERT INTO caste_names (caste_id, tag_id, tag_position, name_id) - VALUES (?1, ?2, ?3, ?4)"; - -/// Insert SQL for the `caste_profession_names` table. -/// -/// # Parameters (ordered) -/// -/// 1. `caste_id` - FK to `castes.id`. -/// 2. `tag_id` - FK to `ref_caste_token_tags.id`. -/// 3. `tag_position` - index/position of this tag. -/// 4. `caste_name_id` - FK to `caste_names.id`. -/// 5. `profession_identifier` - profession string identifier. -pub const INSERT_PROFESSION_NAME_TAG: &str = r" - INSERT INTO caste_profession_names (caste_id, tag_id, tag_position, caste_name_id, profession_identifier) - VALUES (?1, ?2, ?3, ?4, ?5)"; - -/// Insert SQL for the `caste_secretions` table. -/// -/// # Parameters (ordered) -/// -/// 1. `caste_id` - FK to `castes.id`. -/// 2. `tag_id` - FK to `ref_caste_token_tags.id`. -/// 3. `tag_position` - index/position of this tag. -/// 4. `dyn_material_id` - FK to `dyn_materials_in_state.id`. -/// 5. `dyn_body_part_group_id` - FK to `dyn_body_part_groups.id`. -/// 6. `tissue_layer` - string identifying the tissue layer. -/// 7. `secretion_trigger_id` - FK to `ref_secretion_triggers.id`. -pub const INSERT_SECRETION_TAG: &str = r" - INSERT INTO caste_secretions (caste_id, tag_id, tag_position, dyn_material_id, - dyn_body_part_group_id, tissue_layer, secretion_trigger_id) - VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"; - -/// Insert SQL for the `caste_specific_foods` table. -/// -/// # Parameters (ordered) -/// -/// 1. `caste_id` - FK to `castes.id`. -/// 2. `tag_id` - FK to `ref_caste_token_tags.id`. -/// 3. `tag_position` - index/position of this tag. -/// 4. `ref_object_type_id` - FK to `ref_object_types.id` (object type of the food). -/// 5. `object_identifier` - identifier string of the object. -pub const INSERT_SPECIFIC_FOOD_TAG: &str = r" - INSERT INTO caste_specific_foods (caste_id, tag_id, tag_position, - ref_object_type_id, object_identifier) - VALUES (?1, ?2, ?3, ?4, ?5)"; diff --git a/sqlite_lib/src/queries/caste/mod.rs b/sqlite_lib/src/queries/caste/mod.rs deleted file mode 100644 index 333909e2..00000000 --- a/sqlite_lib/src/queries/caste/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! Queries for interacting with `[CASTE]` objects - -pub mod get; -mod get_sql; -pub mod insert; -mod insert_sql; -mod value_tag_helper; - -pub use get_sql::*; -pub use insert_sql::*; -pub use value_tag_helper::*; diff --git a/sqlite_lib/src/queries/caste/value_tag_helper.rs b/sqlite_lib/src/queries/caste/value_tag_helper.rs deleted file mode 100644 index 289ea9d3..00000000 --- a/sqlite_lib/src/queries/caste/value_tag_helper.rs +++ /dev/null @@ -1,83 +0,0 @@ -use turso::{Connection, params}; - -/// Reprsents a value on a caste tag -#[derive(Debug, Clone)] -pub enum TagValue { - /// A boolean value - Bool(bool), - /// An integer value - Int(i64), - /// A string value - String(String), -} - -/// insert a caste value tag -/// -/// # Errors -/// -/// Passes along database errors -pub async fn insert_value_tag( - conn: &Connection, - caste_id: i64, - flag_id: i64, - position: i64, - values: &[TagValue], // Accepts mixed slice: [Int(1), String("A"), Int(2)] -) -> Result<(), Box> { - let mut bit_val: Option = None; - let mut str_vals: Vec> = vec![None; 7]; - let mut int_vals: Vec> = vec![None; 7]; - - let mut str_idx = 0; - let mut int_idx = 0; - - for v in values { - match v { - // right now only supports one boolean per tag - TagValue::Bool(b) => bit_val = Some(*b), - TagValue::String(s) => { - if str_idx < 7 { - str_vals[str_idx] = Some(s.clone()); - str_idx += 1; - } - } - TagValue::Int(i) => { - if int_idx < 7 { - int_vals[int_idx] = Some(*i); - int_idx += 1; - } - } - } - } - - // 4. Execute the Single Wide Insert - // We unroll our vectors into the params! macro. - conn.execute( - super::INSERT_VALUE_TAG, - params![ - caste_id, - flag_id, - position, - // The boolean - bit_val, - // The 7 strings (Some("val") or None) - str_vals[0].as_deref(), - str_vals[1].as_deref(), - str_vals[2].as_deref(), - str_vals[3].as_deref(), - str_vals[4].as_deref(), - str_vals[5].as_deref(), - str_vals[6].as_deref(), - // The 7 integers (Some(1) or None) - int_vals[0], - int_vals[1], - int_vals[2], - int_vals[3], - int_vals[4], - int_vals[5], - int_vals[6], - ], - ) - .await?; - - Ok(()) -} diff --git a/sqlite_lib/src/queries/color/get.rs b/sqlite_lib/src/queries/color/get.rs deleted file mode 100644 index 742c8333..00000000 --- a/sqlite_lib/src/queries/color/get.rs +++ /dev/null @@ -1,65 +0,0 @@ -//! selectsert helper functions for color - -use turso::{Connection, params}; - -/// Get the id of a specific color, inserting it if it doesn't exist -/// -/// # Errors -/// -/// Passes database errors up -/// -/// # Returns -/// -/// The index of the color -pub async fn get_or_insert_color( - conn: &Connection, - foreground: i64, - background: i64, - brightness: i64, -) -> Result> { - // try to get the existing color - if let Some(id) = get_color(conn, foreground, background, brightness).await? { - return Ok(id); - } - // insert it since it doesn't exist - conn.execute( - super::INSERT_COLOR, - params![foreground, background, brightness], - ) - .await?; - // try to get the existing color or return an error - (get_color(conn, foreground, background, brightness).await?) - .map_or_else(|| Err("Failed to insert color and get it back".into()), Ok) -} - -/// Get an id for a color by values -/// -/// # Errors -/// -/// Will pass any database errors along -/// -/// # Returns -/// -/// None if there is no color matching provided values. -pub async fn get_color( - conn: &Connection, - foreground: i64, - background: i64, - brightness: i64, -) -> Result, Box> { - // see if color exists - let mut id_rows = conn - .query( - super::GET_COLOR_ID_BY_VALUES, - params![foreground, background, brightness], - ) - .await?; - - match id_rows.next().await? { - Some(row) => { - let id: i64 = row.get(0)?; - Ok(Some(id)) - } - None => Ok(None), // Return None if no row exists - } -} diff --git a/sqlite_lib/src/queries/color/get_sql.rs b/sqlite_lib/src/queries/color/get_sql.rs deleted file mode 100644 index c212c0f2..00000000 --- a/sqlite_lib/src/queries/color/get_sql.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! SQL Querys to GET from the `colors` table - -/// Get a `colors` table id by its value triplet. -/// -/// Expects parameters: -/// -/// 1. `foreground` - foreground color identifier/value (bound to ?1) -/// 2. `background` - background color identifier/value (bound to ?2) -/// 3. `brightness` - brightness value (bound to ?3) -pub const GET_COLOR_ID_BY_VALUES: &str = r" -SELECT id FROM colors -WHERE foreground = ?1 - AND background = ?2 - AND brightness = ?3;"; diff --git a/sqlite_lib/src/queries/color/insert_sql.rs b/sqlite_lib/src/queries/color/insert_sql.rs deleted file mode 100644 index 1c1478d3..00000000 --- a/sqlite_lib/src/queries/color/insert_sql.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! SQL Querys to INSERT from the `colors` table - -/// Insert SQL for the `colors` table. -/// -/// Expects parameters: -/// -/// 1. `foreground` - foreground color identifier/value (bound to ?1) -/// 2. `background` - background color identifier/value (bound to ?2) -/// 3. `brightness` - brightness value (bound to ?3) -pub const INSERT_COLOR: &str = r" -INSERT INTO colors (foreground, background, brightness) -VALUES (?1, ?2, ?3);"; diff --git a/sqlite_lib/src/queries/color/mod.rs b/sqlite_lib/src/queries/color/mod.rs deleted file mode 100644 index 77949e59..00000000 --- a/sqlite_lib/src/queries/color/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -//! SQL and helper methods for color tables. - -pub mod get; -mod get_sql; -mod insert_sql; - -pub use get_sql::*; -pub use insert_sql::*; diff --git a/sqlite_lib/src/queries/misc/get/body_part_group.rs b/sqlite_lib/src/queries/misc/get/body_part_group.rs deleted file mode 100644 index f7d15672..00000000 --- a/sqlite_lib/src/queries/misc/get/body_part_group.rs +++ /dev/null @@ -1,64 +0,0 @@ -use turso::{Connection, params}; - -use crate::queries::misc::{GET_BODY_PART_GROUP_ID, INSERT_BODY_PART_GROUP}; - -/// Get the id of a specific `dynamic_body_part_group`, inserting it if it doesn't exist -/// -/// # Errors -/// -/// Passes database errors up -/// -/// # Returns -/// -/// The index of the `body_part_group` -pub async fn get_or_insert_body_part_group( - conn: &Connection, - body_part_selector: &str, - body_part: &str, -) -> Result> { - if let Some(id) = get_body_part_group(conn, body_part_selector, body_part).await? { - return Ok(id); - } - // insert it since it doesn't exist - conn.execute( - INSERT_BODY_PART_GROUP, - params![body_part_selector, body_part], - ) - .await?; - // try to get the existing color or return an error - (get_body_part_group(conn, body_part_selector, body_part).await?).map_or_else( - || Err("Failed to insert material and get it back".into()), - Ok, - ) -} - -/// Get an id for a `dynamic_body_part_group` by values -/// -/// # Errors -/// -/// Will pass any database errors along -/// -/// # Returns -/// -/// None if there is no `dynamic_body_part_group` matching provided values. -pub async fn get_body_part_group( - conn: &Connection, - body_part_selector: &str, - body_part: &str, -) -> Result, Box> { - // see if dynamic_body_part_group exists - let mut id_rows = conn - .query( - GET_BODY_PART_GROUP_ID, - params![body_part_selector, body_part], - ) - .await?; - - match id_rows.next().await? { - Some(row) => { - let id: i64 = row.get(0)?; - Ok(Some(id)) - } - None => Ok(None), // Return None if no row exists - } -} diff --git a/sqlite_lib/src/queries/misc/get/creature_caste copy.rs b/sqlite_lib/src/queries/misc/get/creature_caste copy.rs deleted file mode 100644 index 0ca72d32..00000000 --- a/sqlite_lib/src/queries/misc/get/creature_caste copy.rs +++ /dev/null @@ -1,68 +0,0 @@ -use turso::{Connection, params}; - -use super::super::{GET_CREATURE_CASTE_TAG_ID, INSERT_CREATURE_CASTE_TAG_REFERENCE}; - -/// Get the id of a specific `dynamic_creature_caste_tag`, inserting it if it doesn't exist -/// -/// # Errors -/// -/// Passes database errors up -/// -/// # Returns -/// -/// The index of the `dynamic_creature_caste_tag` -pub async fn get_or_insert_dynamic_creature_caste_tag( - conn: &Connection, - creature_identifier: &str, - caste_identifier: &str, -) -> Result> { - // try to get the existing color - if let Some(id) = - get_dynamic_creature_caste_tag(conn, creature_identifier, caste_identifier).await? - { - return Ok(id); - } - // insert it since it doesn't exist - conn.execute( - INSERT_CREATURE_CASTE_TAG_REFERENCE, - params![creature_identifier, caste_identifier], - ) - .await?; - // try to get the existing color or return an error - (get_dynamic_creature_caste_tag(conn, creature_identifier, caste_identifier).await?) - .map_or_else( - || Err("Failed to insert material and get it back".into()), - Ok, - ) -} - -/// Get an id for a `dynamic_creature_caste_tag` by values -/// -/// # Errors -/// -/// Will pass any database errors along -/// -/// # Returns -/// -/// None if there is no `dynamic_creature_caste_tag` matching provided values. -pub async fn get_dynamic_creature_caste_tag( - conn: &Connection, - creature_identifier: &str, - caste_identifier: &str, -) -> Result, Box> { - // see if dynamic_creature_caste_tag exists - let mut id_rows = conn - .query( - GET_CREATURE_CASTE_TAG_ID, - params![creature_identifier, caste_identifier], - ) - .await?; - - match id_rows.next().await? { - Some(row) => { - let id: i64 = row.get(0)?; - Ok(Some(id)) - } - None => Ok(None), // Return None if no row exists - } -} diff --git a/sqlite_lib/src/queries/misc/get/creature_caste.rs b/sqlite_lib/src/queries/misc/get/creature_caste.rs deleted file mode 100644 index 0ca72d32..00000000 --- a/sqlite_lib/src/queries/misc/get/creature_caste.rs +++ /dev/null @@ -1,68 +0,0 @@ -use turso::{Connection, params}; - -use super::super::{GET_CREATURE_CASTE_TAG_ID, INSERT_CREATURE_CASTE_TAG_REFERENCE}; - -/// Get the id of a specific `dynamic_creature_caste_tag`, inserting it if it doesn't exist -/// -/// # Errors -/// -/// Passes database errors up -/// -/// # Returns -/// -/// The index of the `dynamic_creature_caste_tag` -pub async fn get_or_insert_dynamic_creature_caste_tag( - conn: &Connection, - creature_identifier: &str, - caste_identifier: &str, -) -> Result> { - // try to get the existing color - if let Some(id) = - get_dynamic_creature_caste_tag(conn, creature_identifier, caste_identifier).await? - { - return Ok(id); - } - // insert it since it doesn't exist - conn.execute( - INSERT_CREATURE_CASTE_TAG_REFERENCE, - params![creature_identifier, caste_identifier], - ) - .await?; - // try to get the existing color or return an error - (get_dynamic_creature_caste_tag(conn, creature_identifier, caste_identifier).await?) - .map_or_else( - || Err("Failed to insert material and get it back".into()), - Ok, - ) -} - -/// Get an id for a `dynamic_creature_caste_tag` by values -/// -/// # Errors -/// -/// Will pass any database errors along -/// -/// # Returns -/// -/// None if there is no `dynamic_creature_caste_tag` matching provided values. -pub async fn get_dynamic_creature_caste_tag( - conn: &Connection, - creature_identifier: &str, - caste_identifier: &str, -) -> Result, Box> { - // see if dynamic_creature_caste_tag exists - let mut id_rows = conn - .query( - GET_CREATURE_CASTE_TAG_ID, - params![creature_identifier, caste_identifier], - ) - .await?; - - match id_rows.next().await? { - Some(row) => { - let id: i64 = row.get(0)?; - Ok(Some(id)) - } - None => Ok(None), // Return None if no row exists - } -} diff --git a/sqlite_lib/src/queries/misc/get/item_material.rs b/sqlite_lib/src/queries/misc/get/item_material.rs deleted file mode 100644 index 36fe3d32..00000000 --- a/sqlite_lib/src/queries/misc/get/item_material.rs +++ /dev/null @@ -1,65 +0,0 @@ -use turso::{Connection, params}; - -use super::super::{GET_DYNAMIC_ITEM_OF_MATERIAL_ID, INSERT_DYNAMIC_ITEM_OF_MATERIAL}; - -/// Get the id of a specific `dynamic_item_of_material`, inserting it if it doesn't exist -/// -/// # Errors -/// -/// Passes database errors up -/// -/// # Returns -/// -/// The index of the `dynamic_item_of_material` -pub async fn get_or_insert_dynamic_item_of_material( - conn: &Connection, - item_identifier: &str, - material_identifier: &str, -) -> Result> { - // try to get the existing color - if let Some(id) = - get_dynamic_item_of_material(conn, item_identifier, material_identifier).await? - { - return Ok(id); - } - // insert it since it doesn't exist - conn.execute( - INSERT_DYNAMIC_ITEM_OF_MATERIAL, - params![item_identifier, material_identifier], - ) - .await?; - // try to get the existing color or return an error - (get_dynamic_item_of_material(conn, item_identifier, material_identifier).await?) - .map_or_else(|| Err("Failed to insert item and get it back".into()), Ok) -} - -/// Get an id for a `dynamic_item_of_material` by values -/// -/// # Errors -/// -/// Will pass any database errors along -/// -/// # Returns -/// -/// None if there is no `dynamic_item_of_material` matching provided values. -pub async fn get_dynamic_item_of_material( - conn: &Connection, - item_identifier: &str, - material_identifier: &str, -) -> Result, Box> { - // see if dynamic_item_of_material exists - let mut id_rows = conn - .query( - GET_DYNAMIC_ITEM_OF_MATERIAL_ID, - params![item_identifier, material_identifier], - ) - .await?; - - match id_rows.next().await? { - Some(row) => { - let id: i64 = row.get(0)?; - Ok(Some(id)) - } - None => Ok(None), // Return None if no row exists - } -} diff --git a/sqlite_lib/src/queries/misc/get/material_state.rs b/sqlite_lib/src/queries/misc/get/material_state.rs deleted file mode 100644 index b94495d0..00000000 --- a/sqlite_lib/src/queries/misc/get/material_state.rs +++ /dev/null @@ -1,65 +0,0 @@ -use turso::{Connection, params}; - -use super::super::{GET_DYNAMIC_MATERIAL_IN_STATE_ID, INSERT_DYNAMIC_MATERIAL_IN_STATE}; - -/// Get the id of a specific `dynamic_material_in_state`, inserting it if it doesn't exist -/// -/// # Errors -/// -/// Passes database errors up -/// -/// # Returns -/// -/// The index of the `dynamic_material_in_state` -pub async fn get_or_insert_dynamic_material_in_state( - conn: &Connection, - material_identifier: &str, - state: &str, -) -> Result> { - // try to get the existing color - if let Some(id) = get_dynamic_material_in_state(conn, material_identifier, state).await? { - return Ok(id); - } - // insert it since it doesn't exist - conn.execute( - INSERT_DYNAMIC_MATERIAL_IN_STATE, - params![material_identifier, state], - ) - .await?; - // try to get the existing color or return an error - (get_dynamic_material_in_state(conn, material_identifier, state).await?).map_or_else( - || Err("Failed to insert material and get it back".into()), - Ok, - ) -} - -/// Get an id for a `dynamic_material_in_state` by values -/// -/// # Errors -/// -/// Will pass any database errors along -/// -/// # Returns -/// -/// None if there is no `dynamic_material_in_state` matching provided values. -pub async fn get_dynamic_material_in_state( - conn: &Connection, - material_identifier: &str, - state: &str, -) -> Result, Box> { - // see if dynamic_material_in_state exists - let mut id_rows = conn - .query( - GET_DYNAMIC_MATERIAL_IN_STATE_ID, - params![material_identifier, state], - ) - .await?; - - match id_rows.next().await? { - Some(row) => { - let id: i64 = row.get(0)?; - Ok(Some(id)) - } - None => Ok(None), // Return None if no row exists - } -} diff --git a/sqlite_lib/src/queries/misc/get/mod.rs b/sqlite_lib/src/queries/misc/get/mod.rs deleted file mode 100644 index 8a083bd1..00000000 --- a/sqlite_lib/src/queries/misc/get/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! Helper methods for misc queries. - -mod body_part_group; -mod creature_caste; -mod item_material; -mod material_state; -mod name; -mod ref_lair; -mod ref_object_type; -mod secretion_trigger; - -pub use body_part_group::*; -pub use creature_caste::*; -pub use item_material::*; -pub use material_state::*; -pub use name::*; -pub use ref_lair::*; -pub use ref_object_type::*; -pub use secretion_trigger::*; diff --git a/sqlite_lib/src/queries/misc/get/name.rs b/sqlite_lib/src/queries/misc/get/name.rs deleted file mode 100644 index 82faf044..00000000 --- a/sqlite_lib/src/queries/misc/get/name.rs +++ /dev/null @@ -1,82 +0,0 @@ -use turso::{Connection, params}; - -use crate::queries::misc::{ - GET_NAME_ID_BY_SINGULAR_PLURAL, GET_NAME_ID_BY_SINGULAR_PLURAL_ADJECTIVE, INSERT_DYNAMIC_NAME, - INSERT_DYNAMIC_NAME_WITH_ADJECTIVE, -}; - -/// Get the id of a specific `dynamic_name`, inserting it if it doesn't exist -/// -/// # Errors -/// -/// Passes database errors up -/// -/// # Returns -/// -/// The index of the `dynamic_name` -pub async fn get_or_insert_dynamic_name( - conn: &Connection, - name_singular: &str, - name_plural: &str, - name_adjective: Option<&str>, -) -> Result> { - // try to get the existing name - if let Some(id) = get_dynamic_name(conn, name_singular, name_plural, name_adjective).await? { - return Ok(id); - } - // insert it since it doesn't exist - - if let Some(adjective_name) = name_adjective { - conn.execute( - INSERT_DYNAMIC_NAME_WITH_ADJECTIVE, - params![name_singular, name_plural, adjective_name], - ) - .await?; - } else { - conn.execute(INSERT_DYNAMIC_NAME, params![name_singular, name_plural]) - .await?; - } - - // try to get the existing color or return an error - (get_dynamic_name(conn, name_singular, name_plural, name_adjective).await?) - .map_or_else(|| Err("Failed to insert name and get it back".into()), Ok) -} - -/// Get an id for a `dynamic_name` by values -/// -/// # Errors -/// -/// Will pass any database errors along -/// -/// # Returns -/// -/// None if there is no `dynamic_name` matching provided values. -pub async fn get_dynamic_name( - conn: &Connection, - name_singular: &str, - name_plural: &str, - name_adjective: Option<&str>, -) -> Result, Box> { - // see if dynamic_name exists - let mut id_rows = if let Some(adjective_name) = name_adjective { - conn.query( - GET_NAME_ID_BY_SINGULAR_PLURAL_ADJECTIVE, - params![name_singular, name_plural, adjective_name], - ) - .await? - } else { - conn.query( - GET_NAME_ID_BY_SINGULAR_PLURAL, - params![name_singular, name_plural], - ) - .await? - }; - - match id_rows.next().await? { - Some(row) => { - let id: i64 = row.get(0)?; - Ok(Some(id)) - } - None => Ok(None), // Return None if no row exists - } -} diff --git a/sqlite_lib/src/queries/misc/get/ref_lair.rs b/sqlite_lib/src/queries/misc/get/ref_lair.rs deleted file mode 100644 index 63cfd2f4..00000000 --- a/sqlite_lib/src/queries/misc/get/ref_lair.rs +++ /dev/null @@ -1,28 +0,0 @@ -use turso::{Connection, params}; - -use super::super::GET_REF_LAIR_TAG_ID; - -/// Get an id for a `ref_lair_token_flags` by values -/// -/// # Errors -/// -/// Will pass any database errors along -/// -/// # Returns -/// -/// None if there is no `ref_lair_token_flags` matching provided values. -pub async fn get_ref_lair_token_id( - conn: &Connection, - token: &str, -) -> Result, Box> { - // see if ref_lair_token_flags exists - let mut id_rows = conn.query(GET_REF_LAIR_TAG_ID, params![token]).await?; - - match id_rows.next().await? { - Some(row) => { - let id: i64 = row.get(0)?; - Ok(Some(id)) - } - None => Ok(None), // Return None if no row exists - } -} diff --git a/sqlite_lib/src/queries/misc/get/ref_object_type.rs b/sqlite_lib/src/queries/misc/get/ref_object_type.rs deleted file mode 100644 index 7efaf038..00000000 --- a/sqlite_lib/src/queries/misc/get/ref_object_type.rs +++ /dev/null @@ -1,28 +0,0 @@ -use turso::{Connection, params}; - -use super::super::GET_REF_OBJECT_TYPE; - -/// Get an id for a `ref_object_types` by values -/// -/// # Errors -/// -/// Will pass any database errors along -/// -/// # Returns -/// -/// None if there is no `ref_object_types` matching provided values. -pub async fn get_ref_object_type_id( - conn: &Connection, - token: &str, -) -> Result, Box> { - // see if ref_object_types exists - let mut id_rows = conn.query(GET_REF_OBJECT_TYPE, params![token]).await?; - - match id_rows.next().await? { - Some(row) => { - let id: i64 = row.get(0)?; - Ok(Some(id)) - } - None => Ok(None), // Return None if no row exists - } -} diff --git a/sqlite_lib/src/queries/misc/get/secretion_trigger.rs b/sqlite_lib/src/queries/misc/get/secretion_trigger.rs deleted file mode 100644 index d509c62d..00000000 --- a/sqlite_lib/src/queries/misc/get/secretion_trigger.rs +++ /dev/null @@ -1,28 +0,0 @@ -use turso::{Connection, params}; - -use crate::queries::misc::GET_REF_SECRETION_TAG_ID; - -/// Get an id for a `ref_secretion_triggers_flags` by values -/// -/// # Errors -/// -/// Will pass any database errors along -/// -/// # Returns -/// -/// None if there is no `ref_secretion_triggers_flags` matching provided values. -pub async fn get_ref_secretion_triggers_id( - conn: &Connection, - token: &str, -) -> Result, Box> { - // see if ref_secretion_triggers_flags exists - let mut id_rows = conn.query(GET_REF_SECRETION_TAG_ID, params![token]).await?; - - match id_rows.next().await? { - Some(row) => { - let id: i64 = row.get(0)?; - Ok(Some(id)) - } - None => Ok(None), // Return None if no row exists - } -} diff --git a/sqlite_lib/src/queries/misc/get_sql.rs b/sqlite_lib/src/queries/misc/get_sql.rs deleted file mode 100644 index 729acea9..00000000 --- a/sqlite_lib/src/queries/misc/get_sql.rs +++ /dev/null @@ -1,92 +0,0 @@ -//! Get SQL Commands for misc queries. - -/// Selects the `id` from `dyn_items_of_material` for a given item and material. -/// -/// Expects parameters: -/// -/// 1. `item_identifier` - bound to ?1 -/// 2. `material_identifier` - bound to ?2 -pub const GET_DYNAMIC_ITEM_OF_MATERIAL_ID: &str = r" -SELECT id FROM dyn_items_of_material -WHERE item_identifier = ?1 AND material_identifier = ?2;"; - -/// Selects the `id` from `dyn_items_of_material` for a given material in a state. -/// -/// Expects parameters: -/// -/// 1. `material_identifier` - bound to ?1 -/// 2. `state` - bound to ?2 -pub const GET_DYNAMIC_MATERIAL_IN_STATE_ID: &str = r" -SELECT id FROM dyn_items_of_material -WHERE material_identifier = ?1 AND state = ?2;"; - -/// Selects the `id` from `dyn_creature_caste_tags` for a creature/caste pair. -/// -/// Expects parameters: -/// -/// 1. `creature_identifier` - bound to ?1 -/// 2. `caste_identifier` - bound to ?2 -pub const GET_CREATURE_CASTE_TAG_ID: &str = r" -SELECT id FROM dyn_creature_caste_tags -WHERE creature_identifier = ?1 AND caste_identifier = ?2;"; - -/// Selects the `id` from `dyn_names` matching singular and plural. -/// -/// Expects parameters: -/// -/// 1. `singular` - bound to ?1 -/// 2. `plural` - bound to ?2 -pub const GET_NAME_ID_BY_SINGULAR_PLURAL: &str = r" -SELECT id FROM dyn_names -WHERE singular = ?1 AND plural = ?2;"; - -/// Selects the `id` from `dyn_names` matching singular, plural, and adjective. -/// -/// Expects parameters: -/// -/// 1. `singular` - bound to ?1 -/// 2. `plural` - bound to ?2 -/// 3. `adjective` - bound to ?3 -pub const GET_NAME_ID_BY_SINGULAR_PLURAL_ADJECTIVE: &str = r" -SELECT id FROM dyn_names -WHERE singular = ?1 - AND plural = ?2 - AND adjective = ?3;"; - -/// Selects the `id` from `dyn_body_part_groups` for a selector -/// or `body_part` pair. -/// -/// Expects parameters: -/// -/// 1. `body_part_selector` - bound to ?1 -/// 2. `body_part` - bound to ?2 -pub const GET_BODY_PART_GROUP_ID: &str = r" -SELECT id FROM dyn_body_part_groups -WHERE body_part_selector = ?1 AND body_part = ?2;"; - -/// Selects the `id` from `ref_lair_token_flags` by token. -/// -/// Expects parameters: -/// -/// 1. `token` - bound to ?1 -pub const GET_REF_LAIR_TAG_ID: &str = r" -SELECT id FROM ref_lair_token_flags -WHERE token = ?1;"; - -/// Selects the `id` from `ref_secretion_triggers` by token. -/// -/// Expects parameters: -/// -/// 1. `token` - bound to ?1 -pub const GET_REF_SECRETION_TAG_ID: &str = r" -SELECT id FROM ref_secretion_triggers -WHERE token = ?1;"; - -/// Selects the `id` from `ref_object_types` by token. -/// -/// Expects parameters: -/// -/// 1. `token` - bound to ?1 -pub const GET_REF_OBJECT_TYPE: &str = r" -SELECT id FROM ref_object_types -WHERE token = ?1;"; diff --git a/sqlite_lib/src/queries/misc/insert_sql.rs b/sqlite_lib/src/queries/misc/insert_sql.rs deleted file mode 100644 index 34776ac5..00000000 --- a/sqlite_lib/src/queries/misc/insert_sql.rs +++ /dev/null @@ -1,60 +0,0 @@ -/// Insert SQL for the `dyn_items_of_material` table mapping an item to a material. -/// -/// Expects parameters: -/// -/// 1. `item_identifier`: identifier string for the dynamic item -/// 2. `material_identifier`: identifier string for the material -pub const INSERT_DYNAMIC_ITEM_OF_MATERIAL: &str = r" -INSERT INTO dyn_items_of_material (item_identifier, material_identifier) -VALUES (?1, ?2);"; - -/// Insert SQL for the `dyn_items_of_material` table mapping a material to a state. -/// -/// Expects parameters: -/// -/// 1. `material_identifier`: identifier string for the material -/// 2. `state`: textual representation of the material state -pub const INSERT_DYNAMIC_MATERIAL_IN_STATE: &str = r" -INSERT INTO dyn_items_of_material (material_identifier, state) -VALUES (?1, ?2);"; - -/// Insert SQL for the `dyn_creature_caste_tags` table referencing a creature and caste. -/// -/// Expects parameters: -/// -/// 1. `creature_identifier`: identifier string for the dynamic creature -/// 2. `caste_identifier`: identifier string for the dynamic caste -pub const INSERT_CREATURE_CASTE_TAG_REFERENCE: &str = r" -INSERT INTO dyn_creature_caste_tags (creature_identifier, caste_identifier) -VALUES (?1, ?2);"; - -/// Insert SQL for the `dyn_names` table (singular/plural). -/// -/// Expects parameters: -/// -/// 1. `singular`: singular form of the name -/// 2. `plural`: plural form of the name -pub const INSERT_DYNAMIC_NAME: &str = r" -INSERT INTO dyn_names (singular, plural) -VALUES (?1, ?2);"; - -/// Insert SQL for the `dyn_names` table including an adjective. -/// -/// Expects parameters: -/// -/// 1. `singular`: singular form of the name -/// 2. `plural`: plural form of the name -/// 3. `adjective`: associated adjective string -pub const INSERT_DYNAMIC_NAME_WITH_ADJECTIVE: &str = r" -INSERT INTO dyn_names (singular, plural, adjective) -VALUES (?1, ?2, ?3);"; - -/// Insert SQL for the `dyn_body_part_groups` table mapping a selector to a body part. -/// -/// Expects parameters: -/// -/// 1. `body_part_selector`: selector or identifier for the body part group -/// 2. `body_part`: identifier or name of the body part -pub const INSERT_BODY_PART_GROUP: &str = r" -INSERT INTO dyn_body_part_groups (body_part_selector, body_part) -VALUES (?1, ?2)"; diff --git a/sqlite_lib/src/queries/misc/mod.rs b/sqlite_lib/src/queries/misc/mod.rs deleted file mode 100644 index 9c9a2f4e..00000000 --- a/sqlite_lib/src/queries/misc/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -//! SQL and helper methods for misc tables. - -pub mod get; -mod get_sql; -mod insert_sql; - -pub use get_sql::*; -pub use insert_sql::*; diff --git a/sqlite_lib/src/queries/mod.rs b/sqlite_lib/src/queries/mod.rs deleted file mode 100644 index 7c73f9ae..00000000 --- a/sqlite_lib/src/queries/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! Queries for retrieving objects from the database - -pub mod caste; -pub mod color; -pub mod misc; -pub mod reference; diff --git a/sqlite_lib/src/queries/reference/get.rs b/sqlite_lib/src/queries/reference/get.rs deleted file mode 100644 index c407b516..00000000 --- a/sqlite_lib/src/queries/reference/get.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Get helper operations for the reference tables - -use turso::params; - -use crate::client::DbClient; - -impl DbClient { - /// GET the id of a caste flag by its token text - /// - /// # Parameters - /// - /// - `token`: the string token value (e.g. 'AMPHIBOIOUS', 'BABY' etc.) - /// - /// # Errors - /// - /// Will error if a database lookup fails - pub async fn get_caste_flag_id_by_token( - &self, - token: &str, - ) -> Result> { - let conn = self.get_connection()?; - - let mut id_rows = conn - .query(super::GET_CASTE_FLAG_BY_TOKEN, params![token]) - .await?; - - let token_id: i64 = id_rows - .next() - .await? - .ok_or("No ID found for given token on caste_flags table")? - .get(0)?; - - Ok(token_id) - } -} diff --git a/sqlite_lib/src/queries/reference/get_sql.rs b/sqlite_lib/src/queries/reference/get_sql.rs deleted file mode 100644 index f493c742..00000000 --- a/sqlite_lib/src/queries/reference/get_sql.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! SQL for GET queries on the various reference tables. - -/// SQL to GET the `flag_id` of a caste token by the `token` (name) -/// -/// Expects parameters: -/// -/// 1. `token`: the name of the token (e.g. 'AMPHIBIOUS' or 'BABY') -pub const GET_CASTE_FLAG_BY_TOKEN: &str = r" -SELECT id FROM ref_caste_token_flags -WHERE token = ?1"; diff --git a/sqlite_lib/src/queries/reference/mod.rs b/sqlite_lib/src/queries/reference/mod.rs deleted file mode 100644 index e4dddb3d..00000000 --- a/sqlite_lib/src/queries/reference/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! SQL Queries and helper methods for the various reference tables. - -pub mod get; -mod get_sql; - -pub use get_sql::*; diff --git a/sqlite_lib/src/search_helpers.rs b/sqlite_lib/src/search_helpers.rs new file mode 100644 index 00000000..5ce9aab6 --- /dev/null +++ b/sqlite_lib/src/search_helpers.rs @@ -0,0 +1,30 @@ +//! Helpers used in regards to compiling data used for filling search indices + +use dfraw_parser::{Creature, Plant, metadata::ObjectType, traits::RawObject}; + +/// Given a raw object (via `&Box`) will extract names and descriptions to use in +/// the search indices. +#[allow(clippy::borrowed_box)] +pub fn extract_names_and_descriptions(raw: &Box) -> (Vec<&str>, Vec<&str>) { + // Metadata extraction for search index + let mut search_names = Vec::<&str>::new(); + let mut search_descriptions = Vec::<&str>::new(); + + match raw.get_type() { + ObjectType::Creature => { + if let Some(creature) = raw.as_any().downcast_ref::() { + search_names.clone_from(&creature.get_all_names()); + search_descriptions.clone_from(&creature.get_all_descriptions()); + } + } + ObjectType::Plant => { + if let Some(plant) = raw.as_any().downcast_ref::() { + search_names.clone_from(&plant.get_all_names()); + search_descriptions.clone_from(plant.get_pref_strings().as_ref()); + } + } + _ => {} + } + + (search_names, search_descriptions) +} diff --git a/sqlite_lib/src/util.rs b/sqlite_lib/src/util.rs deleted file mode 100644 index 11badd84..00000000 --- a/sqlite_lib/src/util.rs +++ /dev/null @@ -1,55 +0,0 @@ -//! Provides utility functions for the database. -use std::fmt::Write; - -/// Gets the schema version of the database. -/// -/// # Errors -/// Returns an error if the schema version cannot be retrieved. -pub(crate) async fn get_schema_version(conn: &turso::Connection) -> Result { - // PRAGMA user_version returns one row with one column - let mut rows = conn.query("PRAGMA user_version;", ()).await?; - - if let Some(row) = rows.next().await? { - // The value is in the first column (index 0) - Ok(row.get(0)?) - } else { - Ok(0) // Default to 0 if something weird happens - } -} - -/// Escape a string for safe inclusion in a SQL literal by doubling single quotes. -fn escape_sql_string(value: &str) -> String { - value.replace('\'', "''") -} - -/// Wrap a string in single quotes after escaping it for SQL. -/// -/// This can be used directly for any string needed to be included in a SQL literal. This returns -/// a string that is surrounded by single quotes. -/// -/// Example: `quoted_sql("O'Brien")` -> `'O''Brien'` -pub(crate) fn quoted_sql(value: &str) -> String { - format!("'{}'", escape_sql_string(value)) -} - -/// Build a simple batch INSERT statement for a single-column table. -/// -/// The function returns a concatenated series of `INSERT INTO ... VALUES ...;` -/// statements. It is intended to be executed with a single `execute_batch` call. -/// -/// # Arguments -/// -/// * `table` - table name (unquoted). Caller is responsible for ensuring it is a safe identifier. -/// * `column` - column name (unquoted). -/// * `values` - slice of string values to insert. -/// -/// Example: `build_batch_insert("ref_biome_token_tags", "token", &["FOO","BAR"])` returns the SQL to insert. -pub(crate) fn build_batch_insert(table: &str, column: &str, values: &[&str]) -> String { - let mut batch = String::new(); - for v in values { - let esc = quoted_sql(v); - write!(&mut batch, "INSERT INTO {table} ({column}) VALUES ({esc});",) - .expect("Failed to write to string"); - } - batch -} diff --git a/sqlite_lib/tests/common/mod.rs b/sqlite_lib/tests/common/mod.rs new file mode 100644 index 00000000..8192232c --- /dev/null +++ b/sqlite_lib/tests/common/mod.rs @@ -0,0 +1,15 @@ +//! Helper to setup a tracing subscriber exactly once + +use std::sync::Once; + +static INIT: Once = Once::new(); + +pub fn setup_tracing() { + INIT.call_once(|| { + // One-time tracing setup + let _ = tracing_subscriber::fmt() + .with_max_level(tracing::Level::DEBUG) + .compact() + .try_init(); + }); +} diff --git a/sqlite_lib/tests/db_tests.rs b/sqlite_lib/tests/db_tests.rs new file mode 100644 index 00000000..326a2004 --- /dev/null +++ b/sqlite_lib/tests/db_tests.rs @@ -0,0 +1,486 @@ +//! Tests for verifying that the various search functions work. + +use chrono::TimeDelta; +use dfraw_parser::metadata::{ObjectType, RawModuleLocation}; +use dfraw_parser_sqlite_lib::SearchQuery; +use test_util::get_test_client; + +use crate::common::setup_tracing; + +mod common; + +#[test] +fn has_zero_results_for_only_workshopmods_location() { + setup_tracing(); + let client_mutex = get_test_client(); + + // get all raws within only 'WorkshopMods' location + let query = SearchQuery { + locations: vec![RawModuleLocation::WorkshopMods], + ..Default::default() + }; + + let search_results = { + let client = client_mutex.lock().expect("Failed to lock DbClient"); + client + .search_raws(&query) + .expect("Failed to query the generated database") + }; + + // We expect no results + assert!( + search_results.results.is_empty(), + "Should have had no results, but had some." + ); +} + +#[test] +fn has_results_for_only_vanilla_location() { + setup_tracing(); + let client_mutex = get_test_client(); + + // get all raws within only 'Vanilla' location + let query = SearchQuery { + locations: vec![RawModuleLocation::Vanilla], + ..Default::default() + }; + + let search_results = { + let client = client_mutex.lock().expect("Failed to lock DbClient"); + client + .search_raws(&query) + .expect("Failed to query the generated database") + }; + + assert!( + !search_results.results.is_empty(), + "Should have had results, but had none." + ); +} + +#[test] +fn has_results_for_vanilla_or_workshopmods_locations() { + setup_tracing(); + let client_mutex = get_test_client(); + + // get all raws within either 'Vanilla' or 'WorkshopMods' locations + let query = SearchQuery { + locations: vec![RawModuleLocation::Vanilla, RawModuleLocation::WorkshopMods], + ..Default::default() + }; + + let search_results = { + let client = client_mutex.lock().expect("Failed to lock DbClient"); + client + .search_raws(&query) + .expect("Failed to query the generated database") + }; + + assert!( + !search_results.results.is_empty(), + "Should have had results, but had none." + ); +} + +#[test] +fn has_results_when_using_search_index() { + setup_tracing(); + let client_mutex = get_test_client(); + + // Search using the search index. + // Searching 'dvark' should return 3 results on vanilla raws: aardvark, aardvark man, giant aardvark + let query = SearchQuery { + search_string: Some("dvark".to_string()), + ..Default::default() + }; + + let search_results = { + let client = client_mutex.lock().expect("Failed to lock DbClient"); + client + .search_raws(&query) + .expect("Failed to query the generated database") + }; + + assert!( + !search_results.results.is_empty(), + "Search should have found some matches for 'dvark'" + ); +} + +#[test] +fn has_results_for_required_flag() { + setup_tracing(); + let client_mutex = get_test_client(); + + // get all raws with the `[FLIER]` tag + let query = SearchQuery { + required_flags: vec!["FLIER".to_string()], + ..Default::default() + }; + + let search_results = { + let client = client_mutex.lock().expect("Failed to lock DbClient"); + client + .search_raws(&query) + .expect("Failed to query the generated database") + }; + + assert!( + !search_results.results.is_empty(), + "Search should have found flying creatures (e.g., Peregrine Falcons) in the database." + ); +} + +#[test] +fn test_identifier_partial_matching() { + setup_tracing(); + let client_mutex = get_test_client(); + + // Search for a partial identifier. + // "IRON" should match things like "PIG_IRON", "ELEMENTMAN_IRON", etc. + let query = SearchQuery { + identifier_query: Some("IRON".to_string()), + ..Default::default() + }; + + let search_results = { + let client = client_mutex.lock().expect("Failed to lock DbClient"); + client + .search_raws(&query) + .expect("Failed to query database") + }; + + assert!( + !search_results.results.is_empty(), + "Should match identifiers containing 'IRON'" + ); + + for result in search_results.results { + // Since search_raws returns blobs, we deserialize to check the identifier if needed, + // but here we just verify existence based on the SQL logic. + assert!(!result.data.is_empty()); + } +} + +#[test] +fn test_raw_type_filtering_intersection() { + setup_tracing(); + let client_mutex = get_test_client(); + + // Test that multiple types return results from both categories + let query = SearchQuery { + raw_types: vec![ObjectType::Creature, ObjectType::Plant], + ..Default::default() + }; + + let search_results = { + let client = client_mutex.lock().expect("Failed to lock DbClient"); + client + .search_raws(&query) + .expect("Failed to query database") + }; + + // In vanilla raws, both creatures and plants are plentiful + assert!( + search_results.results.len() > 10, + "Should return a healthy mix of creatures and plants" + ); +} + +#[test] +fn test_pagination_logic() { + setup_tracing(); + let client_mutex = get_test_client(); + + // Page 1 + let query_p1 = SearchQuery { + limit: 5, + page: 1, + ..Default::default() + }; + let res_p1 = { + let client = client_mutex.lock().expect("Failed to lock DbClient"); + client.search_raws(&query_p1).expect("Failed P1") + }; + + // Page 2 + let query_p2 = SearchQuery { + limit: 5, + page: 2, + ..Default::default() + }; + let res_p2 = { + let client = client_mutex.lock().expect("Failed to lock DbClient"); + client.search_raws(&query_p2).expect("Failed P2") + }; + + assert_eq!(res_p1.results.len(), 5); + assert_eq!(res_p2.results.len(), 5); + + // Ensure total_count is the same for both pages and reflects the full DB + assert!(res_p1.total_count > 5); + assert_eq!(res_p1.total_count, res_p2.total_count); + + // Ensure the results are actually different (not just returning the same page) + assert_ne!( + res_p1.results[0], res_p2.results[0], + "Page 2 should start with different items than Page 1" + ); +} + +#[test] +fn test_combined_intersection_filtering() { + setup_tracing(); + let client_mutex = get_test_client(); + + // Combined query: String search + Type filter + Flag filter + // Look for "Giant" + CREATURE + FLIER + let query = SearchQuery { + search_string: Some("Giant".to_string()), + raw_types: vec![ObjectType::Creature], + required_flags: vec!["FLIER".to_string()], + ..Default::default() + }; + + let search_results = { + let client = client_mutex.lock().expect("Failed to lock DbClient"); + client.search_raws(&query).expect("Failed combined query") + }; + + // This should match things like Giant Bats or Giant Eagles + assert!( + !search_results.results.is_empty(), + "Should find giant flying creatures" + ); +} + +#[test] +fn test_ranking_by_relevance() { + setup_tracing(); + let client_mutex = get_test_client(); + + // FTS5 ranking test. Searching for "Dwarf" should put "Dwarf" at the top via bm25 before + // any other creatures with "Dwarf" in their description + let query = SearchQuery { + search_string: Some("Dwarf".to_string()), + limit: 10, + ..Default::default() + }; + + let search_results = { + let client = client_mutex.lock().expect("Failed to lock DbClient"); + client.search_raws(&query).expect("Failed ranking query") + }; + + assert!(!search_results.results.is_empty()); +} + +#[test] +fn test_no_results_for_non_existent_criteria() { + setup_tracing(); + let client_mutex = get_test_client(); + + // Search for a flag that doesn't exist + let query = SearchQuery { + required_flags: vec!["MADE_OF_CHEESE".to_string()], + ..Default::default() + }; + + let search_results = { + let client = client_mutex.lock().expect("Failed to lock DbClient"); + client.search_raws(&query).expect("Failed empty query") + }; + + assert!(search_results.results.is_empty()); + assert_eq!(search_results.total_count, 0); +} + +#[test] +fn test_trigram_substring_matching() { + setup_tracing(); + let client_mutex = get_test_client(); + + // Trigram search: "oad" should find "Toad" and similar + let query = SearchQuery { + search_string: Some("oad".to_string()), + ..Default::default() + }; + + let search_results = { + let client = client_mutex.lock().expect("Failed to lock DbClient"); + client.search_raws(&query).expect("Failed trigram query") + }; + + assert!( + !search_results.results.is_empty(), + "Trigram search should find partial matches like 'toad'" + ); +} + +#[test] +fn verify_get_set_delete_favorite_raws() { + setup_tracing(); + let client_mutex = get_test_client(); + let initial_favorite_raws = client_mutex + .lock() + .expect("Failed to lock DbClient") + .get_favorite_raws() + .expect("Get favorite raws failed"); + client_mutex + .lock() + .expect("Failed to lock DbClient") + .add_favorite_raw(13) + .expect("Failed to add favorite raw 13"); + client_mutex + .lock() + .expect("Failed to lock DbClient") + .add_favorite_raw(203) + .expect("Failed to add favorite raw 203"); + let after_favorite_raws = client_mutex + .lock() + .expect("Failed to lock DbClient") + .get_favorite_raws() + .expect("Get favorite raws failed"); + client_mutex + .lock() + .expect("Failed to lock DbClient") + .remove_favorite_raw(13) + .expect("Failed to remove favorite raw 13"); + let final_favorite_raws = client_mutex + .lock() + .expect("Failed to lock DbClient") + .get_favorite_raws() + .expect("Get favorite raws failed"); + + // Verify + assert_eq!(initial_favorite_raws.len(), 0); + assert_eq!(after_favorite_raws.len(), 2); + assert_eq!(final_favorite_raws.len(), 1); + assert_eq!(final_favorite_raws.first().unwrap_or(&0), &203); +} + +#[test] +fn verify_previous_insertion_duration() { + setup_tracing(); + let client_mutex = get_test_client(); + let duration = client_mutex + .lock() + .expect("Failed to lock DbClient") + .get_last_insertion_duration() + .expect("Failed to get last insertion duration") + .expect("No insertion duration found when expected"); + assert_ne!(duration, TimeDelta::zero(), "duration should not be zero"); + tracing::info!( + "Last insertion duration was {}ms", + duration.num_milliseconds() + ); +} + +#[test] +fn verify_previous_insertion_date() { + setup_tracing(); + let client_mutex = get_test_client(); + let date = client_mutex + .lock() + .expect("Failed to lock DbClient") + .get_last_insertion_date() + .expect("Failed to get last insertion duration"); + assert!(!date.is_empty()); + tracing::info!("Last insertion date {date}"); +} + +#[test] +fn verify_previous_parse_date() { + setup_tracing(); + let client_mutex = get_test_client(); + let date = client_mutex + .lock() + .expect("Failed to lock DbClient") + .get_last_parse_operation_date() + .expect("Failed to get last insertion duration"); + assert!(!date.is_empty()); + tracing::info!("Last parse operation date {date}"); +} + +#[test] +fn verify_previous_parse_duration() { + setup_tracing(); + let client_mutex = get_test_client(); + let duration = client_mutex + .lock() + .expect("Failed to lock DbClient") + .get_last_parse_duration() + .expect("Failed to get last insertion duration") + .expect("No insertion duration found when expected"); + assert_ne!(duration, TimeDelta::zero(), "duration should not be zero"); + tracing::info!("Last parse duration was {}ms", duration.num_milliseconds()); +} + +#[test] +fn verify_previous_df_dir() { + setup_tracing(); + let client_mutex = get_test_client(); + let game_dir = client_mutex + .lock() + .expect("Failed to lock DbClient") + .get_last_used_df_game_dir() + .expect("Failed to get last insertion duration"); + + assert!(!game_dir.is_empty(), "game dir shouldn't be empty"); + tracing::info!("Last df game dir was {game_dir}"); +} +#[test] +fn verify_previous_user_dir() { + setup_tracing(); + let client_mutex = get_test_client(); + let user_dir = client_mutex + .lock() + .expect("Failed to lock DbClient") + .get_last_used_df_user_dir() + .expect("Failed to get last insertion duration"); + + assert!(!user_dir.is_empty(), "user dir shouldn't be empty"); + tracing::info!("Last df user dir was {user_dir}"); +} + +#[test] +fn get_last_used_parser_options() { + setup_tracing(); + let client_mutex = get_test_client(); + let parser_options = client_mutex + .lock() + .expect("Failed to lock DbClient") + .get_last_used_parser_options() + .expect("Failed to get last parser options") + .expect("Last parser options shouldn't be None"); + + tracing::info!("Last used parsing options: {parser_options:?}"); +} + +#[test] +fn verify_preferred_search_limit() { + setup_tracing(); + let client_mutex = get_test_client(); + let page_limit_1 = client_mutex + .lock() + .expect("Failed to lock DbClient") + .get_preferred_search_limit() + .expect("Failed to get preferred search limit"); + client_mutex + .lock() + .expect("Failed to lock DbClient") + .set_preferred_search_limit(page_limit_1 + 10) + .expect("Failed to set preferred search limit"); + let page_limit_2 = client_mutex + .lock() + .expect("Failed to lock DbClient") + .get_preferred_search_limit() + .expect("Failed to get preferred search limit"); + + assert_ne!( + page_limit_1, page_limit_2, + "page limits should be different" + ); + assert_ne!(page_limit_1, 0, "page limit cannot be zero"); + assert_ne!(page_limit_2, 0, "page limit cannot be zero"); +} diff --git a/sqlite_lib/tests/test_db_creation.rs b/sqlite_lib/tests/test_db_creation.rs deleted file mode 100644 index 0bdc8347..00000000 --- a/sqlite_lib/tests/test_db_creation.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! Test the database creation/initialization process - -use std::{fs, process::ExitCode}; - -const TEST_DB_NAME: &str = "test.db"; - -/// Create and test initialize the database -#[pollster::test] -async fn create_and_init_database() -> ExitCode { - let subscriber = tracing_subscriber::FmtSubscriber::builder() - // all spans/events with a level higher than TRACE (e.g, debug, info, warn, etc.) - // will be written to stdout. - .with_max_level(tracing::Level::INFO) - // make it pretty - .compact() - // completes the builder. - .finish(); - tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); - - cleanup_test_db(); - let Ok(client) = sqlite_lib::client::DbClient::new(TEST_DB_NAME).await else { - return ExitCode::FAILURE; - }; - - if client.init().await.is_ok() { - // cleanup_test_db(); - ExitCode::SUCCESS - } else { - ExitCode::FAILURE - } -} - -#[allow(dead_code)] -fn cleanup_test_db() { - match fs::remove_file(TEST_DB_NAME) { - Ok(()) => println!("Removed test file."), - Err(error) => println!("{error:?}"), - } -} diff --git a/test_util/Cargo.toml b/test_util/Cargo.toml new file mode 100644 index 00000000..eab644de --- /dev/null +++ b/test_util/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "test_util" +version = "0.1.0" +edition = "2024" + +[lints] +workspace = true + +[dependencies] +zip = "7.0.0" +chrono={workspace=true} +dfraw_parser_sqlite_lib = {workspace=true} +dfraw_parser = {workspace=true} + +[dependencies.reqwest] +version = "0.13.1" +features = ["blocking"] diff --git a/test_util/src/lib.rs b/test_util/src/lib.rs new file mode 100644 index 00000000..14567297 --- /dev/null +++ b/test_util/src/lib.rs @@ -0,0 +1,147 @@ +//! Utility to gather vanilla raws to use for testing. +use chrono::prelude::*; +use dfraw_parser::metadata::{ParserOptions, RawModuleLocation}; +use dfraw_parser::parse; +use dfraw_parser_sqlite_lib::{ClientOptions, DbClient}; +use std::fs::{self, File}; +use std::io::{self, Cursor}; +use std::path::PathBuf; +use std::sync::{Arc, Mutex, OnceLock}; +use zip::ZipArchive; + +const VANILLA_RAW_URL: &str = "https://build-deps.ci.nwest.one/dwarffortress/vanilla_latest.zip"; +const TEST_DATA_DIR: &str = "test-data"; +const TEST_INNER_DIR: &str = "data/vanilla"; +const TEST_DB_NAME: &str = "test.db"; +const TEST_USER_DIR: &str = "user"; + +// We store a Result so that tests can check if setup worked. +// We use Arc so multiple tests can own a reference to the client. +static SHARED_CLIENT: OnceLock>, String>> = OnceLock::new(); + +/// Get a shared test dbclient that is only initialized once +/// +/// # Panics +/// +/// Will panic if some part of setting up the test database errors out +pub fn get_test_client() -> Arc> { + // get_or_init ensures the setup runs exactly once + let result = SHARED_CLIENT.get_or_init(|| { + // Setup test data + let vanilla_path = ensure_vanilla_raws(); + let user_path = get_user_dir(); + + // Initialize the DbClient + let options = ClientOptions { + reset_database: true, + overwrite_raws: true, + }; + + let mut client = + DbClient::init_db(TEST_DB_NAME, options).map_err(|e| format!("DB Init Error: {e}"))?; + + // Parse and Insert + let mut parser_options = ParserOptions::default(); + parser_options.add_location_to_parse(RawModuleLocation::Vanilla); + parser_options.set_dwarf_fortress_directory(&vanilla_path); + parser_options.set_user_data_directory(&user_path); + + let start = Utc::now(); + let parse_results = parse(&parser_options).map_err(|e| format!("Parse Error: {e}"))?; + let end = Utc::now(); + let duration = end - start; + let num_info_files = parse_results.info_files.len(); + + client + .set_last_parse_duration(&duration) + .map_err(|e| format!("DB Metadata Set last parse duration Error: {e}"))?; + client + .set_last_parse_operation_utc_datetime(&end) + .map_err(|e| format!("DB Metadata Set last parse date Error: {e}"))?; + client + .set_last_used_parser_options(&parser_options) + .map_err(|e| format!("DB Metadata Set last parse options Error: {e}"))?; + + client + .insert_parse_results(parse_results) + .map_err(|e| format!("DB Insert Error: {e}"))?; + + println!("Sucessfully inserted {num_info_files} modules."); + Ok(Arc::new(Mutex::new(client))) + }); + + match result { + Ok(client_mutex) => Arc::clone(client_mutex), + Err(e) => panic!("Global test setup failed: {e}"), + } +} + +/// Helper that just assembles the [`PathBuf`] for the testing user data dir. +fn get_user_dir() -> PathBuf { + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let Some(root_dir) = manifest_dir.parent() else { + panic!("Unable to grab correct directory"); + }; + let test_data_dir = root_dir.join(TEST_DATA_DIR); + let user_data_dir = test_data_dir.join(TEST_USER_DIR); + fs::create_dir_all(&user_data_dir).expect("Failed to create user data dir!"); + + user_data_dir +} + +/// Ensures the vanilla raw files are available for testing. +/// Returns the path to the directory containing the raws. +/// +/// # Panics +/// +/// This will panic if failed to download, unzip, or create the folder/files. +/// +/// # Returns +/// +/// The analog for the DF dir +#[must_use] +pub fn ensure_vanilla_raws() -> PathBuf { + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let Some(root_dir) = manifest_dir.parent() else { + panic!("Unable to grab correct directory"); + }; + let test_data_dir = root_dir.join(TEST_DATA_DIR); + let target_dir = test_data_dir.join(TEST_INNER_DIR); + + if target_dir.exists() { + return test_data_dir; + } + + println!("Downloading vanilla raws for testing..."); + let response = reqwest::blocking::get(VANILLA_RAW_URL) + .expect("Failed to download vanilla raws") + .bytes() + .expect("Failed to read response bytes"); + + println!("Extracting vanilla raws..."); + let mut archive = ZipArchive::new(Cursor::new(response)).expect("Failed to parse zip archive"); + + fs::create_dir_all(&target_dir).expect("Failed to create target directory"); + + for i in 0..archive.len() { + let mut file = archive.by_index(i).expect("Failure to open the zip file."); + let outpath = match file.enclosed_name() { + Some(path) => target_dir.join(path), + None => continue, + }; + + if (*file.name()).ends_with('/') { + fs::create_dir_all(&outpath).expect("Failure to create output directory"); + } else { + if let Some(p) = outpath.parent() + && !p.exists() + { + fs::create_dir_all(p).expect("Failure to create output directory"); + } + let mut outfile = File::create(&outpath).expect("Failure to create output file"); + io::copy(&mut file, &mut outfile).expect("Failure to copy from zip to output file"); + } + } + + test_data_dir +} diff --git a/update_bindings b/update_bindings deleted file mode 100755 index d0dca9df..00000000 --- a/update_bindings +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh -# This script is used to update the typescript bindings for the project. -# It is used by the CI system to ensure that the bindings are up to date (on PR/merge/push). -# It can also be used locally to update the bindings. - -# This script requires the following tools to be installed: -# - cargo -# - pnpm - -rm -rf jsonlib/bindings - -# Running `test --all-features` will also generate bindings via `ts-rs` however they are not -# formatted nicely. So we then run prettier to format them. -cargo test --all-features - -# Ensure that the generated bindings are formatted nicely. -bunx @biomejs/biome format --write jsonlib/bindings/*.ts diff --git a/update_bindings.ps1 b/update_bindings.ps1 deleted file mode 100644 index 20852ce4..00000000 --- a/update_bindings.ps1 +++ /dev/null @@ -1,4 +0,0 @@ -Remove-Item '.\jsonlib\bindings\*' -Force -cargo test --all-features -bunx @biomejs/biome format --write "./jsonlib/bindings/DFRawJson.d.ts" -bunx @biomejs/biome format --write "./jsonlib/bindings/DFRawJson-Tauri.d.ts"