diff --git a/package.json b/package.json index 64bf6e4..486fd81 100644 --- a/package.json +++ b/package.json @@ -119,6 +119,16 @@ } } }, + "nwscript-ee-lsp.definition": { + "type": "object", + "properties": { + "preferImplementation": { + "type": "boolean", + "default": true, + "description": "When using Go to Definition on a function, prefer jumping to the implementation (with body) over the forward declaration. When disabled, jumps to the first occurrence found." + } + } + }, "nwscript-ee-lsp.compiler": { "type": "object", "properties": { diff --git a/server/resources/compiler/linux/nwn_script_comp b/server/resources/compiler/linux/nwn_script_comp index 5a188d8..0c984d7 100755 Binary files a/server/resources/compiler/linux/nwn_script_comp and b/server/resources/compiler/linux/nwn_script_comp differ diff --git a/server/resources/compiler/mac/nwn_script_comp b/server/resources/compiler/mac/nwn_script_comp index 88791f0..0c984d7 100644 Binary files a/server/resources/compiler/mac/nwn_script_comp and b/server/resources/compiler/mac/nwn_script_comp differ diff --git a/server/resources/compiler/windows/nwn_script_comp.exe b/server/resources/compiler/windows/nwn_script_comp.exe index c76bdc0..acd87a2 100644 Binary files a/server/resources/compiler/windows/nwn_script_comp.exe and b/server/resources/compiler/windows/nwn_script_comp.exe differ diff --git a/server/src/Providers/Builders/CompletionItemBuilder.ts b/server/src/Providers/Builders/CompletionItemBuilder.ts index 9a54de9..f1d5dda 100644 --- a/server/src/Providers/Builders/CompletionItemBuilder.ts +++ b/server/src/Providers/Builders/CompletionItemBuilder.ts @@ -14,23 +14,38 @@ import Builder from "./Builder"; export default class CompletionItemBuilder extends Builder { public static buildResolvedItem(item: CompletionItem, serverConfig: ServerConfiguration): CompletionItem { + const isAutoImport = item.data?.autoImport; + const params = isAutoImport ? item.data.params : item.data; + if (serverConfig.completion.addParamsToFunctions && item.kind === CompletionItemKind.Function) { - const params = item.data as FunctionParamComplexToken[]; + const typedParams = params as FunctionParamComplexToken[]; return { - label: `${item.label}(${params.reduce((acc, param, index) => { + label: `${item.label}(${typedParams.reduce((acc, param, index) => { return `${acc}${this.handleLanguageType(param.valueType)} ${param.identifier}${ - index === params.length - 1 ? "" : ", " + index === typedParams.length - 1 ? "" : ", " }`; }, "")})`, kind: item.kind, detail: item.detail, + data: item.data, }; } return item; } + public static buildAutoImportItem(token: ComplexToken, sourceFileKey: string, requestingUri: string): CompletionItem { + const item = this.buildItem(token); + item.data = { + autoImport: { sourceFileKey, requestingUri }, + params: item.data, + }; + item.sortText = `1_${item.label}`; + item.detail = `${item.detail || ""} (auto-import from ${sourceFileKey})`; + return item; + } + public static buildItem(token: ComplexToken): CompletionItem { if (this.isConstantToken(token)) { return this.buildConstantItem(token); diff --git a/server/src/Providers/CodeActionProvider.ts b/server/src/Providers/CodeActionProvider.ts new file mode 100644 index 0000000..e11cce9 --- /dev/null +++ b/server/src/Providers/CodeActionProvider.ts @@ -0,0 +1,87 @@ +import { CodeAction, CodeActionKind, CodeActionParams, TextEdit } from "vscode-languageserver"; + +import type { ServerManager } from "../ServerManager"; +import { computeIncludeInsertPosition } from "../Utils/includeInsertPosition"; +import Provider from "./Provider"; + +export default class CodeActionProvider extends Provider { + constructor(server: ServerManager) { + super(server); + + this.server.connection.onCodeAction((params) => this.exceptionsWrapper(this.providerHandler(params), [])); + } + + private providerHandler(params: CodeActionParams) { + return () => { + const { + textDocument: { uri }, + context, + } = params; + + if (!context.diagnostics.length) return []; + + const liveDocument = this.server.liveDocumentsManager.get(uri); + const document = this.server.documentsCollection.getFromUri(uri); + if (!liveDocument || !document) return []; + + const actions: CodeAction[] = []; + const text = liveDocument.getText(); + const lines = text.split("\n"); + const children = document.getChildren(); + const ownKey = document.getKey(); + const excludedKeys = new Set([ownKey, ...children]); + const isQueueSrc = uri.includes("queue_src"); + + // Build a set of identifiers already in scope to avoid false positives + const inScopeIdentifiers = new Set(); + document.getGlobalComplexTokens().forEach((t) => inScopeIdentifiers.add(t.identifier)); + document.getGlobalStructComplexTokens().forEach((t) => inScopeIdentifiers.add(t.identifier)); + this.getStandardLibComplexTokens().forEach((t) => inScopeIdentifiers.add(t.identifier)); + + for (const diagnostic of context.diagnostics) { + const line = lines[diagnostic.range.start.line]; + if (!line) continue; + + // Diagnostics may span the entire line, so extract all identifiers + // from the line and filter to those not already in scope. + const lineIdentifiers = (line.match(/[A-Za-z_][A-Za-z0-9_]*/g) || []).filter((id) => !inScopeIdentifiers.has(id)); + if (lineIdentifiers.length === 0) continue; + + const identifiersSet = new Set(lineIdentifiers); + const suggestedKeys = new Set(); + + this.server.documentsCollection.forEach((doc) => { + const docKey = doc.getKey(); + + if (doc.base || excludedKeys.has(docKey)) return; + if (suggestedKeys.has(docKey)) return; + + const docIsQueueSrc = doc.uri.includes("queue_src"); + if (isQueueSrc !== docIsQueueSrc) return; + + const hasSymbol = + doc.complexTokens.some((token) => identifiersSet.has(token.identifier)) || + doc.structComplexTokens.some((token) => identifiersSet.has(token.identifier)); + + if (hasSymbol) { + suggestedKeys.add(docKey); + const insertPosition = computeIncludeInsertPosition(text); + actions.push({ + title: `Add #include "${docKey}"`, + kind: CodeActionKind.QuickFix, + diagnostics: [diagnostic], + edit: { + changes: { + [uri]: [TextEdit.insert(insertPosition, `#include "${docKey}"\n`)], + }, + }, + command: { title: "Recompile", command: "nwscript-ee-lsp.recompile", arguments: [uri] }, + }); + } + }); + } + + return actions; + }; + } +} diff --git a/server/src/Providers/CompletionItemsProvider.ts b/server/src/Providers/CompletionItemsProvider.ts index 7cbe252..fa68b01 100644 --- a/server/src/Providers/CompletionItemsProvider.ts +++ b/server/src/Providers/CompletionItemsProvider.ts @@ -1,4 +1,4 @@ -import { CompletionParams } from "vscode-languageserver"; +import { CompletionItem, CompletionParams, TextEdit } from "vscode-languageserver"; import type { ServerManager } from "../ServerManager"; import { CompletionItemBuilder } from "./Builders"; @@ -6,6 +6,7 @@ import { LocalScopeTokenizationResult } from "../Tokenizer/Tokenizer"; import { TriggerCharacters } from "."; import { Document } from "../Documents"; import { LanguageTypes } from "../Tokenizer/constants"; +import { computeIncludeInsertPosition } from "../Utils/includeInsertPosition"; import Provider from "./Provider"; export default class CompletionItemsProvider extends Provider { @@ -13,7 +14,23 @@ export default class CompletionItemsProvider extends Provider { super(server); this.server.connection.onCompletion((params) => this.exceptionsWrapper(this.providerHandler(params))); - this.server.connection.onCompletionResolve((item) => this.exceptionsWrapper(() => CompletionItemBuilder.buildResolvedItem(item, this.server.config), item)); + this.server.connection.onCompletionResolve((item) => + this.exceptionsWrapper(() => { + const resolved = CompletionItemBuilder.buildResolvedItem(item, this.server.config); + + if (item.data?.autoImport) { + const { sourceFileKey, requestingUri } = item.data.autoImport; + const liveDocument = this.server.liveDocumentsManager.get(requestingUri); + if (liveDocument) { + const insertPosition = computeIncludeInsertPosition(liveDocument.getText()); + resolved.additionalTextEdits = [TextEdit.insert(insertPosition, `#include "${sourceFileKey}"\n`)]; + resolved.command = { title: "Recompile", command: "nwscript-ee-lsp.recompile", arguments: [requestingUri] }; + } + } + + return resolved; + }, item), + ); } private providerHandler(params: CompletionParams) { @@ -46,7 +63,10 @@ export default class CompletionItemsProvider extends Provider { return document.getGlobalStructComplexTokens().map((token) => CompletionItemBuilder.buildItem(token)); } - return this.getGlobalScopeCompletionItems(document, localScope).concat(this.getLocalScopeCompletionItems(localScope)).concat(this.getStandardLibCompletionItems()); + return this.getGlobalScopeCompletionItems(document, localScope) + .concat(this.getLocalScopeCompletionItems(localScope)) + .concat(this.getStandardLibCompletionItems()) + .concat(this.getAutoImportCompletionItems(document, localScope, uri)); }; } @@ -69,4 +89,43 @@ export default class CompletionItemsProvider extends Provider { private getStandardLibCompletionItems() { return this.getStandardLibComplexTokens().map((token) => CompletionItemBuilder.buildItem(token)); } + + private getAutoImportCompletionItems(document: Document, localScope: LocalScopeTokenizationResult, uri: string) { + const children = document.getChildren(); + const ownKey = document.getKey(); + const excludedKeys = new Set([ownKey, ...children]); + + const inScopeIdentifiers = new Set(); + document + .getGlobalComplexTokens( + [], + localScope.functionsComplexTokens.map((t) => t.identifier), + ) + .forEach((t) => inScopeIdentifiers.add(t.identifier)); + localScope.functionsComplexTokens.forEach((t) => inScopeIdentifiers.add(t.identifier)); + localScope.functionVariablesComplexTokens.forEach((t) => inScopeIdentifiers.add(t.identifier)); + this.getStandardLibComplexTokens().forEach((t) => inScopeIdentifiers.add(t.identifier)); + + const isQueueSrc = uri.includes("queue_src"); + const items: CompletionItem[] = []; + + this.server.documentsCollection.forEach((doc) => { + const docKey = doc.getKey(); + + if (doc.base || excludedKeys.has(docKey)) return; + + const docIsQueueSrc = doc.uri.includes("queue_src"); + if (isQueueSrc !== docIsQueueSrc) return; + + const seenInDoc = new Set(); + doc.complexTokens.forEach((token) => { + if (!inScopeIdentifiers.has(token.identifier) && !seenInDoc.has(token.identifier)) { + seenInDoc.add(token.identifier); + items.push(CompletionItemBuilder.buildAutoImportItem(token, docKey, uri)); + } + }); + }); + + return items; + } } diff --git a/server/src/Providers/DiagnosticsProvider.ts b/server/src/Providers/DiagnosticsProvider.ts index 2246163..7449e89 100644 --- a/server/src/Providers/DiagnosticsProvider.ts +++ b/server/src/Providers/DiagnosticsProvider.ts @@ -25,20 +25,27 @@ export default class DiagnoticsProvider extends Provider { private generateDiagnostics(uris: string[], files: FilesDiagnostics, severity: DiagnosticSeverity) { return (line: string) => { - const uri = uris.find((uri) => basename(fileURLToPath(uri)) === lineFilename.exec(line)![2]); - - if (uri) { - const linePosition = Number(lineNumber.exec(line)![1]) - 1; - const diagnostic = { - severity, - range: { - start: { line: linePosition, character: 0 }, - end: { line: linePosition, character: Number.MAX_VALUE }, - }, - message: lineMessage.exec(line)![2].trim(), - }; - - files[uri].push(diagnostic); + const lineFilenameMatch = lineFilename.exec(line); + if (lineFilenameMatch) { + const reportedFileName = lineFilenameMatch[2]; + const uri = uris.find((uri) => basename(fileURLToPath(uri)) === reportedFileName); + if (uri) { + const lineNumberMatch = lineNumber.exec(line); + const lineMessageMatch = lineMessage.exec(line); + if (lineNumberMatch && lineMessageMatch) { + const linePosition = Number(lineNumberMatch[1]) - 1; + const diagnostic = { + severity, + range: { + start: { line: linePosition, character: 0 }, + end: { line: linePosition, character: Number.MAX_VALUE }, + }, + message: lineMessageMatch[2].trim(), + }; + + files[uri].push(diagnostic); + } + } } }; } @@ -101,7 +108,10 @@ export default class DiagnoticsProvider extends Provider { // The compiler command: // - y; continue on error // - s; dry run - const args = ["-y", "-s"]; + // - n; no entry point required (for include files) + // - E; collect and report all errors (not just the first) + // Note: -E requires compiler binary with ABI v2+ + const args = ["-y", "-s", "-n", "-E"]; if (Boolean(nwnHome)) { args.push("--userdirectory"); args.push(`"${nwnHome}"`); @@ -114,12 +124,25 @@ export default class DiagnoticsProvider extends Provider { } else if (verbose) { this.server.logger.info("Trying to resolve Neverwinter Nights installation directory automatically."); } - if (children.length > 0) { + // Collect directories from ALL indexed documents so the compiler can + // resolve the full transitive include chain, not just direct children. + const allDirs: Set = new Set(); + this.server.documentsCollection.forEach((doc) => { + if (doc.uri && doc.uri.startsWith("file://")) { + allDirs.add(dirname(fileURLToPath(doc.uri))); + } + }); + // Also add the compiled file's own directory + allDirs.add(dirname(fileURLToPath(uri))); + if (allDirs.size > 0) { args.push("--dirs"); - args.push(`"${[...new Set(uris.map((uri) => dirname(fileURLToPath(uri))))].join(",")}"`); + args.push(`"${[...allDirs].join(",")}"`); } + + const filePath = fileURLToPath(uri); + this.server.logger.info(`Compiling file: ${filePath}`); args.push("-c"); - args.push(`"${fileURLToPath(uri)}"`); + args.push(`"${filePath}"`); let stdout = ""; let stderr = ""; @@ -186,6 +209,7 @@ export default class DiagnoticsProvider extends Provider { for (const [uri, diagnostics] of Object.entries(files)) { this.server.connection.sendDiagnostics({ uri, diagnostics }); } + resolve(true); }); }); diff --git a/server/src/Providers/GotoDefinitionProvider.ts b/server/src/Providers/GotoDefinitionProvider.ts index 86e8900..27cabd5 100644 --- a/server/src/Providers/GotoDefinitionProvider.ts +++ b/server/src/Providers/GotoDefinitionProvider.ts @@ -3,7 +3,7 @@ import { TextDocument } from "vscode-languageserver-textdocument"; import type { OwnedComplexTokens, OwnedStructComplexTokens } from "../Documents/Document"; import type { ServerManager } from "../ServerManager"; -import type { ComplexToken } from "../Tokenizer/types"; +import type { ComplexToken, FunctionComplexToken } from "../Tokenizer/types"; import { Document } from "../Documents"; import Provider from "./Provider"; @@ -71,12 +71,43 @@ export default class GotoDefinitionProvider extends Provider { tokensWithRef.push({ owner: localStandardLibDefinitions?.uri, tokens: localStandardLibDefinitions?.complexTokens }); } - loop: for (let i = 0; i < tokensWithRef.length; i++) { - ref = tokensWithRef[i]; + if (this.server.config.definition.preferImplementation) { + // Two-pass: prefer function definitions (with bodies) over forward declarations + let fallbackToken: ComplexToken | undefined; + let fallbackRef: OwnedComplexTokens | undefined; + + loop: for (let i = 0; i < tokensWithRef.length; i++) { + ref = tokensWithRef[i]; + + for (const candidate of ref?.tokens ?? []) { + if (candidate.identifier !== rawContent) continue; + + const funcCandidate = candidate as FunctionComplexToken; + if (funcCandidate.tokenType === CompletionItemKind.Function && funcCandidate.isForwardDeclaration) { + if (!fallbackToken) { + fallbackToken = candidate; + fallbackRef = ref; + } + } else { + token = candidate; + break loop; + } + } + } - token = ref?.tokens.find((candidate) => candidate.identifier === rawContent); - if (token) { - break loop; + if (!token && fallbackToken) { + token = fallbackToken; + ref = fallbackRef; + } + } else { + // Jump to first occurrence found + loop: for (let i = 0; i < tokensWithRef.length; i++) { + ref = tokensWithRef[i]; + + token = ref?.tokens.find((candidate) => candidate.identifier === rawContent); + if (token) { + break loop; + } } } break; diff --git a/server/src/Providers/index.ts b/server/src/Providers/index.ts index ffddb20..9fe2aed 100644 --- a/server/src/Providers/index.ts +++ b/server/src/Providers/index.ts @@ -1,3 +1,4 @@ +import CodeActionProvider from "./CodeActionProvider"; import ConfigurationProvider from "./ConfigurationProvider"; import WorkspaceProvider from "./WorkspaceProvider"; import CompletionItemsProvider from "./CompletionItemsProvider"; @@ -16,6 +17,7 @@ export enum TriggerCharacters { } export { + CodeActionProvider, ConfigurationProvider, WorkspaceProvider, CompletionItemsProvider, diff --git a/server/src/ServerManager/CapabilitiesHandler.ts b/server/src/ServerManager/CapabilitiesHandler.ts index 33d40a6..aa7ae71 100644 --- a/server/src/ServerManager/CapabilitiesHandler.ts +++ b/server/src/ServerManager/CapabilitiesHandler.ts @@ -1,4 +1,4 @@ -import { ClientCapabilities, ServerCapabilities, TextDocumentSyncKind } from "vscode-languageserver"; +import { ClientCapabilities, CodeActionKind, ServerCapabilities, TextDocumentSyncKind } from "vscode-languageserver"; import { TriggerCharacters } from "../Providers"; export default class CapabilitiesHandler { @@ -29,6 +29,12 @@ export default class CapabilitiesHandler { signatureHelpProvider: { triggerCharacters: [TriggerCharacters.leftRoundBracket, TriggerCharacters.comma], }, + codeActionProvider: { + codeActionKinds: [CodeActionKind.QuickFix], + }, + executeCommandProvider: { + commands: ["nwscript-ee-lsp.recompile"], + }, }; if (this.clientCapabilities.workspace?.workspaceFolders) { diff --git a/server/src/ServerManager/Config.ts b/server/src/ServerManager/Config.ts index d02703e..e2eb4bf 100644 --- a/server/src/ServerManager/Config.ts +++ b/server/src/ServerManager/Config.ts @@ -33,6 +33,9 @@ const defaultServerConfiguration = { nwnHome: "", nwnInstallation: "", }, + definition: { + preferImplementation: true, + }, }; /* eslint-enable @typescript-eslint/naming-convention */ diff --git a/server/src/ServerManager/ServerManager.ts b/server/src/ServerManager/ServerManager.ts index fd229ab..9a7da2a 100644 --- a/server/src/ServerManager/ServerManager.ts +++ b/server/src/ServerManager/ServerManager.ts @@ -6,6 +6,7 @@ import * as clustering from "cluster"; import type { Connection, InitializeParams } from "vscode-languageserver"; import { + CodeActionProvider, CompletionItemsProvider, ConfigurationProvider, DiagnosticsProvider, @@ -55,6 +56,7 @@ export default class ServerManger { this.tokenizer.loadGrammar(); this.registerProviders(); this.registerLiveDocumentsEvents(); + this.registerCommands(); return this; } @@ -130,10 +132,24 @@ export default class ServerManger { DocumentFormatingProvider.register(this); DocumentRangeFormattingProvider.register(this); SymbolsProvider.register(this); + CodeActionProvider.register(this); this.diagnosticsProvider = DiagnosticsProvider.register(this) as DiagnosticsProvider; } + private registerCommands() { + this.connection.onExecuteCommand(async (params) => { + if (params.command === "nwscript-ee-lsp.recompile" && params.arguments?.[0]) { + const uri = params.arguments[0] as string; + const liveDocument = this.liveDocumentsManager.get(uri); + if (liveDocument) { + this.documentsCollection.updateDocument(liveDocument, this.tokenizer, this.workspaceFilesSystem); + await this.diagnosticsProvider?.publish(uri); + } + } + }); + } + private registerLiveDocumentsEvents() { this.liveDocumentsManager.onDidSave((event) => this.diagnosticsProvider?.publish(event.document.uri)); this.liveDocumentsManager.onWillSave((event) => this.documentsCollection?.updateDocument(event.document, this.tokenizer, this.workspaceFilesSystem)); @@ -145,11 +161,12 @@ export default class ServerManger { } private async loadConfig() { - const { completion, hovering, formatter, compiler, ...rest } = await this.connection.workspace.getConfiguration("nwscript-ee-lsp"); + const { completion, hovering, formatter, compiler, definition, ...rest } = await this.connection.workspace.getConfiguration("nwscript-ee-lsp"); this.config = { ...this.config, ...rest }; this.config.completion = { ...this.config.completion, ...completion }; this.config.hovering = { ...this.config.hovering, ...hovering }; this.config.formatter = { ...this.config.formatter, ...formatter }; this.config.compiler = { ...this.config.compiler, ...compiler }; + this.config.definition = { ...this.config.definition, ...definition }; } } diff --git a/server/src/Tokenizer/Tokenizer.ts b/server/src/Tokenizer/Tokenizer.ts index 52db3b6..47821ee 100644 --- a/server/src/Tokenizer/Tokenizer.ts +++ b/server/src/Tokenizer/Tokenizer.ts @@ -166,12 +166,11 @@ export default class Tokenizer { return isFunctionDeclaration; } - private isGlobalFunctionDeclaration(lineIndex: number, tokenIndex: number, token: IToken, tokensArrays: (IToken[] | undefined)[]) { + private isGlobalFunctionDeclarationOrDefinition(lineIndex: number, tokenIndex: number, token: IToken, tokensArrays: (IToken[] | undefined)[]) { return ( !(tokenIndex === 0 && lineIndex === 0) && // Not sure why we need this !token.scopes.includes(LanguageScopes.block) && - token.scopes.includes(LanguageScopes.functionIdentifier) && - this.isFunctionDeclaration(lineIndex, tokensArrays) + token.scopes.includes(LanguageScopes.functionIdentifier) ); } @@ -256,7 +255,8 @@ export default class Tokenizer { break; } - if (this.isGlobalFunctionDeclaration(lineIndex, tokenIndex, token, tokensArrays)) { + if (this.isGlobalFunctionDeclarationOrDefinition(lineIndex, tokenIndex, token, tokensArrays)) { + const isForwardDeclaration = this.isFunctionDeclaration(lineIndex, tokensArrays); scope.complexTokens.push({ position: { line: lineIndex, character: token.startIndex }, identifier: this.getRawTokenContent(line, token), @@ -264,6 +264,7 @@ export default class Tokenizer { returnType: tokenIndex === 0 ? this.getTokenLanguageType(lines[lineIndex - 1], tokensArrays[lineIndex - 1]!, 0) : this.getTokenLanguageType(line, tokensArray, tokenIndex - 2), params: this.getFunctionParams(lineIndex, lines, tokensArrays), comments: this.getFunctionComments(lines, tokensArrays, tokenIndex === 0 ? lineIndex - 2 : lineIndex - 1), + isForwardDeclaration, }); break; diff --git a/server/src/Tokenizer/types.ts b/server/src/Tokenizer/types.ts index be51b9d..9a64c28 100644 --- a/server/src/Tokenizer/types.ts +++ b/server/src/Tokenizer/types.ts @@ -18,6 +18,7 @@ type LanguageFunction = { params: FunctionParamComplexToken[]; variables?: VariableComplexToken[]; comments: string[]; + isForwardDeclaration?: boolean; }; type LanguageFunctionParam = { tokenType: typeof CompletionItemKind.TypeParameter; diff --git a/server/src/Utils/includeInsertPosition.ts b/server/src/Utils/includeInsertPosition.ts new file mode 100644 index 0000000..4ae4d10 --- /dev/null +++ b/server/src/Utils/includeInsertPosition.ts @@ -0,0 +1,18 @@ +import { Position } from "vscode-languageserver"; + +export function computeIncludeInsertPosition(text: string): Position { + const lines = text.split("\n"); + let lastIncludeLine = -1; + + for (let i = 0; i < lines.length; i++) { + if (/^\s*#include\s+"[^"]*"/.test(lines[i])) { + lastIncludeLine = i; + } + } + + if (lastIncludeLine === -1) { + return { line: 0, character: 0 }; + } + + return { line: lastIncludeLine + 1, character: 0 }; +} diff --git a/server/test/static/globalScopeTokens.json b/server/test/static/globalScopeTokens.json index bf005da..91fecb5 100644 --- a/server/test/static/globalScopeTokens.json +++ b/server/test/static/globalScopeTokens.json @@ -1,552 +1,846 @@ { "complexTokens": [ - { + { + "position": { + "line": 7, + "character": 13 + }, + "identifier": "NWNX_SkillRanks", + "tokenType": 21, + "valueType": "string", + "value": "\"NWNX_SkillRanks\"" + }, + { + "position": { + "line": 14, + "character": 10 + }, + "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_STRENGTH", + "tokenType": 21, + "valueType": "int", + "value": "1" + }, + { + "position": { + "line": 15, + "character": 10 + }, + "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_DEXTERITY", + "tokenType": 21, + "valueType": "int", + "value": "2" + }, + { + "position": { + "line": 16, + "character": 10 + }, + "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_CONSTITUTION", + "tokenType": 21, + "valueType": "int", + "value": "4" + }, + { + "position": { + "line": 17, + "character": 10 + }, + "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_INTELLIGENCE", + "tokenType": 21, + "valueType": "int", + "value": "8" + }, + { + "position": { + "line": 18, + "character": 10 + }, + "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_WISDOM", + "tokenType": 21, + "valueType": "int", + "value": "16" + }, + { + "position": { + "line": 19, + "character": 10 + }, + "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_CHARISMA", + "tokenType": 21, + "valueType": "int", + "value": "32" + }, + { + "position": { + "line": 28, + "character": 10 + }, + "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_CALC_MIN", + "tokenType": 21, + "valueType": "int", + "value": "64" + }, + { + "position": { + "line": 29, + "character": 10 + }, + "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_CALC_MAX", + "tokenType": 21, + "valueType": "int", + "value": "128" + }, + { + "position": { + "line": 30, + "character": 10 + }, + "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_CALC_AVERAGE", + "tokenType": 21, + "valueType": "int", + "value": "256" + }, + { + "position": { + "line": 31, + "character": 10 + }, + "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_CALC_SUM", + "tokenType": 21, + "valueType": "int", + "value": "512" + }, + { + "position": { + "line": 84, + "character": 4 + }, + "identifier": "NWNX_SkillRanks_GetSkillFeatCountForSkill", + "tokenType": 3, + "returnType": "int", + "params": [ + { "position": { - "line": 7, - "character": 13 + "line": 84, + "character": 50 }, - "identifier": "NWNX_SkillRanks", - "tokenType": 21, - "valueType": "string", - "value": "\"NWNX_SkillRanks\"" + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [ + "/// @param iSkill The skill to check the feat count.", + "/// @return The count of feats for a specific skill." + ], + "isForwardDeclaration": true + }, + { + "position": { + "line": 90, + "character": 33 }, - { + "identifier": "NWNX_SkillRanks_GetSkillFeat", + "tokenType": 3, + "returnType": "NWNX_SkillRanks_SkillFeat", + "params": [ + { "position": { - "line": 14, - "character": 10 + "line": 91, + "character": 6 }, - "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_STRENGTH", - "tokenType": 21, - "valueType": "int", - "value": "1" + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 92, + "character": 6 + }, + "identifier": "iFeat", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [ + "/// @brief Returns a skill feat.", + "/// @param iSkill The skill.", + "/// @param iFeat The feat.", + "/// @return A constructed NWNX_SkillRanks_SkillFeat." + ], + "isForwardDeclaration": true + }, + { + "position": { + "line": 101, + "character": 0 }, - { + "identifier": "NWNX_SkillRanks_GetSkillFeatForSkillByIndex", + "tokenType": 3, + "returnType": "NWNX_SkillRanks_SkillFeat", + "params": [ + { "position": { - "line": 15, - "character": 10 + "line": 101, + "character": 48 }, - "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_DEXTERITY", - "tokenType": 21, - "valueType": "int", - "value": "2" + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 101, + "character": 60 + }, + "identifier": "iIndex", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [ + "/// @brief Returns a skill feat by index.", + "/// @remark Generally used in a loop with NWNX_SkillRanks_GetSkillFeatCountForSkill().", + "/// @param iSkill The skill.", + "/// @param iIndex The index in the list of feats for the skill.", + "/// @return A constructed NWNX_SkillRanks_SkillFeat." + ], + "isForwardDeclaration": true + }, + { + "position": { + "line": 107, + "character": 0 }, - { + "identifier": "NWNX_SkillRanks_SetSkillFeat", + "tokenType": 3, + "returnType": "void", + "params": [ + { "position": { - "line": 16, - "character": 10 + "line": 108, + "character": 35 }, - "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_CONSTITUTION", - "tokenType": 21, + "identifier": "skillFeat", + "tokenType": 25, + "valueType": "NWNX_SkillRanks_SkillFeat" + }, + { + "position": { + "line": 109, + "character": 6 + }, + "identifier": "createIfNonExistent", + "tokenType": 25, "valueType": "int", - "value": "4" + "defaultValue": "FALSE" + } + ], + "comments": [ + "/// @brief Modifies or creates a skill feat.", + "/// @param skillFeat The defined NWNX_SkillRanks_SkillFeat.", + "/// @param createIfNonExistent TRUE to create if the feat does not exist." + ], + "isForwardDeclaration": true + }, + { + "position": { + "line": 119, + "character": 0 }, - { + "identifier": "NWNX_SkillRanks_AddSkillFeatClass", + "tokenType": 3, + "returnType": "NWNX_SkillRanks_SkillFeat", + "params": [ + { "position": { - "line": 17, - "character": 10 + "line": 120, + "character": 35 }, - "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_INTELLIGENCE", - "tokenType": 21, - "valueType": "int", - "value": "8" + "identifier": "skillFeat", + "tokenType": 25, + "valueType": "NWNX_SkillRanks_SkillFeat" + }, + { + "position": { + "line": 121, + "character": 6 + }, + "identifier": "iClass", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [ + "/// @brief Add classes to a skill feat instead of working with the NWNX_SkillRanks_SkillFeat::sClasses string.", + "///", + "/// Manipulating the sClasses string in the NWNX_SkillRanks_SkillFeat struct can be difficult. This", + "/// function allows the builder to enter one class at a time.", + "/// @param skillFeat The NWNX_SkillRanks_SkillFeat for which the sClasses field will be modifier.", + "/// @param iClass The class to add to the Skill Feat.", + "/// @return The updated NWNX_SkillRanks_SkillFeat." + ], + "isForwardDeclaration": true + }, + { + "position": { + "line": 130, + "character": 5 }, - { + "identifier": "NWNX_SkillRanks_SetSkillFeatFocusModifier", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 130, + "character": 51 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + }, + { "position": { - "line": 18, - "character": 10 + "line": 130, + "character": 66 }, - "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_WISDOM", - "tokenType": 21, + "identifier": "iEpic", + "tokenType": 25, "valueType": "int", - "value": "16" + "defaultValue": "FALSE" + } + ], + "comments": [ + "/// @brief Change the modifier value for Skill Focus and Epic Skill Focus feats.", + "///", + "/// The stock modifier on Skill Focus and Epic Skill Focus are 3 and 10 respectively, these can be", + "/// changed with this function.", + "/// @param iModifier The new value for the feat modifier.", + "/// @param iEpic Set to TRUE to change the value for Epic Skill Focus." + ], + "isForwardDeclaration": true + }, + { + "position": { + "line": 134, + "character": 4 + }, + "identifier": "NWNX_SkillRanks_GetBlindnessPenalty", + "tokenType": 3, + "returnType": "int", + "params": [], + "comments": [ + "/// @brief Gets the current penalty to Dexterity based skills when blind.", + "/// @return The penalty to Dexterity when blind." + ], + "isForwardDeclaration": true + }, + { + "position": { + "line": 139, + "character": 5 }, - { + "identifier": "NWNX_SkillRanks_SetBlindnessPenalty", + "tokenType": 3, + "returnType": "void", + "params": [ + { "position": { - "line": 19, - "character": 10 + "line": 139, + "character": 45 }, - "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_CHARISMA", - "tokenType": 21, - "valueType": "int", - "value": "32" + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [ + "/// @brief Set the value the Dexterity based skills get decreased due to blindness.", + "/// @remark Default is 4.", + "/// @param iModifier The penalty to Dexterity when blind." + ], + "isForwardDeclaration": true + }, + { + "position": { + "line": 145, + "character": 4 }, - { + "identifier": "NWNX_SkillRanks_GetAreaModifier", + "tokenType": 3, + "returnType": "int", + "params": [ + { "position": { - "line": 28, - "character": 10 + "line": 145, + "character": 43 }, - "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_CALC_MIN", - "tokenType": 21, - "valueType": "int", - "value": "64" + "identifier": "oArea", + "tokenType": 25, + "valueType": "object" + }, + { + "position": { + "line": 145, + "character": 54 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [ + "/// @brief Get a skill modifier for an area.", + "/// @param oArea The area.", + "/// @param iSkill The skill to check.", + "/// @return The modifier to that skill in the area." + ], + "isForwardDeclaration": true + }, + { + "position": { + "line": 151, + "character": 5 }, - { + "identifier": "NWNX_SkillRanks_SetAreaModifier", + "tokenType": 3, + "returnType": "void", + "params": [ + { "position": { - "line": 29, - "character": 10 + "line": 151, + "character": 44 }, - "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_CALC_MAX", - "tokenType": 21, - "valueType": "int", - "value": "128" + "identifier": "oArea", + "tokenType": 25, + "valueType": "object" + }, + { + "position": { + "line": 151, + "character": 55 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 151, + "character": 67 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [ + "/// @brief Sets a skill modifier for the area.", + "/// @param oArea The area.", + "/// @param iSkill The skill to change.", + "/// @param iModifier The modifier to the skill in the area." + ], + "isForwardDeclaration": true + }, + { + "position": { + "line": 155, + "character": 4 + }, + "identifier": "NWNX_SkillRanks_GetSkillFeatCountForSkill", + "tokenType": 3, + "returnType": "int", + "params": [ + { + "position": { + "line": 155, + "character": 50 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [], + "isForwardDeclaration": false + }, + { + "position": { + "line": 165, + "character": 33 + }, + "identifier": "NWNX_SkillRanks_GetSkillFeatForSkillByIndex", + "tokenType": 3, + "returnType": "NWNX_SkillRanks_SkillFeat", + "params": [ + { + "position": { + "line": 165, + "character": 81 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 165, + "character": 93 + }, + "identifier": "iIndex", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [], + "isForwardDeclaration": false + }, + { + "position": { + "line": 190, + "character": 5 }, - { + "identifier": "NWNX_SkillRanks_SetSkillFeat", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 190, + "character": 67 + }, + "identifier": "skillFeat", + "tokenType": 25, + "valueType": "NWNX_SkillRanks_SkillFeat" + }, + { "position": { - "line": 30, - "character": 10 + "line": 190, + "character": 82 }, - "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_CALC_AVERAGE", - "tokenType": 21, + "identifier": "createIfNonExistent", + "tokenType": 25, "valueType": "int", - "value": "256" + "defaultValue": "FALSE" + } + ], + "comments": [], + "isForwardDeclaration": false + }, + { + "position": { + "line": 210, + "character": 33 + }, + "identifier": "NWNX_SkillRanks_AddSkillFeatClass", + "tokenType": 3, + "returnType": "NWNX_SkillRanks_SkillFeat", + "params": [ + { + "position": { + "line": 210, + "character": 100 + }, + "identifier": "skillFeat", + "tokenType": 25, + "valueType": "NWNX_SkillRanks_SkillFeat" + }, + { + "position": { + "line": 210, + "character": 115 + }, + "identifier": "iClass", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [], + "isForwardDeclaration": false + }, + { + "position": { + "line": 223, + "character": 5 }, - { + "identifier": "NWNX_SkillRanks_SetSkillFeatFocusModifier", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 223, + "character": 51 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + }, + { "position": { - "line": 31, - "character": 10 + "line": 223, + "character": 66 }, - "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_CALC_SUM", - "tokenType": 21, + "identifier": "epicFocus", + "tokenType": 25, "valueType": "int", - "value": "512" - }, - { - "position": { - "line": 84, - "character": 4 - }, - "identifier": "NWNX_SkillRanks_GetSkillFeatCountForSkill", - "tokenType": 3, - "returnType": "int", - "params": [ - { - "position": { - "line": 84, - "character": 50 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [ - "/// @param iSkill The skill to check the feat count.", - "/// @return The count of feats for a specific skill." - ] - }, - { - "position": { - "line": 90, - "character": 33 - }, - "identifier": "NWNX_SkillRanks_GetSkillFeat", - "tokenType": 3, - "returnType": "NWNX_SkillRanks_SkillFeat", - "params": [ - { - "position": { - "line": 91, - "character": 6 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" - }, - { - "position": { - "line": 92, - "character": 6 - }, - "identifier": "iFeat", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [ - "/// @brief Returns a skill feat.", - "/// @param iSkill The skill.", - "/// @param iFeat The feat.", - "/// @return A constructed NWNX_SkillRanks_SkillFeat." - ] - }, - { - "position": { - "line": 101, - "character": 0 - }, - "identifier": "NWNX_SkillRanks_GetSkillFeatForSkillByIndex", - "tokenType": 3, - "returnType": "NWNX_SkillRanks_SkillFeat", - "params": [ - { - "position": { - "line": 101, - "character": 48 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" - }, - { - "position": { - "line": 101, - "character": 60 - }, - "identifier": "iIndex", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [ - "/// @brief Returns a skill feat by index.", - "/// @remark Generally used in a loop with NWNX_SkillRanks_GetSkillFeatCountForSkill().", - "/// @param iSkill The skill.", - "/// @param iIndex The index in the list of feats for the skill.", - "/// @return A constructed NWNX_SkillRanks_SkillFeat." - ] - }, - { - "position": { - "line": 107, - "character": 0 - }, - "identifier": "NWNX_SkillRanks_SetSkillFeat", - "tokenType": 3, - "returnType": "void", - "params": [ - { - "position": { - "line": 108, - "character": 35 - }, - "identifier": "skillFeat", - "tokenType": 25, - "valueType": "NWNX_SkillRanks_SkillFeat" - }, - { - "position": { - "line": 109, - "character": 6 - }, - "identifier": "createIfNonExistent", - "tokenType": 25, - "valueType": "int", - "defaultValue": "FALSE" - } - ], - "comments": [ - "/// @brief Modifies or creates a skill feat.", - "/// @param skillFeat The defined NWNX_SkillRanks_SkillFeat.", - "/// @param createIfNonExistent TRUE to create if the feat does not exist." - ] - }, - { - "position": { - "line": 119, - "character": 0 - }, - "identifier": "NWNX_SkillRanks_AddSkillFeatClass", - "tokenType": 3, - "returnType": "NWNX_SkillRanks_SkillFeat", - "params": [ - { - "position": { - "line": 120, - "character": 35 - }, - "identifier": "skillFeat", - "tokenType": 25, - "valueType": "NWNX_SkillRanks_SkillFeat" - }, - { - "position": { - "line": 121, - "character": 6 - }, - "identifier": "iClass", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [ - "/// @brief Add classes to a skill feat instead of working with the NWNX_SkillRanks_SkillFeat::sClasses string.", - "///", - "/// Manipulating the sClasses string in the NWNX_SkillRanks_SkillFeat struct can be difficult. This", - "/// function allows the builder to enter one class at a time.", - "/// @param skillFeat The NWNX_SkillRanks_SkillFeat for which the sClasses field will be modifier.", - "/// @param iClass The class to add to the Skill Feat.", - "/// @return The updated NWNX_SkillRanks_SkillFeat." - ] - }, - { - "position": { - "line": 130, - "character": 5 - }, - "identifier": "NWNX_SkillRanks_SetSkillFeatFocusModifier", - "tokenType": 3, - "returnType": "void", - "params": [ - { - "position": { - "line": 130, - "character": 51 - }, - "identifier": "iModifier", - "tokenType": 25, - "valueType": "int" - }, - { - "position": { - "line": 130, - "character": 66 - }, - "identifier": "iEpic", - "tokenType": 25, - "valueType": "int", - "defaultValue": "FALSE" - } - ], - "comments": [ - "/// @brief Change the modifier value for Skill Focus and Epic Skill Focus feats.", - "///", - "/// The stock modifier on Skill Focus and Epic Skill Focus are 3 and 10 respectively, these can be", - "/// changed with this function.", - "/// @param iModifier The new value for the feat modifier.", - "/// @param iEpic Set to TRUE to change the value for Epic Skill Focus." - ] - }, - { - "position": { - "line": 134, - "character": 4 - }, - "identifier": "NWNX_SkillRanks_GetBlindnessPenalty", - "tokenType": 3, - "returnType": "int", - "params": [], - "comments": [ - "/// @brief Gets the current penalty to Dexterity based skills when blind.", - "/// @return The penalty to Dexterity when blind." - ] - }, - { - "position": { - "line": 139, - "character": 5 - }, - "identifier": "NWNX_SkillRanks_SetBlindnessPenalty", - "tokenType": 3, - "returnType": "void", - "params": [ - { - "position": { - "line": 139, - "character": 45 - }, - "identifier": "iModifier", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [ - "/// @brief Set the value the Dexterity based skills get decreased due to blindness.", - "/// @remark Default is 4.", - "/// @param iModifier The penalty to Dexterity when blind." - ] - }, - { - "position": { - "line": 145, - "character": 4 - }, - "identifier": "NWNX_SkillRanks_GetAreaModifier", - "tokenType": 3, - "returnType": "int", - "params": [ - { - "position": { - "line": 145, - "character": 43 - }, - "identifier": "oArea", - "tokenType": 25, - "valueType": "object" - }, - { - "position": { - "line": 145, - "character": 54 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [ - "/// @brief Get a skill modifier for an area.", - "/// @param oArea The area.", - "/// @param iSkill The skill to check.", - "/// @return The modifier to that skill in the area." - ] - }, - { - "position": { - "line": 151, - "character": 5 - }, - "identifier": "NWNX_SkillRanks_SetAreaModifier", - "tokenType": 3, - "returnType": "void", - "params": [ - { - "position": { - "line": 151, - "character": 44 - }, - "identifier": "oArea", - "tokenType": 25, - "valueType": "object" - }, - { - "position": { - "line": 151, - "character": 55 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" - }, - { - "position": { - "line": 151, - "character": 67 - }, - "identifier": "iModifier", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [ - "/// @brief Sets a skill modifier for the area.", - "/// @param oArea The area.", - "/// @param iSkill The skill to change.", - "/// @param iModifier The modifier to the skill in the area." - ] - } + "defaultValue": "FALSE" + } + ], + "comments": [], + "isForwardDeclaration": false + }, + { + "position": { + "line": 232, + "character": 4 + }, + "identifier": "NWNX_SkillRanks_GetBlindnessPenalty", + "tokenType": 3, + "returnType": "int", + "params": [], + "comments": [], + "isForwardDeclaration": false + }, + { + "position": { + "line": 241, + "character": 5 + }, + "identifier": "NWNX_SkillRanks_SetBlindnessPenalty", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 241, + "character": 45 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [], + "isForwardDeclaration": false + }, + { + "position": { + "line": 249, + "character": 4 + }, + "identifier": "NWNX_SkillRanks_GetAreaModifier", + "tokenType": 3, + "returnType": "int", + "params": [ + { + "position": { + "line": 249, + "character": 43 + }, + "identifier": "oArea", + "tokenType": 25, + "valueType": "object" + }, + { + "position": { + "line": 249, + "character": 54 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [], + "isForwardDeclaration": false + }, + { + "position": { + "line": 260, + "character": 5 + }, + "identifier": "NWNX_SkillRanks_SetAreaModifier", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 260, + "character": 44 + }, + "identifier": "oArea", + "tokenType": 25, + "valueType": "object" + }, + { + "position": { + "line": 260, + "character": 55 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 260, + "character": 67 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [], + "isForwardDeclaration": false + }, + { + "position": { + "line": 270, + "character": 33 + }, + "identifier": "NWNX_SkillRanks_GetSkillFeat", + "tokenType": 3, + "returnType": "NWNX_SkillRanks_SkillFeat", + "params": [ + { + "position": { + "line": 270, + "character": 66 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 270, + "character": 78 + }, + "identifier": "iFeat", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [], + "isForwardDeclaration": false + } ], "structComplexTokens": [ - { - "position": { - "line": 35, - "character": 7 - }, - "identifier": "NWNX_SkillRanks_SkillFeat", - "tokenType": 22, - "properties": [ - { - "position": { - "line": 37, - "character": 8 - }, - "identifier": "iSkill", - "tokenType": 10, - "valueType": "int" - }, - { - "position": { - "line": 38, - "character": 8 - }, - "identifier": "iFeat", - "tokenType": 10, - "valueType": "int" - }, - { - "position": { - "line": 41, - "character": 8 - }, - "identifier": "iModifier", - "tokenType": 10, - "valueType": "int" - }, - { - "position": { - "line": 45, - "character": 8 - }, - "identifier": "iFocusFeat", - "tokenType": 10, - "valueType": "int" - }, - { - "position": { - "line": 55, - "character": 11 - }, - "identifier": "sClasses", - "tokenType": 10, - "valueType": "string" - }, - { - "position": { - "line": 59, - "character": 10 - }, - "identifier": "fClassLevelMod", - "tokenType": 10, - "valueType": "float" - }, - { - "position": { - "line": 62, - "character": 8 - }, - "identifier": "iAreaFlagsRequired", - "tokenType": 10, - "valueType": "int" - }, - { - "position": { - "line": 65, - "character": 8 - }, - "identifier": "iAreaFlagsForbidden", - "tokenType": 10, - "valueType": "int" - }, - { - "position": { - "line": 69, - "character": 8 - }, - "identifier": "iDayOrNight", - "tokenType": 10, - "valueType": "int" - }, - { - "position": { - "line": 72, - "character": 8 - }, - "identifier": "bBypassArmorCheckPenalty", - "tokenType": 10, - "valueType": "int" - }, - { - "position": { - "line": 79, - "character": 8 - }, - "identifier": "iKeyAbilityMask", - "tokenType": 10, - "valueType": "int" - } - ] - } + { + "position": { + "line": 35, + "character": 7 + }, + "identifier": "NWNX_SkillRanks_SkillFeat", + "tokenType": 22, + "properties": [ + { + "position": { + "line": 37, + "character": 8 + }, + "identifier": "iSkill", + "tokenType": 10, + "valueType": "int" + }, + { + "position": { + "line": 38, + "character": 8 + }, + "identifier": "iFeat", + "tokenType": 10, + "valueType": "int" + }, + { + "position": { + "line": 41, + "character": 8 + }, + "identifier": "iModifier", + "tokenType": 10, + "valueType": "int" + }, + { + "position": { + "line": 45, + "character": 8 + }, + "identifier": "iFocusFeat", + "tokenType": 10, + "valueType": "int" + }, + { + "position": { + "line": 55, + "character": 11 + }, + "identifier": "sClasses", + "tokenType": 10, + "valueType": "string" + }, + { + "position": { + "line": 59, + "character": 10 + }, + "identifier": "fClassLevelMod", + "tokenType": 10, + "valueType": "float" + }, + { + "position": { + "line": 62, + "character": 8 + }, + "identifier": "iAreaFlagsRequired", + "tokenType": 10, + "valueType": "int" + }, + { + "position": { + "line": 65, + "character": 8 + }, + "identifier": "iAreaFlagsForbidden", + "tokenType": 10, + "valueType": "int" + }, + { + "position": { + "line": 69, + "character": 8 + }, + "identifier": "iDayOrNight", + "tokenType": 10, + "valueType": "int" + }, + { + "position": { + "line": 72, + "character": 8 + }, + "identifier": "bBypassArmorCheckPenalty", + "tokenType": 10, + "valueType": "int" + }, + { + "position": { + "line": 79, + "character": 8 + }, + "identifier": "iKeyAbilityMask", + "tokenType": 10, + "valueType": "int" + } + ] + } ], "children": [ - "nwnx" + "nwnx" ] } \ No newline at end of file