diff --git a/Cargo.lock b/Cargo.lock index cc3a4bc..213acda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,12 +56,6 @@ version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - [[package]] name = "arrayvec" version = "0.7.8" @@ -98,9 +92,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "base64" -version = "0.22.1" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" [[package]] name = "bitflags" @@ -116,15 +110,15 @@ checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "blake3" -version = "1.5.5" +version = "1.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e" +checksum = "6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae" dependencies = [ - "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", + "cpufeatures", ] [[package]] @@ -202,18 +196,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.5" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301b56658598e48f3648647ac6fc887be7e7108eddfa4e9b63fcf3ec58c0cadf" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.6.5" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94a65403d1a1bd28f7dc68eb8506e8874808ee5eecb59298de588e2e1407a078" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" dependencies = [ "anstyle", "clap_lex", @@ -242,9 +236,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "constant_time_eq" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] name = "countme" @@ -252,6 +246,15 @@ version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -1155,9 +1158,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -1165,22 +1168,22 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 3.0.4", ] [[package]] @@ -1808,7 +1811,7 @@ dependencies = [ [[package]] name = "zerosyntax-analysis" -version = "1.3.1" +version = "1.3.2" dependencies = [ "criterion", "rowan", @@ -1820,7 +1823,7 @@ dependencies = [ [[package]] name = "zerosyntax-schema" -version = "1.3.1" +version = "1.3.2" dependencies = [ "serde", "serde_json", @@ -1828,7 +1831,7 @@ dependencies = [ [[package]] name = "zerosyntax-server" -version = "1.3.1" +version = "1.3.2" dependencies = [ "anyhow", "base64", @@ -1854,7 +1857,7 @@ dependencies = [ [[package]] name = "zerosyntax-syntax" -version = "1.3.1" +version = "1.3.2" dependencies = [ "criterion", "logos", @@ -1863,7 +1866,7 @@ dependencies = [ [[package]] name = "zerosyntax-w3d" -version = "1.3.1" +version = "1.3.2" dependencies = [ "glam", "image", diff --git a/Cargo.toml b/Cargo.toml index aa99fa6..d73ddf8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "2" members = ["crates/schema", "crates/syntax", "crates/analysis", "crates/w3d", "crates/server"] [workspace.package] -version = "1.3.1" +version = "1.3.2" edition = "2021" license = "MIT" repository = "https://github.com/ViTeXFTW/ZeroSyntaxV2" @@ -33,7 +33,7 @@ tokio = { version = "1", features = ["full"] } ropey = "1" dashmap = "6" percent-encoding = "2" -base64 = "0.22" +base64 = "0.23" glam = "0.27" image = { version = "0.24.9", default-features = false, features = ["dds", "png", "tga"] } @@ -47,7 +47,7 @@ clap = { version = "4", default-features = false, features = [ ] } rusqlite = { version = "0.31", features = ["bundled"] } postcard = { version = "1.1", default-features = false, features = ["use-std"] } -blake3 = "=1.5.5" +blake3 = "=1.8.7" # benches criterion = "0.8" diff --git a/README.md b/README.md index 28dd129..e5273d4 100644 --- a/README.md +++ b/README.md @@ -1,110 +1,125 @@ -# ZeroSyntax v2 +

+ ZeroSyntax logo +

- ZeroSyntax logo + + CI + + + Latest Release + + + License: MIT +

-[![CI](https://github.com/ViTeXFTW/ZeroSyntaxV2/actions/workflows/ci.yml/badge.svg)](https://github.com/ViTeXFTW/ZeroSyntaxV2/actions/workflows/ci.yml) -[![Release](https://github.com/ViTeXFTW/ZeroSyntaxV2/actions/workflows/release.yml/badge.svg)](https://github.com/ViTeXFTW/ZeroSyntaxV2/actions/workflows/release.yml) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) +## 📝 About the project +This project was born from the frustration when creating maps with `map.ini` changes. The SAGE engine is very selective and will crash if files contain unknown fields or values, being a software developer I wished language features such as diagnostics and completions was a part of the development flow. Thus `ZeroSyntax` was born. + +![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png) +## ⚡ Quick start +The easiest way to get started is to use the VSCode extension inside VSCode. This will install the server and automatically recognize INI files + +1. Open VSCode +2. Go to the `Extensions` tab +3. Search `ZeroSyntaxV2` +4. Install the extension -ZeroSyntax v2 brings modern editor support to the INI scripting files used by -*Command & Conquer: Generals – Zero Hour*. It helps modders and map authors find -mistakes early and navigate large game or mod workspaces. +

+ ZeroSyntax logo +

-The project includes the IDE-independent `zerosyntax-lsp` language server and a -VS Code extension with the server bundled. +Opening a map folder or ini file will now be parsed and checked for syntax errors. -## What you get +> [!WARNING] +> The server will recognize all `.ini` files as Generals Zero Hour ini files, disable the extension if you need to edit normal files. -- Schema-aware diagnostics and quick fixes for blocks, fields, modules, values, - references, module tags, and missing `End` statements. -- Context-aware completion for INI keywords, enum values, flags, and definitions - in your workspace. -- Hover information, go to definition, find references, rename, workspace and - document symbols, and folding. -- Semantic highlighting for Generals INI files. -- Optional indentation formatting, disabled by default so existing files are - never reformatted without your consent. -- Base-game and mod indexing for `map.ini` and `solo.ini`, including W3D model - and bone checks. Selecting a `Model =` completion shows a textured W3D - thumbnail when the indexed asset is available, with configurable size and - zoom. +To expand on features go to the settings page in VSCode and find the `ZeroSyntax` extension. One of the settings will say `baseIniRoots`, here you can add the path to your desired game folder for `ZeroSyntax` to parse and read your game files. This will provide completions for models, bones, textures, audio and more specific to that game install. -## Install the VS Code extension +![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png) -ZeroSyntax supports Windows x64 and Linux x64 release builds. +## 💡 Examples -1. Download the `.vsix` for your platform from the - [latest GitHub release](https://github.com/ViTeXFTW/ZeroSyntaxV2/releases/latest). -2. In VS Code, open **Extensions**, choose **Views and More Actions …**, - select **Install from VSIX…**, and open the downloaded file. -3. Open your mod or map folder, then open an `.ini` file. +### 1. ❌ Diagnostics +When incorrect values are parsed to fields the extension will create an error for this -The extension treats `.ini` files as **Generals INI**. If a workspace also -contains unrelated INI files, use VS Code's `files.associations` setting to -limit that language association to the appropriate folders. +![value_diagnostic](./resources/readme-examples/value_diagnostic_percentage.png) -See the [VS Code extension guide](editors/vscode/README.md) for settings and -troubleshooting. +Similar objects or types which hasn't been defined yet will also give warnings -## Use the standalone language server +![reference_diagnostic](./resources/readme-examples/reference_diagnostic_value.png) -Download the `zerosyntax-lsp` archive for your platform from the -[latest release](https://github.com/ViTeXFTW/ZeroSyntaxV2/releases/latest), -extract it, and configure your editor to run the binary over stdio. +### 2. ✅ Completions +When the `baseIniRoots` setting is pointing to the game install certain completions will be available, like model references -See the [language server guide](docs/language-server.md) for initialization -options and editor integration details. +![model_completion](./resources/readme-examples/model_completion_example.png) -## Check files from the command line +### 3. ✏️ Snippets +Common boilerplate code and long field/value pairs will be suggested and fill out all required text and allow the user to select only the customizable values -The same binary can run diagnostics without an editor: +
+ snippet_example +
-```sh -zerosyntax-lsp check Data/INI -zerosyntax-lsp check map.ini --base-root "C:/Games/Zero Hour" -zerosyntax-lsp check --json --stdin-filename map.ini - < generated.ini -``` +![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png) -This is intended for CI, pre-commit checks, and LLM edit/check loops. Errors -produce exit code 1 by default; add `--fail-on warning` for a stricter gate. -See the [standalone guide](docs/language-server.md#command-line-diagnostics) for -the complete output and exit-code contract. - -## Configure map and model checks - -For complete `map.ini` and `solo.ini` diagnostics, set -`zerosyntax.baseIniRoots` in VS Code to the base game or mod directories and/or -`.big` archives that load before the map. The same setting also enables W3D -model and bone completion and validation. - -`zerosyntax.analysis.modelMemberStrictness` controls bone/subobject warnings: -`off`, `compatible` (the default; present in any applicable model), or `strict` -(present in every applicable model). - -```json -{ - "zerosyntax.baseIniRoots": [ - "C:/Games/Zero Hour", - "C:/Mods/MyMod/Data/INI", - "C:/Mods/MyMod.big" - ] -} -``` +## ⚙️ Commands & Settings -## Documentation +Open **Settings** in VSCode and search for `ZeroSyntax` to configure the extension. Open the Command Palette (`Ctrl+Shift+P`) and search for `ZeroSyntax` to run its commands. -- [Diagnostics, suppression, and quick fixes](docs/diagnostics.md) -- [Standalone language server setup](docs/language-server.md) -- [Contributing](CONTRIBUTING.md) -- [Support](SUPPORT.md) -- [Security policy](SECURITY.md) +### Recommended settings -## License and trademarks +| Setting | Default | Description | +| --- | --- | --- | +| `zerosyntax.baseIniRoots` | `[]` | Add every game/mod folder or `.big` archive loaded before your project. This enables accurate references and asset completions for models, bones, textures, and audio. | +| `zerosyntax.server.path` | Empty (uses bundled server) | Leave empty for normal use. Set an absolute path only when using a separately installed or locally built `zerosyntax-lsp` binary. | +| `zerosyntax.analysis.allowPercentagesWithoutSign` | `false` | Allow engine-compatible percentage values without a trailing `%`. | +| `zerosyntax.preview.enable` | `true` | Show W3D model thumbnails in completion details. Disable this on slower hardware. | +| `zerosyntax.format.enable` | `false` | Enable indentation formatting and format-on-save support. It is off by default to preserve existing formatting. | +| `zerosyntax.schema.path` | `""` | Add path to a different schema file for custom diagnostics. | -ZeroSyntax v2 is available under the [MIT License](LICENSE). +### Commands + +| Command | Description | +| --- | --- | +| **ZeroSyntax: Rebuild Index Cache** | Clear and rebuild the index after game, mod, or asset files change outside VSCode. | +| **ZeroSyntax: Clear Index Cache** | Remove cached index data. It will be recreated during the next scan. | +| **ZeroSyntax: Open Index Cache Location** | Reveal the persistent cache on disk for inspection or troubleshooting. | +| **ZeroSyntax: Select Custom Schema** | Choose a custom schema JSON for development or advanced mod support. Most users should keep the built-in schema. | + +When using the server without the VSCode extension, configure your LSP client to run `zerosyntax-lsp` over **stdio** for Zero Hour `.ini` files and set the workspace root to the map or mod folder. See the [language server guide](docs/language-server.md) for all initialization options. + +![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png) + +### Suppressing diagnostics with a pragma + +Add a file-scope comment to disable selected diagnostics for the entire file: + +```ini +; zerosyntax-disable: unknown-field, unresolved-reference +``` -ZeroSyntax v2 is an unofficial community project and is not affiliated with, -endorsed by, or sponsored by Electronic Arts. Command & Conquer and related -names are trademarks of their respective owners. +Place the pragma outside any block, usually at the top of the file. Codes may be separated by commas or spaces, and multiple pragma lines are combined. VSCode also offers **Suppress `` in this file** as a quick fix. A misspelled code produces an `unknown-suppression` hint. + +Available codes are: +- Structure and definitions: `syntax`, `stray-field`, `unknown-block`, + `overrides`, `duplicate-definition`, `unknown-field`. +- Values: `missing-condition`, `missing-value`, `bad-bool`, `non-positive`, + `bad-percent`, `bad-color`, `bad-coord`, `bad-number`, `bad-enum`, `bad-flag`, + `bad-prefixed`. +- References and assets: `unresolved-reference`, `unknown-model`, + `unknown-model-member`, `unknown-audio-file`, `unknown-texture`. +- Modules: `missing-module-tag`, `unknown-module`, `unknown-module-tag`, + `module-wrong-slot`, `duplicate-module-tag`, `editor-default-module`, + `default-modules-not-removed`. +- Map checks: `map-forward-reference`, `map-projectile-object`, + `unreachable-set`. +- Pragmas: `unknown-suppression`. + +![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png) + +## ⚠️ License & Notice +ZeroSyntaxV2 is available under the [MIT License](./LICENSE) + +ZeroSyntaxV2 is an unofficial community project and is not affiliated with, endorsed by, or sponsored by Electronic Arts. Command & Conquer and related names are trademarks of their respective owners. diff --git a/crates/analysis/src/completion.rs b/crates/analysis/src/completion.rs index 2c3965b..6dfa969 100644 --- a/crates/analysis/src/completion.rs +++ b/crates/analysis/src/completion.rs @@ -6,14 +6,14 @@ //! * after `=` -> enum/bitflag members, `Yes`/`No`, module names, or (with the //! workspace index) names of the referenced definition kind. -use zerosyntax_schema::{AudioExtension, ValueType}; +use zerosyntax_schema::{AudioExtension, RefKind, ValueType}; use zerosyntax_syntax::ast::{Block, Field, Module}; use zerosyntax_syntax::{Parse, SyntaxKind, SyntaxNode}; use crate::index::AssetKind; use crate::model::{ - is_model_asset_type, is_model_member_type, model_member_ini_name, models_for_source, - scope_schema, + is_model_asset_type, is_model_member_type, model_member_mode, model_member_names, + models_for_source, scope_schema, }; use crate::{Analyzer, WorkspaceIndex}; @@ -35,6 +35,8 @@ pub struct Completion { pub label: String, pub kind: CompletionKind, pub detail: Option, + /// Optional Markdown shown by an LSP client alongside the selected item. + pub documentation: Option, /// Optional LSP snippet string. When the client supports snippets, the /// server uses this as `insertText` with `InsertTextFormat::SNIPPET` /// instead of the plain `label`. `$0` marks the final cursor position; @@ -52,9 +54,10 @@ pub fn complete( file: Option<&str>, ) -> Vec { let root = parse.syntax(); - let ctx = classify_position(analyzer, &root, offset); + let ctx = classify_position(analyzer, &root, offset, file); match ctx { PosContext::TopLevel => top_level_completions(analyzer), + PosContext::ObjectName => object_name_completions(index), PosContext::FieldKey(scope_node) => field_key_completions(analyzer, &scope_node), PosContext::FieldValue { scope_node, @@ -83,6 +86,11 @@ pub fn complete( enum PosContext { TopLevel, + /// Completing a top-level Object name in a map override layer. Existing + /// objects are useful override targets, but this is deliberately a + /// completion context rather than a Reference value: map files may also + /// introduce entirely new object names. + ObjectName, /// Completing a field/slot keyword inside this scope node. FieldKey(SyntaxNode), /// Completing the value of `key` inside this scope node; `value_index` is @@ -108,7 +116,12 @@ enum PosContext { }, } -fn classify_position(analyzer: &Analyzer, root: &SyntaxNode, offset: u32) -> PosContext { +fn classify_position( + analyzer: &Analyzer, + root: &SyntaxNode, + offset: u32, + file: Option<&str>, +) -> PosContext { let off = rowan::TextSize::from(offset.min(root.text_range().end().into())); let element = root.covering_element(rowan::TextRange::empty(off)); let node = match &element { @@ -252,6 +265,9 @@ fn classify_position(analyzer: &Analyzer, root: &SyntaxNode, offset: u32) -> Pos if offset <= u32::from(kw.text_range().end()) { return PosContext::TopLevel; } + if file.is_some_and(is_override_layer) && kw.text().eq_ignore_ascii_case("Object") { + return PosContext::ObjectName; + } } } return PosContext::FieldKey(block_node); @@ -261,6 +277,26 @@ fn classify_position(analyzer: &Analyzer, root: &SyntaxNode, offset: u32) -> Pos PosContext::TopLevel } +fn is_override_layer(file: &str) -> bool { + file.rsplit(['/', '\\']).next().is_some_and(|name| { + name.eq_ignore_ascii_case("map.ini") || name.eq_ignore_ascii_case("solo.ini") + }) +} + +fn object_name_completions(index: Option<&WorkspaceIndex>) -> Vec { + index + .into_iter() + .flat_map(|idx| idx.override_target_names(RefKind::Object)) + .map(|name| Completion { + label: name.to_string(), + kind: CompletionKind::Reference, + detail: Some("Object (override target)".into()), + documentation: None, + insert: None, + }) + .collect() +} + fn field_key_completions(analyzer: &Analyzer, scope_node: &SyntaxNode) -> Vec { let scope = scope_schema(analyzer, scope_node); let mut out: Vec = scope @@ -270,6 +306,7 @@ fn field_key_completions(analyzer: &Analyzer, scope_node: &SyntaxNode) -> Vec Vec Vec, Option<&str>), index: Option<&WorkspaceIndex>, - source: Option<&zerosyntax_schema::ModelSource>, + field: Option<&Field>, ) -> Option> { let index = index?; if !index.has_model_assets() { return None; } - let ty = token_value_type(ty, value_index); + let (value_index, current_token, first_token) = position; + let ty = field_schema + .value_type + .variant_for_first_token(first_token)? + .token_type_at(value_index)?; + let (ty, prefix) = match ty { + ValueType::Prefixed { prefix, value_type } => (value_type.as_ref(), Some(prefix)), + _ => (ty, None), + }; if is_model_asset_type(ty) { return Some( index @@ -428,6 +486,7 @@ fn model_asset_completions( label: name.to_string(), kind: CompletionKind::W3dModel, detail: Some("W3D model".into()), + documentation: None, insert: None, }) .collect(), @@ -436,33 +495,52 @@ fn model_asset_completions( if !is_model_member_type(ty) { return None; } + let mode = model_member_mode(field_schema.model_member_mode, field); let mut seen = std::collections::HashSet::new(); - let out = models_for_source(analyzer, scope_node, source, index) - .into_iter() - .flat_map(|model| { - index - .model_members(&model) - .map(|member| model_member_ini_name(member).to_string()) - .collect::>() - }) - .filter(|member| seen.insert(member.to_ascii_lowercase())) - .map(|member| Completion { - label: member, - kind: CompletionKind::Reference, - detail: Some("W3D model member".into()), - insert: None, - }) - .collect(); + let out = models_for_source( + analyzer, + scope_node, + field_schema.model_source.as_ref(), + index, + ) + .into_iter() + .flat_map(|model| { + index + .model_members(&model) + .flat_map(|member| { + model_member_names(member, mode) + .into_iter() + .map(move |name| { + ( + name.to_string(), + mode.is_some() && name != member.rsplit('.').next().unwrap_or(member), + ) + }) + }) + .collect::>() + }) + .filter(|(member, _)| seen.insert(member.to_ascii_lowercase())) + .map(|(member, family)| Completion { + insert: prefix + .filter(|prefix| { + !current_token + .and_then(|t| t.split_once(':')) + .is_some_and(|(actual, _)| actual.eq_ignore_ascii_case(prefix)) + }) + .map(|prefix| format!("{prefix}:{member}")), + detail: Some(if family { + "W3D numbered bone family (01, 02, ...)".into() + } else { + "W3D model member".into() + }), + label: member, + kind: CompletionKind::Reference, + documentation: None, + }) + .collect(); Some(out) } -fn token_value_type(ty: &ValueType, value_index: usize) -> &ValueType { - match ty { - ValueType::TokenList { tokens } => tokens.get(value_index).unwrap_or(ty), - _ => ty, - } -} - /// Build a single-token snippet placeholder for a value type, used when /// generating a full-sequence snippet for TokenList or structured types. /// `n` is the tab-stop index (1-based). @@ -472,7 +550,7 @@ fn type_snippet_placeholder(ty: &ValueType, n: usize) -> String { if prefix.eq_ignore_ascii_case("Bone") && matches!( value_type.as_ref(), - ValueType::AsciiString | ValueType::QuotedString + ValueType::AsciiString | ValueType::QuotedString | ValueType::W3dModelMember ) { format!("{prefix}:${{{n}:NONE}}") @@ -624,6 +702,7 @@ fn completions_for_type( label: "".into(), kind: CompletionKind::Value, detail: Some(format!("{} tokens", tokens.len())), + documentation: None, insert: Some(snippet), }, ); @@ -636,18 +715,21 @@ fn completions_for_type( label: "R: G: B:".into(), kind: CompletionKind::Value, detail: Some("color".into()), + documentation: None, insert: Some("R:${1:255} G:${2:255} B:${3:255}".into()), }], ValueType::Coord2D => vec![Completion { label: "X: Y:".into(), kind: CompletionKind::Value, detail: Some("2D coordinate".into()), + documentation: None, insert: Some("X:${1:0} Y:${2:0}".into()), }], ValueType::Coord3D => vec![Completion { label: "X: Y: Z:".into(), kind: CompletionKind::Value, detail: Some("3D coordinate".into()), + documentation: None, insert: Some("X:${1:0} Y:${2:0} Z:${3:0}".into()), }], ValueType::Bool => ["Yes", "No"] @@ -656,6 +738,7 @@ fn completions_for_type( label: v.to_string(), kind: CompletionKind::Value, detail: None, + documentation: None, insert: None, }) .collect(), @@ -668,6 +751,7 @@ fn completions_for_type( label: m.name.clone(), kind: CompletionKind::EnumMember, detail: Some(value_set.clone()), + documentation: None, insert: None, }) .collect() @@ -681,6 +765,7 @@ fn completions_for_type( label: n.to_string(), kind: CompletionKind::Reference, detail: Some(format!("{ref_kind:?}")), + documentation: None, insert: None, }) .collect() @@ -692,6 +777,7 @@ fn completions_for_type( label: n.to_string(), kind: CompletionKind::Reference, detail: Some(format!("{ref_kind:?} (engine builtin)")), + documentation: None, insert: None, })); out @@ -768,6 +854,7 @@ fn asset_completions( label, kind: CompletionKind::Reference, detail: Some(detail.to_string()), + documentation: None, insert: None, }) .collect() @@ -791,6 +878,7 @@ fn top_level_completions(analyzer: &Analyzer) -> Vec { label: b.name.clone(), kind: CompletionKind::Block, detail: Some("block".into()), + documentation: None, insert, } }) @@ -818,6 +906,7 @@ fn module_name_completions( label: m.name.clone(), kind: CompletionKind::Module, detail: Some("module".into()), + documentation: None, insert, } }) @@ -1032,6 +1121,73 @@ mod tests { ); } + #[test] + fn map_object_header_suggests_existing_objects_without_requiring_one() { + let a = Analyzer::embedded(); + let mut index = WorkspaceIndex::new(); + let base = a.parse("Object AmericaVehicleHumvee\nEnd\n"); + index.set_file( + "data/INI/Object.ini", + crate::index::definitions_in(&a, &base, "data/INI/Object.ini"), + ); + let map_only = a.parse("Object MapOnlyObject\nEnd\n"); + index.set_file( + "maps/other/map.ini", + crate::index::definitions_in(&a, &map_only, "maps/other/map.ini"), + ); + + // `NewMapObject` intentionally does not exist in the index: an Object + // header in map.ini may define a new template as well as override one. + let src = "Object NewMapObject\nEnd\n"; + let offset = "Object New".len() as u32; + let out = complete( + &a, + &a.parse(src), + offset, + Some(&index), + Some("maps/map.ini"), + ); + assert!( + out.iter().any(|item| item.label == "AmericaVehicleHumvee"), + "{out:?}" + ); + assert!( + !out.iter().any(|item| item.label == "MapOnlyObject"), + "{out:?}" + ); + + let blank_header = "Object \nEnd\n"; + let blank_out = complete( + &a, + &a.parse(blank_header), + "Object ".len() as u32, + Some(&index), + Some("maps/map.ini"), + ); + assert!( + blank_out + .iter() + .any(|item| item.label == "AmericaVehicleHumvee"), + "{blank_out:?}" + ); + + // This is a completion-only affordance; typing a new name remains + // valid and produces no unknown-reference diagnostic. + assert!(crate::diagnostics::diagnose( + &a, + &a.parse(src), + Some(&index), + Some("maps/map.ini") + ) + .iter() + .all(|diagnostic| diagnostic.code != "unresolved-reference")); + + let non_map = complete(&a, &a.parse(src), offset, Some(&index), Some("Object.ini")); + assert!(!non_map + .iter() + .any(|item| item.label == "AmericaVehicleHumvee")); + } + #[test] fn remove_module_completion_excludes_later_declarations() { let a = Analyzer::embedded(); @@ -1045,6 +1201,25 @@ mod tests { assert!(!out.iter().any(|item| item.label == "ModuleTag_Later")); } + #[test] + fn remove_module_completion_documents_the_defining_module() { + let a = Analyzer::embedded(); + let defs = + a.parse("Object Tank\n Behavior = PhysicsBehavior ModuleTag_Physics\n End\nEnd\n"); + let mut index = WorkspaceIndex::new(); + index.set_file_tags("base.ini", crate::index::module_tags_in(&a, &defs)); + let src = "Object Tank\n RemoveModule \nEnd\n"; + let offset = "Object Tank\n RemoveModule ".len() as u32; + let item = complete(&a, &a.parse(src), offset, Some(&index), Some("map.ini")) + .into_iter() + .find(|item| item.label == "ModuleTag_Physics") + .expect("module tag completion"); + assert_eq!( + item.documentation.as_deref(), + Some("```ini\nBehavior = PhysicsBehavior ModuleTag_Physics\n```") + ); + } + #[test] fn enum_value_suggests_members() { let src = "Weapon AK47\n DeathType = \nEnd\n"; diff --git a/crates/analysis/src/diagnostics.rs b/crates/analysis/src/diagnostics.rs index b6e3a70..1566f6d 100644 --- a/crates/analysis/src/diagnostics.rs +++ b/crates/analysis/src/diagnostics.rs @@ -22,8 +22,8 @@ use zerosyntax_syntax::{Parse, SyntaxKind, SyntaxNode, SyntaxToken}; use crate::index::{AssetKind, ModelMemberStrictness}; use crate::model::{ - is_model_asset_type, is_model_member_type, model_member_matches, models_for_source, - module_fits_slot, scope_schema, ScopeSchema, + is_model_asset_type, is_model_member_type, model_member_matches, model_member_mode, + models_for_source, module_fits_slot, scope_schema, ScopeSchema, }; use crate::{Analyzer, Span, WorkspaceIndex}; @@ -1069,7 +1069,7 @@ impl<'a> Ctx<'a> { Some(tag.text_range().start().into()), ) .iter() - .any(|known| known.eq_ignore_ascii_case(tag_name)) + .any(|known| known.name.eq_ignore_ascii_case(tag_name)) { self.error( &tag, @@ -1093,43 +1093,22 @@ impl<'a> Ctx<'a> { return; } let tokens = field.value_tokens(); - match &schema_field.value_type { - ValueType::W3dModelList => { - for tok in &tokens { - self.validate_model_asset_token( - &schema_field.value_type, - tok, - scope_node, - schema_field.model_source.as_ref(), - ); - } - } - ValueType::TokenList { tokens: specs } => { - let mut i = 0; - for spec in specs { - let Some(tok) = tokens.get(i) else { break }; - let (ty, tok, consumed) = split_prefixed_token(&tokens[i..], spec) - .map(|(ty, tok)| (ty, tok, 2)) - .unwrap_or((spec, tok, 1)); - self.validate_model_asset_token( - ty, - tok, - scope_node, - schema_field.model_source.as_ref(), - ); - i += consumed; - } - } - ty => { - if let Some(tok) = tokens.first() { - let (ty, tok) = split_prefixed_token(&tokens, ty).unwrap_or((ty, tok)); - self.validate_model_asset_token( - ty, - tok, - scope_node, - schema_field.model_source.as_ref(), - ); - } + let input = tokens.iter().map(|t| unquote(t.text())).collect::>(); + let mode = model_member_mode(schema_field.model_member_mode, Some(field)); + for (i, tok) in tokens.iter().enumerate() { + let ty = if matches!(schema_field.value_type, ValueType::W3dModelList) { + Some(&schema_field.value_type) + } else { + schema_field.value_type.token_type_at_input(&input, i) + }; + if let Some(ty) = ty { + self.validate_model_asset_token( + ty, + tok, + scope_node, + schema_field.model_source.as_ref(), + mode, + ); } } } @@ -1197,6 +1176,7 @@ impl<'a> Ctx<'a> { tok: &SyntaxToken, scope_node: &SyntaxNode, source: Option<&zerosyntax_schema::ModelSource>, + mode: Option, ) { let Some(index) = self.index else { return }; let raw = unquote(tok.text()); @@ -1244,7 +1224,7 @@ impl<'a> Ctx<'a> { checked_any_model = true; if index .model_members(&model) - .any(|member| model_member_matches(member, value)) + .any(|member| model_member_matches(member, value, mode)) { if index.model_member_strictness() == ModelMemberStrictness::Compatible { return; @@ -2560,6 +2540,7 @@ End parse_fn: String::new(), doc: None, model_source: None, + model_member_mode: None, }; for parser in eager { field.parse_fn = parser.into(); diff --git a/crates/analysis/src/index.rs b/crates/analysis/src/index.rs index d035d4a..298fd4d 100644 --- a/crates/analysis/src/index.rs +++ b/crates/analysis/src/index.rs @@ -64,6 +64,10 @@ pub struct ModuleTagDefinition { pub object: String, pub name: String, pub span: Span, + /// The defining module header, shown when this tag is offered as a + /// RemoveModule completion. + #[serde(default)] + pub snippet: String, #[serde(default)] pub is_reference: bool, } @@ -86,6 +90,14 @@ struct NameEntry { struct ModuleTagEntry { name: String, location: Location, + snippet: String, +} + +/// A module tag that is valid at a `RemoveModule`/`ReplaceModule` site. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct EffectiveModuleTag<'a> { + pub name: &'a str, + pub snippet: &'a str, } /// Workspace-wide symbol table, grouped by reference kind then name. @@ -574,6 +586,7 @@ impl WorkspaceIndex { .push(ModuleTagEntry { name: tag.name, location, + snippet: tag.snippet, }); } } @@ -648,7 +661,7 @@ impl WorkspaceIndex { name: &str, file: Option<&str>, before: Option, - ) -> Vec<&'a str> { + ) -> Vec> { let mut out = self .object_tags .get(&name.to_ascii_lowercase()) @@ -659,13 +672,25 @@ impl WorkspaceIndex { tag.location.file == file && tag.location.span.start >= before }) }) - .map(|tag| tag.name.as_str()) + .map(|tag| EffectiveModuleTag { + name: tag.name.as_str(), + snippet: tag.snippet.as_str(), + }) .collect::>(); let is_new_override = file.is_some_and(|file| self.is_new_override_object(name, file)); if is_new_override { - out.extend(self.module_tags_for_object("DefaultThingTemplate")); + out.extend( + self.object_tags + .get("defaultthingtemplate") + .into_iter() + .flatten() + .map(|tag| EffectiveModuleTag { + name: tag.name.as_str(), + snippet: tag.snippet.as_str(), + }), + ); let mut seen = std::collections::HashSet::new(); - out.retain(|tag| seen.insert(tag.to_ascii_lowercase())); + out.retain(|tag| seen.insert(tag.name.to_ascii_lowercase())); } out } @@ -782,6 +807,23 @@ impl WorkspaceIndex { .flat_map(|n| n.values().map(|e| e.display.as_str())) } + /// Names that have at least one definition outside a map/solo override + /// layer. These are valid targets for a map Object override; names that + /// exist solely in an override layer are new map-local templates instead. + pub fn override_target_names(&self, kind: RefKind) -> impl Iterator { + self.by_kind + .get(&kind) + .into_iter() + .flat_map(|names| names.values()) + .filter(|entry| { + entry + .locations + .iter() + .any(|loc| !is_override_layer(&loc.file)) + }) + .map(|entry| entry.display.as_str()) + } + /// All reference sites for `name` of `kind` (case-insensitive). pub fn reference_sites(&self, kind: RefKind, name: &str) -> &[Location] { self.sites @@ -1132,11 +1174,19 @@ pub fn module_tags_in(_analyzer: &Analyzer, parse: &Parse) -> Vec { + format!("{} = {} {}", slot.text(), module_name.text(), tag.text()) + } + _ => tag.text().to_string(), + }; out.push(ModuleTagDefinition { object: name_lower.clone(), name: tag.text().to_string(), span: tag.text_range().into(), + snippet, is_reference: false, }); } @@ -1153,6 +1203,7 @@ pub fn module_tags_in(_analyzer: &Analyzer, parse: &Parse) -> Vec bool { } pub(crate) fn model_member_ini_name(member: &str) -> &str { + let member = member.rsplit('.').next().unwrap_or(member); let trimmed = member.trim_end_matches(|c: char| c.is_ascii_digit()); if trimmed.is_empty() { member @@ -160,8 +161,66 @@ pub(crate) fn model_member_ini_name(member: &str) -> &str { } } -pub(crate) fn model_member_matches(member: &str, value: &str) -> bool { - member.eq_ignore_ascii_case(value) || model_member_ini_name(member).eq_ignore_ascii_case(value) +/// Resolve a contextual mode once, using the entire field (including tokens +/// after the cursor). Colon-separated values may occupy one or two tokens. +pub(crate) fn model_member_mode( + mode: Option, + field: Option<&AstField>, +) -> Option { + use zerosyntax_schema::ModelMemberMode; + if mode != Some(ModelMemberMode::RandomBone) { + return mode; + } + let tokens = field.map(AstField::value_tokens).unwrap_or_default(); + for (i, token) in tokens.iter().enumerate() { + let Some((key, value)) = token.text().split_once(':') else { + continue; + }; + if !key.eq_ignore_ascii_case("RandomBone") { + continue; + } + let value = if value.is_empty() { + tokens.get(i + 1).map(|t| t.text()).unwrap_or("") + } else { + value + }; + if value.eq_ignore_ascii_case("Yes") { + return Some(ModelMemberMode::Indexed); + } + if value.eq_ignore_ascii_case("No") { + return Some(ModelMemberMode::Exact); + } + } + // While the controlling value is incomplete, offer both legal forms. + Some(ModelMemberMode::ExactOrIndexed) +} + +pub(crate) fn model_member_names( + member: &str, + mode: Option, +) -> Vec<&str> { + use zerosyntax_schema::ModelMemberMode; + let short = member.rsplit('.').next().unwrap_or(member); + let base = short.strip_suffix("01").filter(|base| !base.is_empty()); + match mode { + None => vec![model_member_ini_name(short)], + Some(ModelMemberMode::Exact) => vec![short], + Some(ModelMemberMode::Indexed) => base.into_iter().collect(), + Some(ModelMemberMode::ExactOrIndexed | ModelMemberMode::RandomBone) => { + std::iter::once(short).chain(base).collect() + } + } +} + +pub(crate) fn model_member_matches( + member: &str, + value: &str, + mode: Option, +) -> bool { + (mode.is_none() && member.eq_ignore_ascii_case(value)) + || model_member_names(member, mode) + .iter() + .any(|name| name.eq_ignore_ascii_case(value)) } /// Models referenced by W3D-model-typed fields visible from `scope_node`: diff --git a/crates/analysis/tests/bone_names.rs b/crates/analysis/tests/bone_names.rs new file mode 100644 index 0000000..aef1658 --- /dev/null +++ b/crates/analysis/tests/bone_names.rs @@ -0,0 +1,47 @@ +use zerosyntax_analysis::{completion::complete, index::ModelAsset, Analyzer, WorkspaceIndex}; + +#[test] +fn bone_completions_deduplicate_aliases_and_preserve_prefix_insertion() { + let analyzer = Analyzer::embedded(); + let mut index = WorkspaceIndex::new(); + index.set_file_models( + "good.w3d", + vec![ModelAsset { + name: "Good".into(), + members: vec![ + "Good.Fire01".into(), + "Fire01".into(), + "FIRE01".into(), + "Fire02".into(), + ], + }], + ); + for (value, expected_insert) in [ + ("$", Some("Bone:Fire01")), + ("Bone:$", None), + ("Bone: $", None), + ("Bone: F$", None), + ("Bone:F$", None), + ] { + let marked = format!("Object Tank\n Draw = W3DModelDraw Tag\n DefaultConditionState\n Model = Good\n End\n End\n Behavior = TransitionDamageFX Damage\n DamagedParticleSystem1 = {value} RandomBone:No PSys:Smoke\n End\nEnd\n"); + let offset = marked.find('$').unwrap(); + let src = marked.replace('$', ""); + let out = complete( + &analyzer, + &analyzer.parse(&src), + offset as u32, + Some(&index), + None, + ); + let matches = out + .iter() + .filter(|item| item.label.eq_ignore_ascii_case("Fire01")) + .collect::>(); + assert_eq!(matches.len(), 1, "{value}: {out:?}"); + assert_eq!(matches[0].insert.as_deref(), expected_insert, "{value}"); + assert!( + !out.iter().any(|item| item.label.contains("Good.")), + "{value}: {out:?}" + ); + } +} diff --git a/crates/analysis/tests/spec.rs b/crates/analysis/tests/spec.rs index 385e9af..317278d 100644 --- a/crates/analysis/tests/spec.rs +++ b/crates/analysis/tests/spec.rs @@ -63,6 +63,14 @@ struct Spec { audio_assets: Vec, #[serde(default)] texture_assets: Vec, + #[serde(default)] + model_assets: Vec, +} + +#[derive(Deserialize)] +struct ModelSpec { + name: String, + members: Vec, } #[derive(Deserialize)] @@ -400,6 +408,16 @@ fn specs_hold() { let mut index = WorkspaceIndex::new(); index.set_file(&name, definitions_in(&analyzer, &parse, &name)); index.set_file_tags(&name, module_tags_in(&analyzer, &parse)); + index.set_file_models( + "spec-models", + spec.model_assets + .iter() + .map(|model| zerosyntax_analysis::index::ModelAsset { + name: model.name.clone(), + members: model.members.clone(), + }) + .collect(), + ); index.set_file_assets( "spec-assets", spec.audio_assets diff --git a/crates/analysis/tests/spec/BoneNames.ini b/crates/analysis/tests/spec/BoneNames.ini new file mode 100644 index 0000000..43ce254 --- /dev/null +++ b/crates/analysis/tests/spec/BoneNames.ini @@ -0,0 +1,24 @@ +ParticleSystem Smoke +End +Object Tank + Draw = W3DModelDraw Tag + DefaultConditionState + Model = Good + ParticleSysBone = $1Fire01 Smoke + ParticleSysBone = Fire Smoke + WeaponFireFXBone = PRIMARY $2Fire + WeaponRecoilBone = SECONDARY Fire02 + ParticleSysBone = fire02 Smoke + End + End + Behavior = TransitionDamageFX DamageTag + DamagedParticleSystem1 = Bone:$3Fire01 RandomBone:No PSys:Smoke + DamagedParticleSystem2 = Bone: $4Fire RandomBone: Yes PSys:Smoke + DamagedParticleSystem3 = Bone:Fire RandomBone:No PSys:Smoke + DamagedParticleSystem4 = Bone:Solo RandomBone:Yes PSys:Smoke + End + Behavior = BoneFXUpdate BoneTag + DamagedParticleSystem1 = Bone:$5Fire02 OnlyOnce:No 0 0 PSys:Smoke + DamagedParticleSystem2 = Bone: Fire OnlyOnce:No 0 0 PSys:Smoke + End +End diff --git a/crates/analysis/tests/spec/BoneNames.spec.toml b/crates/analysis/tests/spec/BoneNames.spec.toml new file mode 100644 index 0000000..5ca9733 --- /dev/null +++ b/crates/analysis/tests/spec/BoneNames.spec.toml @@ -0,0 +1,90 @@ +no_errors = true + +[[model_assets]] +name = "Good" +members = ["Fire01", "Fire02", "Good.Fire01", "Good.Fire02", "Fire1", "Solo"] + +[[complete]] +at = "$1" +includes = ["Fire01", "Fire02", "Fire1", "Solo"] +excludes = ["Fire", "Good.Fire01", "Good.Fire"] + +[[complete]] +at = "$2" +includes = ["Fire", "Fire01", "Fire02", "Solo"] +excludes = ["Good.Fire", "Good.Fire01"] + +[[complete]] +at = "$3" +includes = ["Fire01", "Fire02"] +excludes = ["Fire", "Good.Fire01"] + +[[complete]] +at = "$4" +includes = ["Fire"] +excludes = ["Fire01", "Fire02", "Solo", "Good.Fire"] + +[[diag]] +severity = "warning" +code = "unknown-model-member" +on = "Fire" +nth = 1 + +[[diag]] +severity = "warning" +code = "unknown-model-member" +on = "Bone:Fire" + +[[complete]] +at = "$5" +includes = ["Fire01", "Fire02"] +excludes = ["Fire", "Good.Fire01"] + +[[diag]] +severity = "warning" +code = "unknown-model-member" +on = "Fire01" +absent = true + +[[diag]] +severity = "warning" +code = "unknown-model-member" +on = "Fire" +nth = 2 +absent = true + +[[diag]] +severity = "warning" +code = "unknown-model-member" +on = "Fire02" +absent = true + +[[diag]] +severity = "warning" +code = "unknown-model-member" +on = "fire02" +absent = true + +[[diag]] +severity = "warning" +code = "unknown-model-member" +on = "Bone:Fire01" +absent = true + +[[diag]] +severity = "warning" +code = "unknown-model-member" +on = "Fire" +nth = 3 +absent = true + +[[diag]] +severity = "warning" +code = "unknown-model-member" +on = "Bone:Solo" + +[[diag]] +severity = "warning" +code = "unknown-model-member" +on = "Fire" +nth = 4 diff --git a/crates/schema/schema.json b/crates/schema/schema.json index 29c8c19..07e0e01 100644 --- a/crates/schema/schema.json +++ b/crates/schema/schema.json @@ -18562,6 +18562,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -18579,6 +18580,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -18596,6 +18598,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -18613,6 +18616,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -18630,6 +18634,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -18697,6 +18702,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -18827,6 +18833,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -18844,6 +18851,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -18861,6 +18869,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -18878,6 +18887,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -18895,6 +18905,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -18962,6 +18973,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -19095,6 +19107,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -19112,6 +19125,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -19129,6 +19143,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -19146,6 +19161,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -19163,6 +19179,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -19230,6 +19247,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -21267,6 +21285,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -21305,6 +21324,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -21343,6 +21363,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -21381,6 +21402,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -21419,6 +21441,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -21457,6 +21480,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -21495,6 +21519,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -21533,6 +21558,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -21571,6 +21597,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -21609,6 +21636,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -21647,6 +21675,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -21685,6 +21714,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -21723,6 +21753,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -21761,6 +21792,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -21799,6 +21831,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -21837,6 +21870,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -21875,6 +21909,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -21913,6 +21948,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -21951,6 +21987,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -21989,6 +22026,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -22027,6 +22065,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -22065,6 +22104,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -22103,6 +22143,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -22141,6 +22182,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -22179,6 +22221,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -22217,6 +22260,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -22255,6 +22299,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -22293,6 +22338,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -22331,6 +22377,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -22369,6 +22416,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -22407,6 +22455,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -22445,6 +22494,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseFXList", + "model_member_mode": "exact", "doc": null }, { @@ -22483,6 +22533,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -22521,6 +22572,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -22559,6 +22611,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -22597,6 +22650,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -22635,6 +22689,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -22673,6 +22728,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -22711,6 +22767,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -22749,6 +22806,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -22787,6 +22845,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -22825,6 +22884,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -22863,6 +22923,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -22901,6 +22962,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -22939,6 +23001,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -22977,6 +23040,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -23015,6 +23079,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -23053,6 +23118,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -23091,6 +23157,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -23129,6 +23196,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -23167,6 +23235,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -23205,6 +23274,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -23243,6 +23313,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -23281,6 +23352,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -23319,6 +23391,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -23357,6 +23430,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -23395,6 +23469,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -23433,6 +23508,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -23471,6 +23547,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -23509,6 +23586,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -23547,6 +23625,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -23585,6 +23664,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -23623,6 +23703,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -23661,6 +23742,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseObjectCreationList", + "model_member_mode": "exact", "doc": null }, { @@ -23699,6 +23781,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -23737,6 +23820,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -23775,6 +23859,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -23813,6 +23898,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -23851,6 +23937,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -23889,6 +23976,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -23927,6 +24015,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -23965,6 +24054,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -24003,6 +24093,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -24041,6 +24132,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -24079,6 +24171,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -24117,6 +24210,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -24155,6 +24249,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -24193,6 +24288,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -24231,6 +24327,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -24269,6 +24366,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -24307,6 +24405,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -24345,6 +24444,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -24383,6 +24483,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -24421,6 +24522,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -24459,6 +24561,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -24497,6 +24600,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -24535,6 +24639,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -24573,6 +24678,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -24611,6 +24717,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -24649,6 +24756,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -24687,6 +24795,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -24725,6 +24834,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -24763,6 +24873,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -24801,6 +24912,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -24839,6 +24951,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null }, { @@ -24877,6 +24990,7 @@ ] }, "parse_fn": "BoneFXUpdateModuleData::parseParticleSystem", + "model_member_mode": "exact", "doc": null } ], @@ -46239,7 +46353,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -46296,6 +46410,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -46310,7 +46425,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -46367,6 +46482,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -46381,7 +46497,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -46438,6 +46554,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -46452,7 +46569,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -46509,6 +46626,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -46523,7 +46641,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -46580,6 +46698,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -46594,7 +46713,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -46651,6 +46770,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -46665,7 +46785,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -46722,6 +46842,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -46736,7 +46857,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -46793,6 +46914,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -46807,7 +46929,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -46864,6 +46986,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -46878,7 +47001,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -46935,6 +47058,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -46949,7 +47073,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -47006,6 +47130,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -47020,7 +47145,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -47077,6 +47202,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -47091,7 +47217,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -47148,6 +47274,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -47162,7 +47289,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -47219,6 +47346,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -47233,7 +47361,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -47290,6 +47418,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -47304,7 +47433,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -47361,6 +47490,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -47375,7 +47505,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -47432,6 +47562,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -47446,7 +47577,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -47503,6 +47634,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -47517,7 +47649,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -47574,6 +47706,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -47588,7 +47721,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -47645,6 +47778,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -47659,7 +47793,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -47716,6 +47850,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -47730,7 +47865,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -47787,6 +47922,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -47801,7 +47937,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -47858,6 +47994,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -47872,7 +48009,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -47929,6 +48066,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -47943,7 +48081,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -48000,6 +48138,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -48014,7 +48153,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -48071,6 +48210,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -48085,7 +48225,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -48142,6 +48282,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -48156,7 +48297,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -48213,6 +48354,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -48227,7 +48369,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -48284,6 +48426,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -48298,7 +48441,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -48355,6 +48498,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -48369,7 +48513,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -48426,6 +48570,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -48440,7 +48585,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -48497,6 +48642,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -48511,7 +48657,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -48568,6 +48714,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -48582,7 +48729,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -48639,6 +48786,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -48653,7 +48801,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -48710,6 +48858,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -48724,7 +48873,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -48781,6 +48930,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseFXList", + "model_member_mode": "random_bone", "doc": null }, { @@ -48795,7 +48945,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -48852,6 +49002,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -48866,7 +49017,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -48923,6 +49074,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -48937,7 +49089,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -48994,6 +49146,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -49008,7 +49161,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -49065,6 +49218,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -49079,7 +49233,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -49136,6 +49290,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -49150,7 +49305,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -49207,6 +49362,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -49221,7 +49377,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -49278,6 +49434,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -49292,7 +49449,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -49349,6 +49506,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -49363,7 +49521,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -49420,6 +49578,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -49434,7 +49593,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -49491,6 +49650,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -49505,7 +49665,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -49562,6 +49722,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -49576,7 +49737,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -49633,6 +49794,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -49647,7 +49809,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -49704,6 +49866,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -49718,7 +49881,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -49775,6 +49938,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -49789,7 +49953,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -49846,6 +50010,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -49860,7 +50025,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -49917,6 +50082,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -49931,7 +50097,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -49988,6 +50154,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -50002,7 +50169,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -50059,6 +50226,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -50073,7 +50241,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -50130,6 +50298,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -50144,7 +50313,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -50201,6 +50370,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -50215,7 +50385,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -50272,6 +50442,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -50286,7 +50457,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -50343,6 +50514,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -50357,7 +50529,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -50414,6 +50586,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -50428,7 +50601,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -50485,6 +50658,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -50499,7 +50673,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -50556,6 +50730,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -50570,7 +50745,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -50627,6 +50802,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -50641,7 +50817,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -50698,6 +50874,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -50712,7 +50889,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -50769,6 +50946,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -50783,7 +50961,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -50840,6 +51018,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -50854,7 +51033,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -50911,6 +51090,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -50925,7 +51105,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -50982,6 +51162,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -50996,7 +51177,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -51053,6 +51234,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -51067,7 +51249,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -51124,6 +51306,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -51138,7 +51321,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -51195,6 +51378,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -51209,7 +51393,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -51266,6 +51450,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -51280,7 +51465,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -51337,6 +51522,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseObjectCreationList", + "model_member_mode": "random_bone", "doc": null }, { @@ -51351,7 +51537,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -51408,6 +51594,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -51422,7 +51609,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -51479,6 +51666,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -51493,7 +51681,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -51550,6 +51738,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -51564,7 +51753,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -51621,6 +51810,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -51635,7 +51825,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -51692,6 +51882,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -51706,7 +51897,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -51763,6 +51954,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -51777,7 +51969,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -51834,6 +52026,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -51848,7 +52041,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -51905,6 +52098,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -51919,7 +52113,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -51976,6 +52170,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -51990,7 +52185,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -52047,6 +52242,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -52061,7 +52257,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -52118,6 +52314,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -52132,7 +52329,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -52189,6 +52386,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -52203,7 +52401,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -52260,6 +52458,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -52274,7 +52473,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -52331,6 +52530,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -52345,7 +52545,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -52402,6 +52602,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -52416,7 +52617,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -52473,6 +52674,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -52487,7 +52689,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -52544,6 +52746,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -52558,7 +52761,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -52615,6 +52818,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -52629,7 +52833,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -52686,6 +52890,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -52700,7 +52905,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -52757,6 +52962,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -52771,7 +52977,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -52828,6 +53034,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -52842,7 +53049,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -52899,6 +53106,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -52913,7 +53121,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -52970,6 +53178,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -52984,7 +53193,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -53041,6 +53250,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -53055,7 +53265,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -53112,6 +53322,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -53126,7 +53337,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -53183,6 +53394,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -53197,7 +53409,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -53254,6 +53466,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -53268,7 +53481,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -53325,6 +53538,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -53339,7 +53553,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -53396,6 +53610,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -53410,7 +53625,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -53467,6 +53682,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -53481,7 +53697,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -53538,6 +53754,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -53552,7 +53769,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -53609,6 +53826,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -53623,7 +53841,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -53680,6 +53898,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -53694,7 +53913,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -53751,6 +53970,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -53765,7 +53985,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -53822,6 +54042,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null }, { @@ -53836,7 +54057,7 @@ "kind": "prefixed", "prefix": "Bone", "value_type": { - "kind": "ascii_string" + "kind": "w3d_model_member" } }, { @@ -53893,6 +54114,7 @@ ] }, "parse_fn": "TransitionDamageFXModuleData::parseParticleSystem", + "model_member_mode": "random_bone", "doc": null } ], @@ -55507,6 +55729,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -55524,6 +55747,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -55541,6 +55765,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -55558,6 +55783,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -55575,6 +55801,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -55642,6 +55869,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -55772,6 +56000,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -55789,6 +56018,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -55806,6 +56036,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -55823,6 +56054,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -55840,6 +56072,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -55907,6 +56140,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -56040,6 +56274,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -56057,6 +56292,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -56074,6 +56310,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -56091,6 +56328,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -56108,6 +56346,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -56175,6 +56414,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -56562,6 +56802,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -56579,6 +56820,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -56596,6 +56838,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -56613,6 +56856,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -56630,6 +56874,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -56697,6 +56942,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -56827,6 +57073,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -56844,6 +57091,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -56861,6 +57109,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -56878,6 +57127,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -56895,6 +57145,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -56962,6 +57213,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -57095,6 +57347,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -57112,6 +57365,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -57129,6 +57383,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -57146,6 +57401,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -57163,6 +57419,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -57230,6 +57487,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -57537,6 +57795,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -57554,6 +57813,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -57571,6 +57831,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -57588,6 +57849,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -57605,6 +57867,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -57672,6 +57935,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -57802,6 +58066,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -57819,6 +58084,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -57836,6 +58102,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -57853,6 +58120,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -57870,6 +58138,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -57937,6 +58206,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -58070,6 +58340,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -58087,6 +58358,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -58104,6 +58376,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -58121,6 +58394,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -58138,6 +58412,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -58205,6 +58480,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -58633,6 +58909,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -58650,6 +58927,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -58667,6 +58945,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -58684,6 +58963,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -58701,6 +58981,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -58768,6 +59049,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -58898,6 +59180,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -58915,6 +59198,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -58932,6 +59216,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -58949,6 +59234,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -58966,6 +59252,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -59033,6 +59320,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -59166,6 +59454,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -59183,6 +59472,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -59200,6 +59490,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -59217,6 +59508,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -59234,6 +59526,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -59301,6 +59594,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -59729,6 +60023,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -59746,6 +60041,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -59763,6 +60059,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -59780,6 +60077,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -59797,6 +60095,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -59864,6 +60163,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -59994,6 +60294,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -60011,6 +60312,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -60028,6 +60330,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -60045,6 +60348,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -60062,6 +60366,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -60129,6 +60434,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -60262,6 +60568,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -60279,6 +60586,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -60296,6 +60604,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -60313,6 +60622,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -60330,6 +60640,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -60397,6 +60708,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -60748,6 +61060,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -60765,6 +61078,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -60782,6 +61096,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -60799,6 +61114,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -60816,6 +61132,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -60883,6 +61200,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -61013,6 +61331,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -61030,6 +61349,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -61047,6 +61367,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -61064,6 +61385,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -61081,6 +61403,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -61148,6 +61471,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -61281,6 +61605,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -61298,6 +61623,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -61315,6 +61641,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -61332,6 +61659,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -61349,6 +61677,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -61416,6 +61745,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -61689,6 +62019,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -61706,6 +62037,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -61723,6 +62055,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -61740,6 +62073,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -61757,6 +62091,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -61824,6 +62159,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -61954,6 +62290,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -61971,6 +62308,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -61988,6 +62326,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -62005,6 +62344,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -62022,6 +62362,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -62089,6 +62430,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -62222,6 +62564,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -62239,6 +62582,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -62256,6 +62600,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -62273,6 +62618,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -62290,6 +62636,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -62357,6 +62704,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -62664,6 +63012,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -62681,6 +63030,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -62698,6 +63048,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -62715,6 +63066,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -62732,6 +63084,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -62799,6 +63152,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -62929,6 +63283,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -62946,6 +63301,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -62963,6 +63319,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -62980,6 +63337,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -62997,6 +63355,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -63064,6 +63423,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -63197,6 +63557,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -63214,6 +63575,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -63231,6 +63593,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -63248,6 +63611,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -63265,6 +63629,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -63332,6 +63697,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -63746,6 +64112,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -63763,6 +64130,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -63780,6 +64148,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -63797,6 +64166,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -63814,6 +64184,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -63881,6 +64252,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -64011,6 +64383,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -64028,6 +64401,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -64045,6 +64419,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -64062,6 +64437,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -64079,6 +64455,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -64146,6 +64523,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -64279,6 +64657,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -64296,6 +64675,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -64313,6 +64693,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -64330,6 +64711,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -64347,6 +64729,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -64414,6 +64797,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -65007,6 +65391,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -65024,6 +65409,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -65041,6 +65427,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -65058,6 +65445,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -65075,6 +65463,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -65142,6 +65531,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -65272,6 +65662,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -65289,6 +65680,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -65306,6 +65698,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -65323,6 +65716,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -65340,6 +65734,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -65407,6 +65802,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { @@ -65540,6 +65936,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -65557,6 +65954,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -65574,6 +65972,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -65591,6 +65990,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -65608,6 +66008,7 @@ ] }, "parse_fn": "parseWeaponBoneName", + "model_member_mode": "exact_or_indexed", "doc": null }, { @@ -65675,6 +66076,7 @@ ] }, "parse_fn": "parseParticleSysBone", + "model_member_mode": "exact", "doc": null }, { diff --git a/crates/schema/src/lib.rs b/crates/schema/src/lib.rs index 76686c1..cf85d13 100644 --- a/crates/schema/src/lib.rs +++ b/crates/schema/src/lib.rs @@ -159,6 +159,22 @@ pub struct Field { pub doc: Option, #[serde(default)] pub model_source: Option, + /// Engine lookup semantics for the model-member token in this field. + /// Omitted for fields whose lookup has not yet been audited. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub model_member_mode: Option, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum ModelMemberMode { + Exact, + /// Enumerates base01, base02, ... starting at 01. + Indexed, + /// Enumerates numbered bones, with an unadorned-name fallback. + ExactOrIndexed, + /// TransitionDamageFX: selected by the field's RandomBone:Yes/No value. + RandomBone, } /// Selects the Object whose W3D models own a model-member field. @@ -529,6 +545,7 @@ mod tests { parse_fn: "parseReal".into(), doc: None, model_source: None, + model_member_mode: None, }], module_slots: vec![], sub_blocks: vec![], @@ -1435,7 +1452,7 @@ mod tests { &ValueType::OneOf { variants: vec![ token_list(vec![ - prefixed("Bone", ValueType::AsciiString), + prefixed("Bone", ValueType::W3dModelMember), prefixed("RandomBone", ValueType::Bool), prefixed(prefix, reference(ref_kind)), ]), diff --git a/crates/server/src/cache.rs b/crates/server/src/cache.rs index 7ac239b..e5f35fa 100644 --- a/crates/server/src/cache.rs +++ b/crates/server/src/cache.rs @@ -17,7 +17,7 @@ const STORE_FILE: &str = "index-v1.sqlite3"; const STORE_VERSION: i64 = 1; /// Bump when `CachedEntry` serialization or any extractor feeding it changes. /// SQL layout changes instead bump `STORE_VERSION` and the store filename. -const PRODUCER_ABI: &[u8] = b"zerosyntax-physical-input-v2"; +const PRODUCER_ABI: &[u8] = b"zerosyntax-physical-input-v3"; const BUSY_TIMEOUT: Duration = Duration::from_secs(2); const TOUCH_INTERVAL_SECS: i64 = 24 * 60 * 60; const MAX_AGE_SECS: i64 = 30 * 24 * 60 * 60; diff --git a/crates/server/src/convert.rs b/crates/server/src/convert.rs index b3cd75d..e5ae144 100644 --- a/crates/server/src/convert.rs +++ b/crates/server/src/convert.rs @@ -177,6 +177,12 @@ pub fn to_lsp_completion(c: Completion, snippets_supported: bool) -> CompletionI "model": c.label, }) }); + let documentation = c.documentation.map(|value| { + Documentation::MarkupContent(MarkupContent { + kind: MarkupKind::Markdown, + value, + }) + }); CompletionItem { label: c.label, kind: Some(match c.kind { @@ -189,6 +195,7 @@ pub fn to_lsp_completion(c: Completion, snippets_supported: bool) -> CompletionI CompletionKind::W3dModel => CompletionItemKind::REFERENCE, }), detail: c.detail, + documentation, data, insert_text, insert_text_format, @@ -354,6 +361,7 @@ mod tests { label: "AVTank".into(), kind: CompletionKind::W3dModel, detail: Some("W3D model".into()), + documentation: None, insert: None, }, false, @@ -365,6 +373,26 @@ mod tests { assert!(item.documentation.is_none()); } + #[test] + fn completion_documentation_is_sent_as_markdown() { + let item = to_lsp_completion( + Completion { + label: "ModuleTag_Physics".into(), + kind: CompletionKind::Reference, + detail: Some("module tag".into()), + documentation: Some( + "```ini\nBehavior = PhysicsBehavior ModuleTag_Physics\n```".into(), + ), + insert: None, + }, + false, + ); + assert!(matches!( + item.documentation, + Some(Documentation::MarkupContent(_)) + )); + } + #[test] fn splice_reproduces_next_from_prev() { let tok = |dl, ds, len, ty| SemanticToken { diff --git a/crates/server/tests/e2e.py b/crates/server/tests/e2e.py index 9f1c9f1..2fa9a5c 100644 --- a/crates/server/tests/e2e.py +++ b/crates/server/tests/e2e.py @@ -482,6 +482,23 @@ def latest_burst_diag(message): assert targets[0]["range"]["start"]["line"] == 1, targets print("OK: RemoveModule tag resolves to its module definition") + # A RemoveModule completion identifies the module that owns each tag. + send({"jsonrpc": "2.0", "id": 69, "method": "textDocument/completion", + "params": {"textDocument": {"uri": module_map_uri}, + "position": {"line": 3, "character": len(" RemoveModule ")}}}) + module_completion = wait_for( + lambda m: m.get("id") == 69 and "result" in m, + "RemoveModule completion result", + ) + module_items = module_completion["result"] + if isinstance(module_items, dict): + module_items = module_items.get("items", []) + target_item = next(item for item in module_items if item["label"] == "ModuleTag_Target") + assert target_item["documentation"]["kind"] == "markdown", target_item + assert target_item["documentation"]["value"].startswith("```ini\n"), target_item + assert "Behavior = DestroyDie ModuleTag_Target" in target_item["documentation"]["value"] + print("OK: RemoveModule completion previews its defining module") + send({"jsonrpc": "2.0", "id": 70, "method": "textDocument/references", "params": {"textDocument": {"uri": module_map_uri}, "position": {"line": 3, "character": 16}, diff --git a/editors/vscode/README.md b/editors/vscode/README.md index 49cb224..892d8b1 100644 --- a/editors/vscode/README.md +++ b/editors/vscode/README.md @@ -26,8 +26,17 @@ Open the folder containing your project rather than a single file. ZeroSyntax indexes the workspace so definitions, references, rename, and completion work across its INI files. -When editing `map.ini` or `solo.ini`, configure **ZeroSyntax v2: Base Ini Roots** -with any game or mod folders and `.big` archives that load before the map. This +When you first open an INI file, click **Select Game Folder** in the setup +notification and browse to your Zero Hour installation folder. You can also run +**ZeroSyntax: Select Game Folder** from the Command Palette at any time to add +a game or mod folder. The folder is added to `zerosyntax.baseIniRoots` without +replacing existing entries, and indexing starts automatically without a restart. +It is saved in user settings for reuse across projects, unless the workspace +already overrides this setting; in that case, the workspace setting is updated. + +Use **ZeroSyntax v2: Base Ini Roots** in Settings to manage additional roots, +reorder or remove entries, or add individual `.big` archives that load before +`map.ini` or `solo.ini`. This prevents false unresolved-reference warnings and enables W3D model, bone, WAV/MP3 audio, and TGA/DDS texture checks. Configure every loaded game/mod root; asset warnings activate per kind once any matching asset is indexed. DDS-only diff --git a/editors/vscode/package-lock.json b/editors/vscode/package-lock.json index badd0a5..de24600 100644 --- a/editors/vscode/package-lock.json +++ b/editors/vscode/package-lock.json @@ -1,28 +1,28 @@ { "name": "zerosyntax-vscode", - "version": "1.0.4", + "version": "1.0.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "zerosyntax-vscode", - "version": "1.0.4", + "version": "1.0.5", "license": "MIT", "dependencies": { - "vscode-languageclient": "^10.1.0" + "vscode-languageclient": "^10.1.1" }, "devDependencies": { "@types/mocha": "^10.0.10", - "@types/node": "^26.2.0", - "@types/vscode": "^1.84.0", + "@types/node": "^26.4.1", + "@types/vscode": "^1.136.0", "@vscode/test-electron": "^3.1.0", "@vscode/vsce": "^3.9.2", "esbuild": "^0.28.2", - "mocha": "^11.8.0", + "mocha": "^12.0.0", "typescript": "^7.0.2" }, "engines": { - "vscode": "^1.84.0" + "vscode": "^1.136.0" } }, "node_modules/@azu/format-text": { @@ -677,80 +677,6 @@ "node": ">=18" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -789,17 +715,6 @@ "node": ">= 8" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@secretlint/config-creator": { "version": "10.2.2", "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-10.2.2.tgz", @@ -1079,9 +994,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", - "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", + "version": "26.4.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.1.tgz", + "integrity": "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA==", "dev": true, "license": "MIT", "dependencies": { @@ -1103,9 +1018,9 @@ "license": "MIT" }, "node_modules/@types/vscode": { - "version": "1.125.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.125.0.tgz", - "integrity": "sha512-0icm/ZQAaism87P0ekHqi4/Ju9du+Tm0RUW+y7vqRsxY2cY0FNRX1nAnaW7nT6npPt2tfHiheZ55Zm9UhqonFA==", + "version": "1.136.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.136.0.tgz", + "integrity": "sha512-DO746VAfnHk8+Y8CsUKYMm2rjWD0mxEuYBZI+ssaqJY7yvLJPf9lcwxSSz9W60gbdvdcGyvHQxHTyByfF9mVqg==", "dev": true, "license": "MIT" }, @@ -1871,15 +1786,15 @@ "license": "BSD-2-Clause" }, "node_modules/brace-expansion": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", - "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/braces": { @@ -1992,19 +1907,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2067,16 +1969,16 @@ } }, "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "dev": true, "license": "MIT", "dependencies": { - "readdirp": "^4.0.1" + "readdirp": "^5.0.0" }, "engines": { - "node": ">= 14.16.0" + "node": ">= 20.19.0" }, "funding": { "url": "https://paulmillr.com/funding/" @@ -2119,44 +2021,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/cockatiel": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.2.1.tgz", @@ -2217,21 +2081,6 @@ "dev": true, "license": "MIT" }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/css-select": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", @@ -2280,19 +2129,6 @@ } } }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -2386,9 +2222,9 @@ } }, "node_modules/diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-9.0.0.tgz", + "integrity": "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -2469,13 +2305,6 @@ "node": ">= 0.4" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -2652,29 +2481,6 @@ "@esbuild/win32-x64": "0.28.2" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -2767,33 +2573,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/form-data": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", @@ -2844,16 +2623,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, "node_modules/get-east-asian-width": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", @@ -3038,16 +2807,6 @@ "node": ">= 0.4" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, "node_modules/hosted-git-info": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", @@ -3303,16 +3062,6 @@ "node": ">=8" } }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -3349,13 +3098,6 @@ "dev": true, "license": "MIT" }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, "node_modules/istextorbinary": { "version": "9.5.0", "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-9.5.0.tgz", @@ -3374,22 +3116,6 @@ "url": "https://bevry.me/fund" } }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3651,23 +3377,6 @@ "dev": true, "license": "MIT" }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -3814,12 +3523,12 @@ } }, "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.5" + "brace-expansion": "^5.0.8" }, "engines": { "node": "18 || 20 || >=22" @@ -3858,119 +3567,57 @@ "optional": true }, "node_modules/mocha": { - "version": "11.8.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.8.0.tgz", - "integrity": "sha512-VyCeUdGN3A9lmCTTgG4yuvY9ixxaDk+xt2R/7/+1AP6EqNG+G9OKkzBwhVtVYoNX8YsxNSgAl8mOv3IAeOpFbw==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-12.0.0.tgz", + "integrity": "sha512-NYNh5IFt6WYqm9bi4601m7vix8MZdXC0DwS4gY6WhXO2RgJWhivhISVmq1oklCif18OSI9l+vx5Mdm5oh1XGiQ==", "dev": true, "license": "MIT", "dependencies": { "browser-stdout": "^1.3.1", - "chokidar": "^4.0.1", + "chokidar": "^5.0.0", "debug": "^4.3.5", - "diff": "^7.0.0", - "escape-string-regexp": "^4.0.0", + "diff": "^9.0.0", "find-up": "^5.0.0", - "glob": "^10.4.5", - "he": "^1.2.0", + "glob": "^13.0.0", "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^9.0.5", + "is-unicode-supported": "^0.1.0", + "js-yaml": "^5.0.0", + "minimatch": "^10.2.2", "ms": "^2.1.3", "picocolors": "^1.1.1", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", + "serialize-javascript": "^7.0.2", + "strip-json-comments": "^5.0.3", "supports-color": "^8.1.1", - "workerpool": "^9.2.0", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1", - "yargs-unparser": "^2.0.0" + "workerpool": "^10.0.0" }, "bin": { - "_mocha": "bin/_mocha", "mocha": "bin/mocha.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/mocha/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", - "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", + "node_modules/mocha/node_modules/js-yaml": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.4.1.tgz", + "integrity": "sha512-28R/k+NAjeuf7+CKlTxWZVExJGwVVLwY06DgEnOMz2gEpfNkDcD7QvyiVPT0xy0XXhU8vHsd4Ot42OOPdJG7dQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "argparse": "^2.0.1" }, "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "js-yaml": "bin/js-yaml.mjs" } }, "node_modules/mocha/node_modules/supports-color": { @@ -4304,13 +3951,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -4419,16 +4059,6 @@ "node": ">=8" } }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/path-scurry": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", @@ -4602,16 +4232,6 @@ ], "license": "MIT" }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -4723,29 +4343,19 @@ "license": "MIT" }, "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", "dev": true, "license": "MIT", "engines": { - "node": ">= 14.18.0" + "node": ">= 20.19.0" }, "funding": { "type": "individual", "url": "https://paulmillr.com/funding/" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -4894,13 +4504,13 @@ } }, "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.1.1.tgz", + "integrity": "sha512-k3CMsaIvvdSwm8oLB4MXSl0wH2/cwlH7xGcnRd2DaeRmBkbzYmyT8j0tsX60DwD1eRwHTpNpH8ljKu9oUT1MeQ==", "dev": true, "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" + "engines": { + "node": ">=20.0.0" } }, "node_modules/setimmediate": { @@ -4910,29 +4520,6 @@ "dev": true, "license": "MIT" }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/side-channel": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", @@ -5183,45 +4770,6 @@ "node": ">=8" } }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/string-width/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -5261,38 +4809,14 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", + "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -5673,49 +5197,49 @@ } }, "node_modules/vscode-jsonrpc": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.1.tgz", - "integrity": "sha512-rfuA6T75H6m5EkbhtEPzre9pT0HPcDI2MMy4+nPFIBks5J8JBAUHD4tRYSgaBOijIEC7SRkC1kKyXTLqbmh9jw==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.2.tgz", + "integrity": "sha512-SbQSV9yRemARxeXw6LU5sS6Zq0e9/DgCCX5yelH263ZQWukbTk8EF8fjTrr1dziasf4GwlJbvTwFnTrnQFWZXQ==", "license": "MIT", "engines": { "node": ">=14.0.0" } }, "node_modules/vscode-languageclient": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-10.1.0.tgz", - "integrity": "sha512-XXRx6lqVitQy/oOLr9MfNYRG+MbQkhXkDaxbQMiKxEm8zZNfheRFUKNb8UYNh2stn9btl2wQM5wZFJjJvoc+jA==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-10.1.1.tgz", + "integrity": "sha512-EbwIF+KUbwxD5jumQNpiBCv7BJM3Rierv4hcbw/p5jdsnwFFpR/B2VmwqfH4p5XsK46nkH3ugA3WtwJekHHJEw==", "license": "MIT", "dependencies": { - "minimatch": "^10.2.5", + "minimatch": "^10.2.6", "semver": "^7.8.1", - "vscode-languageserver-protocol": "3.18.2", - "vscode-languageserver-textdocument": "1.0.13" + "vscode-languageserver-protocol": "3.18.3", + "vscode-languageserver-textdocument": "1.0.14" }, "engines": { "vscode": "^1.91.0" } }, "node_modules/vscode-languageserver-protocol": { - "version": "3.18.2", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.18.2.tgz", - "integrity": "sha512-XRyDbT0Pp3sSNti3JmxVEUMySWCSi1hhM+/KUlCy1hV1zmrqpM1OwO12EAki8blhmLuIMpaJrYbo0OzGVfK2Qg==", + "version": "3.18.3", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.18.3.tgz", + "integrity": "sha512-DF49+WeV5py4zO5hhobp60jjsDSK0lAqA0OuKBLBvp423HPWQcCbhZz3JgyfIewsEz2f8U+X75xNIFHdiXZm2w==", "license": "MIT", "dependencies": { - "vscode-jsonrpc": "9.0.1", - "vscode-languageserver-types": "3.18.0" + "vscode-jsonrpc": "9.0.2", + "vscode-languageserver-types": "3.18.3" } }, "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.13.tgz", - "integrity": "sha512-nx0ZHwMGIsVkzFG3/VLeJYBLTaFBRuNdGDvevvjuoayU5EOS2fEYazOhtCM3PI9ClMMg5igc0uwXtAq4tJj+Dw==", + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.14.tgz", + "integrity": "sha512-EQyqJMi552E4ZTf46izQ4Fj6XquqxCySR3J5ZSD1SisMf6RfpeOWHxGBE8Gr6V0/3GHIGdAzDn8F8+1nTGCnoQ==", "license": "MIT" }, "node_modules/vscode-languageserver-types": { - "version": "3.18.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.18.0.tgz", - "integrity": "sha512-8TsGPNMIMiiBdkORgRSvLjuiEIiAFtO+KssmYWxQ+uSVvlf7RjK8YKCOjPzZ+YA04jXEV7+7LvkSmHkhpNS99g==", + "version": "3.18.3", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.18.3.tgz", + "integrity": "sha512-XIlzJ7Qp/jzSI1ds7/FwPAWrPeTZA7pAtlW4hdJ1J6xXWJL6dR9QYnDhJOdLzdKhUQ5Mm6mvUMw+3DcOQQasPw==", "license": "MIT" }, "node_modules/whatwg-encoding": { @@ -5742,112 +5266,13 @@ "node": ">=18" } }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/workerpool": { - "version": "9.3.4", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", - "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-10.0.3.tgz", + "integrity": "sha512-6z2Iis68Wqth93/G/wJP9u+R3O+d2XTlgWChGCwuT1qLbBsOYueGRZuJ++v3mtDP5KjYdy+WzvWC+VWETSVXJA==", "dev": true, "license": "Apache-2.0" }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -5896,16 +5321,6 @@ "node": ">=4.0" } }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -5913,51 +5328,6 @@ "dev": true, "license": "ISC" }, - "node_modules/yargs": { - "version": "17.7.3", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", - "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/yauzl": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.4.0.tgz", diff --git a/editors/vscode/package.json b/editors/vscode/package.json index 1221f0d..4a1bb52 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -11,7 +11,7 @@ "publisher": "ViTeXFTW", "icon": "icon/ZeroSyntaxLogo256.png", "engines": { - "vscode": "^1.84.0" + "vscode": "^1.136.0" }, "categories": [ "Programming Languages", @@ -26,6 +26,10 @@ ], "contributes": { "commands": [ + { + "command": "zerosyntax.selectGameDirectory", + "title": "ZeroSyntax: Select Game Folder" + }, { "command": "zerosyntax.clearIndexCache", "title": "ZeroSyntax: Clear Index Cache" @@ -102,7 +106,7 @@ "items": { "type": "string" }, - "markdownDescription": "Directories or `.big` archives containing base game/mod INI files and game assets. INI definitions are treated as already loaded before `map.ini`/`solo.ini`; W3D, WAV/MP3, and TGA/DDS assets power completions and diagnostics. Configure all loaded roots to avoid partial-index warnings. Changing this reindexes in the background." + "markdownDescription": "Directories or `.big` archives containing base game/mod INI files and game assets. Use **ZeroSyntax: Select Game Folder** to browse for your installation or mod folder. INI definitions are treated as already loaded before `map.ini`/`solo.ini`; W3D, WAV/MP3, and TGA/DDS assets power completions and diagnostics. Configure all loaded roots to avoid partial-index warnings. Changing this reindexes in the background." }, "zerosyntax.progress.mode": { "type": "string", @@ -161,21 +165,21 @@ "compile:dev": "tsc --noEmit && esbuild src/extension.ts --bundle --sourcemap --platform=node --external:vscode --outfile=out/extension.js", "compile:test": "tsc -p ./tsconfig.test.json", "watch": "esbuild src/extension.ts --bundle --platform=node --external:vscode --outfile=out/extension.js --watch", - "vscode:prepublish": "node -e \"const fs=require('fs');fs.mkdirSync('icon',{recursive:true});fs.copyFileSync('../../icon/ZeroSyntaxLogo256.png','icon/ZeroSyntaxLogo256.png')\" && npm run compile", + "vscode:prepublish": "node -e \"const fs=require('fs');fs.mkdirSync('icon',{recursive:true});fs.copyFileSync('../../resources/ZeroSyntaxLogo256.png','icon/ZeroSyntaxLogo256.png')\" && npm run compile", "package": "vsce package --allow-missing-repository", "test": "npm run compile && npm run compile:test && node ./out-test/src/test/runTest.js" }, "dependencies": { - "vscode-languageclient": "^10.1.0" + "vscode-languageclient": "^10.1.1" }, "devDependencies": { "@types/mocha": "^10.0.10", - "@types/node": "^26.2.0", - "@types/vscode": "^1.84.0", + "@types/node": "^26.4.1", + "@types/vscode": "^1.136.0", "@vscode/test-electron": "^3.1.0", "@vscode/vsce": "^3.9.2", "esbuild": "^0.28.2", - "mocha": "^11.8.0", + "mocha": "^12.0.0", "typescript": "^7.0.2" } } diff --git a/editors/vscode/src/extension.ts b/editors/vscode/src/extension.ts index f3021b9..615671b 100644 --- a/editors/vscode/src/extension.ts +++ b/editors/vscode/src/extension.ts @@ -84,6 +84,7 @@ export function activate(context: vscode.ExtensionContext) { }); context.subscriptions.push( + vscode.commands.registerCommand("zerosyntax.selectGameDirectory", selectGameDirectory), vscode.commands.registerCommand("zerosyntax.openIndexCacheLocation", async () => { const cachePath = await client?.sendRequest("zerosyntax/indexCachePath"); if (!cachePath) { @@ -191,7 +192,11 @@ function resolveServerPath(context: vscode.ExtensionContext): string | undefined } async function maybeShowBaseIniRootsHint(document: vscode.TextDocument) { - if (baseIniRootsHintShown || !isMapLayerDocument(document)) { + if ( + baseIniRootsHintShown || + document.uri.scheme !== "file" || + document.languageId !== "generals-ini" + ) { return; } @@ -201,12 +206,16 @@ async function maybeShowBaseIniRootsHint(document: vscode.TextDocument) { } baseIniRootsHintShown = true; - const configure = "Configure base INI roots"; - const choice = await vscode.window.showWarningMessage( - "ZeroSyntax v2: map/solo.ini diagnostics are limited until base game or mod INIs are configured.", - configure + const selectDirectory = "Select Game Folder"; + const openSettings = "Open Settings"; + const choice = await vscode.window.showInformationMessage( + "ZeroSyntax v2: select your Zero Hour installation folder to enable game asset completions, model previews, and base game reference checks.", + selectDirectory, + openSettings ); - if (choice === configure) { + if (choice === selectDirectory) { + await vscode.commands.executeCommand("zerosyntax.selectGameDirectory"); + } else if (choice === openSettings) { await vscode.commands.executeCommand( "workbench.action.openSettings", "zerosyntax.baseIniRoots" @@ -214,6 +223,48 @@ async function maybeShowBaseIniRootsHint(document: vscode.TextDocument) { } } +async function selectGameDirectory() { + try { + const selected = await vscode.window.showOpenDialog({ + title: "Select your Zero Hour installation or mod folder", + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false, + openLabel: "Use Game Folder", + }); + if (!selected?.[0]) { + return; + } + + // Match the configuration sent to the server. Read it after the dialog so + // edits made while the picker is open are preserved. + const configuration = vscode.workspace.getConfiguration("zerosyntax"); + const roots = configuration.get("baseIniRoots", []); + const directory = selected[0].fsPath; + const normalize = (root: string) => { + const normalized = path.normalize(root).replace(/[\\/]+$/, ""); + return process.platform === "win32" ? normalized.toLowerCase() : normalized; + }; + if (roots.some((root) => normalize(root) === normalize(directory))) { + void vscode.window.showInformationMessage("ZeroSyntax v2: this game folder is already configured."); + return; + } + + const inspected = configuration.inspect("baseIniRoots"); + const target = inspected?.workspaceValue !== undefined + ? vscode.ConfigurationTarget.Workspace + : vscode.ConfigurationTarget.Global; + await configuration.update("baseIniRoots", [...roots, directory], target); + void vscode.window.showInformationMessage( + "ZeroSyntax v2: game folder added. Game data will be indexed in the background." + ); + } catch (error) { + void vscode.window.showErrorMessage( + `ZeroSyntax v2: could not configure the game folder. ${error instanceof Error ? error.message : String(error)}` + ); + } +} + function setting(key: string, fallback: T): T { const current = vscode.workspace.getConfiguration("zerosyntax"); const inspected = current.inspect(key); @@ -230,14 +281,6 @@ function setting(key: string, fallback: T): T { return vscode.workspace.getConfiguration("zerosyntax").get(key, fallback); } -function isMapLayerDocument(document: vscode.TextDocument): boolean { - if (document.uri.scheme !== "file" || document.languageId !== "generals-ini") { - return false; - } - const name = path.basename(document.uri.fsPath).toLowerCase(); - return name === "map.ini" || name === "solo.ini"; -} - class BigIniDocumentProvider implements vscode.TextDocumentContentProvider { constructor(private readonly getClient: () => LanguageClient | undefined) {} diff --git a/editors/vscode/src/test/suite/index.ts b/editors/vscode/src/test/suite/index.ts index b2e40f8..0f6f400 100644 --- a/editors/vscode/src/test/suite/index.ts +++ b/editors/vscode/src/test/suite/index.ts @@ -8,12 +8,13 @@ export function run(): Promise { ui: "tdd", }); + mocha.addFile(path.resolve(__dirname, "setup.test.js")); mocha.addFile(path.resolve(__dirname, "smoke.test.js")); return new Promise((resolve, reject) => { mocha.run((failures) => { if (failures > 0) { - reject(new Error(`${failures} VS Code smoke test(s) failed`)); + reject(new Error(`${failures} VS Code extension test(s) failed`)); } else { resolve(); } diff --git a/editors/vscode/src/test/suite/setup.test.ts b/editors/vscode/src/test/suite/setup.test.ts new file mode 100644 index 0000000..8c8d302 --- /dev/null +++ b/editors/vscode/src/test/suite/setup.test.ts @@ -0,0 +1,253 @@ +import * as assert from "assert"; +import * as fs from "fs"; +import * as path from "path"; +import * as vm from "vm"; +import type * as vscode from "vscode"; +import type { activate } from "../../extension"; + +const selectCommand = "zerosyntax.selectGameDirectory"; +const gameFolder = path.resolve("Game Folder"); +const modArchive = path.resolve("Mod.big"); +const document = { + languageId: "generals-ini", + uri: { scheme: "file", fsPath: path.resolve("Object.ini") }, +}; + +// Load a fresh extension for each test, with an isolated VS Code boundary. +// Exercise its registered commands/events without a real picker, server, or +// changes to the extension host's settings. The type import above also makes +// compile:test emit the production extension beside the test output. +function setup(options: { + roots?: string[]; + workspaceRoots?: string[]; + platform?: "win32" | "linux"; + visible?: boolean; +} = {}) { + const state = { + roots: options.roots ?? [], + selected: [{ fsPath: gameFolder }] as { fsPath: string }[] | undefined, + choice: undefined as string | undefined, + saveError: undefined as Error | undefined, + beforeSelection: () => {}, + }; + const updates: { key: string; value: string[]; target: number }[] = []; + const notifications: { message: string; actions: string[] }[] = []; + const errors: string[] = []; + const dialogs: vscode.OpenDialogOptions[] = []; + const executed: { name: string; args: unknown[] }[] = []; + const commands = new Map Promise>(); + let openDocument: (doc: typeof document) => void = () => assert.fail("open listener missing"); + const disposable = { dispose() {} }; + const api = { + ConfigurationTarget: { Global: 1, Workspace: 2 }, + CodeActionKind: { QuickFix: {} }, + window: { + visibleTextEditors: options.visible ? [{ document }] : [], + async showOpenDialog(dialog: vscode.OpenDialogOptions) { + dialogs.push(dialog); + state.beforeSelection(); + return state.selected; + }, + async showInformationMessage(message: string, ...actions: string[]) { + notifications.push({ message, actions }); + return actions.length ? state.choice : undefined; + }, + async showErrorMessage(message: string) { errors.push(message); }, + }, + workspace: { + getConfiguration(section: string) { + assert.strictEqual(section, "zerosyntax"); + return { + get(key: string, fallback: unknown) { + if (key === "server.path") { return "test-server"; } + return key === "baseIniRoots" ? state.roots : fallback; + }, + inspect(key: string) { + return key === "baseIniRoots" ? { workspaceValue: options.workspaceRoots } : undefined; + }, + async update(key: string, value: string[], target: number) { + if (state.saveError) { throw state.saveError; } + updates.push({ key, value, target }); + state.roots = value; + }, + }; + }, + createFileSystemWatcher: () => disposable, + registerTextDocumentContentProvider: () => disposable, + onDidChangeConfiguration: () => disposable, + onDidOpenTextDocument(handler: typeof openDocument) { + openDocument = handler; + return disposable; + }, + }, + languages: { registerCodeActionsProvider: () => disposable }, + commands: { + registerCommand(name: string, handler: () => Promise) { + commands.set(name, handler); + return disposable; + }, + async executeCommand(name: string, ...args: unknown[]) { + executed.push({ name, args }); + await commands.get(name)?.(); + }, + }, + }; + const filename = path.resolve(__dirname, "../../extension.js"); + const extensionModule = { exports: {} as { activate: typeof activate } }; + const evaluate = vm.runInThisContext( + `(function(require, module, exports, process) { ${fs.readFileSync(filename, "utf8")}\n})`, + { filename } + ); + evaluate((name: string) => { + if (name === "vscode") { return api; } + if (name === "vscode-languageclient/node") { + return { LanguageClient: class { start() {} }, TransportKind: { stdio: 0 } }; + } + if (name === "path" && options.platform) { + return options.platform === "win32" ? path.win32 : path.posix; + } + return require(name); + }, extensionModule, extensionModule.exports, { platform: options.platform ?? process.platform }); + extensionModule.exports.activate({ subscriptions: [] } as unknown as vscode.ExtensionContext); + return { + state, updates, notifications, errors, dialogs, executed, + open: (doc = document) => openDocument(doc), + async select() { + const command = commands.get(selectCommand); + assert.ok(command, "game-folder command must be registered"); + await command(); + }, + }; +} + +// Document-open handlers intentionally return void; flush their async work. +const settle = () => new Promise((resolve) => setImmediate(resolve)); + +suite("Game folder setup", () => { + test("uses a folder-only picker and appends to user settings", async () => { + const app = setup({ roots: [modArchive] }); + await app.select(); + assert.strictEqual(app.dialogs[0].canSelectFiles, false); + assert.strictEqual(app.dialogs[0].canSelectFolders, true); + assert.strictEqual(app.dialogs[0].canSelectMany, false); + assert.deepStrictEqual(app.updates, [ + { key: "baseIniRoots", value: [modArchive, gameFolder], target: 1 }, + ]); + assert.match(app.notifications[0].message, /indexed in the background/); + }); + + for (const roots of [[], [modArchive]]) { + test(`respects ${roots.length ? "populated" : "empty"} workspace overrides`, async () => { + const app = setup({ roots, workspaceRoots: roots }); + await app.select(); + assert.deepStrictEqual(app.updates, [ + { key: "baseIniRoots", value: [...roots, gameFolder], target: 2 }, + ]); + }); + } + + for (const selected of [undefined, []]) { + test(`cancelling with ${selected === undefined ? "undefined" : "no selections"} changes nothing`, async () => { + const app = setup({ roots: [modArchive] }); + app.state.selected = selected; + await app.select(); + assert.deepStrictEqual(app.state.roots, [modArchive]); + assert.deepStrictEqual(app.updates, []); + assert.deepStrictEqual(app.notifications, []); + assert.deepStrictEqual(app.errors, []); + }); + } + + test("preserves settings changed while the picker is open", async () => { + const app = setup(); + app.state.beforeSelection = () => { app.state.roots = [modArchive]; }; + await app.select(); + assert.deepStrictEqual(app.updates[0].value, [modArchive, gameFolder]); + }); + + test("does not duplicate Windows paths with different case or separators", async () => { + const app = setup({ roots: ["c:/games/zero hour/"], platform: "win32" }); + app.state.selected = [{ fsPath: "C:\\Games\\Zero Hour" }]; + await app.select(); + assert.deepStrictEqual(app.updates, []); + assert.match(app.notifications[0].message, /already configured/); + }); + + test("normalizes POSIX paths without folding their case", async () => { + const app = setup({ roots: ["/games/ZeroHour/"], platform: "linux" }); + app.state.selected = [{ fsPath: "/games/ZeroHour" }]; + await app.select(); + assert.strictEqual(app.updates.length, 0); + app.state.selected = [{ fsPath: "/games/zerohour" }]; + await app.select(); + assert.deepStrictEqual(app.updates[0].value, ["/games/ZeroHour/", "/games/zerohour"]); + }); + + test("reports save failures without claiming success", async () => { + const app = setup(); + app.state.saveError = new Error("Settings are read-only"); + await app.select(); + assert.deepStrictEqual(app.updates, []); + assert.deepStrictEqual(app.notifications, []); + assert.strictEqual(app.errors.length, 1); + assert.match(app.errors[0], /Settings are read-only/); + }); + + test("offers setup for ordinary INIs and routes the picker action", async () => { + const app = setup(); + app.state.choice = "Select Game Folder"; + app.open(); + await settle(); + assert.deepStrictEqual(app.notifications[0].actions, ["Select Game Folder", "Open Settings"]); + assert.deepStrictEqual(app.executed, [{ name: selectCommand, args: [] }]); + assert.strictEqual(app.updates.length, 1); + }); + + test("routes the Settings action to base INI roots", async () => { + const app = setup(); + app.state.choice = "Open Settings"; + app.open(); + await settle(); + assert.deepStrictEqual(app.executed, [ + { name: "workbench.action.openSettings", args: ["zerosyntax.baseIniRoots"] }, + ]); + assert.deepStrictEqual(app.dialogs, []); + }); + + test("prompts once per activation even for concurrent opens and dismissal", async () => { + const app = setup(); + app.open(); + app.open(); + await settle(); + app.open(); + await settle(); + assert.strictEqual(app.notifications.length, 1); + assert.deepStrictEqual(app.executed, []); + assert.deepStrictEqual(app.updates, []); + }); + + test("checks editors already visible at activation", async () => { + const app = setup({ visible: true }); + await settle(); + assert.strictEqual(app.notifications.length, 1); + }); + + test("skips unrelated languages and virtual documents without consuming the hint", async () => { + const app = setup(); + app.open({ ...document, languageId: "plaintext" }); + app.open({ ...document, uri: { ...document.uri, scheme: "big" } }); + await settle(); + assert.deepStrictEqual(app.notifications, []); + app.open(); + await settle(); + assert.strictEqual(app.notifications.length, 1); + }); + + test("does not prompt when base roots are configured", async () => { + const app = setup({ roots: [modArchive], visible: true }); + app.open(); + await settle(); + assert.deepStrictEqual(app.notifications, []); + assert.deepStrictEqual(app.executed, []); + }); +}); diff --git a/icon/ZeroSyntaxLogo256.png b/resources/ZeroSyntaxLogo256.png similarity index 100% rename from icon/ZeroSyntaxLogo256.png rename to resources/ZeroSyntaxLogo256.png diff --git a/resources/readme-examples/model_completion_example.png b/resources/readme-examples/model_completion_example.png new file mode 100644 index 0000000..1cf9e74 Binary files /dev/null and b/resources/readme-examples/model_completion_example.png differ diff --git a/resources/readme-examples/reference_diagnostic_value.png b/resources/readme-examples/reference_diagnostic_value.png new file mode 100644 index 0000000..8b9e998 Binary files /dev/null and b/resources/readme-examples/reference_diagnostic_value.png differ diff --git a/resources/readme-examples/snippet_example.gif b/resources/readme-examples/snippet_example.gif new file mode 100644 index 0000000..a610bd9 Binary files /dev/null and b/resources/readme-examples/snippet_example.gif differ diff --git a/resources/readme-examples/value_diagnostic_percentage.png b/resources/readme-examples/value_diagnostic_percentage.png new file mode 100644 index 0000000..dcf5ff6 Binary files /dev/null and b/resources/readme-examples/value_diagnostic_percentage.png differ diff --git a/resources/readme-examples/vscode_install_example.png b/resources/readme-examples/vscode_install_example.png new file mode 100644 index 0000000..ee8c93f Binary files /dev/null and b/resources/readme-examples/vscode_install_example.png differ