diff --git a/Extensions/DialogueTree/JsExtension.js b/Extensions/DialogueTree/JsExtension.js index 55350b422e95..0d26fec87773 100644 --- a/Extensions/DialogueTree/JsExtension.js +++ b/Extensions/DialogueTree/JsExtension.js @@ -264,6 +264,22 @@ module.exports = { .getCodeExtraInformation() .setFunctionName('gdjs.dialogueTree.setVariable'); + extension + .addAction( + 'DeleteVariable', + _('Delete dialogue state variable and its $nested.siblings'), + _( + 'Delete dialogue state variable and its $nested.siblings. with $a, $c, $c.tom and $c.james, targetting "c" would result in only $a remaining' + ), + _('Delete dialogue state variable and its $nested.siblings _PARAM0_'), + '', + 'JsPlatform/Extensions/yarn32.png', + 'JsPlatform/Extensions/yarn32.png' + ) + .addParameter('string', _('State Variable Name'), '', false) + .getCodeExtraInformation() + .setFunctionName('gdjs.dialogueTree.deleteDialogueStateVariable'); + extension .addAction( 'SaveState', @@ -311,6 +327,58 @@ module.exports = { .getCodeExtraInformation() .setFunctionName('gdjs.dialogueTree.clearState'); + extension + .addAction( + 'CreateNewActor', + _('Create a new actor'), + _( + 'Create a new dialog actor, which can be used to trigger/set events said actor is active' + ), + _( + 'Create a new dialog actor with ID of _PARAM0_, name _PARAM1_ and color _PARAM2_' + ), + '', + 'JsPlatform/Extensions/yarn32.png', + 'JsPlatform/Extensions/yarn32.png' + ) + .addParameter('string', _('Actor ID'), '', false) + .addParameter('string', _('Actor name'), '', false) + .addParameter('color', _('Actor color'), '', false) + .getCodeExtraInformation() + .setFunctionName('gdjs.dialogueTree.createNewActor'); + + extension + .addAction( + 'SetActorInfo', + _('Set actor variable'), + _( + 'Set variable of dialog actor. Any is allowed to be set, apart of id' + ), + _('Set dialog actor with ID of _PARAM0_ variable _PARAM1_ to _PARAM2_'), + '', + 'JsPlatform/Extensions/yarn32.png', + 'JsPlatform/Extensions/yarn32.png' + ) + .addParameter('string', _('Actor ID'), '', false) + .addParameter('string', _('Actor variable'), '', false) + .addParameter('string', _('Variable string value'), '', false) + .getCodeExtraInformation() + .setFunctionName('gdjs.dialogueTree.setActorInfo'); + + extension + .addAction( + 'DeleteActor', + _('Delete actor'), + _('Delete dialog actor'), + _('Delete a dialog actor with ID of _PARAM0_'), + '', + 'JsPlatform/Extensions/yarn32.png', + 'JsPlatform/Extensions/yarn32.png' + ) + .addParameter('string', _('Actor ID'), '', false) + .getCodeExtraInformation() + .setFunctionName('gdjs.dialogueTree.deleteActor'); + extension .addStrExpression( 'LineText', @@ -455,6 +523,20 @@ module.exports = { .getCodeExtraInformation() .setFunctionName('gdjs.dialogueTree.getCommandParameter'); + extension + .addStrExpression( + 'CommandParameterViaKey', + _('Get the parameter of a command call via a key'), + _( + 'Get the parameter of a command call via a key. For example: asking <> for a parameter with key "a" will return 22' + ), + '', + 'JsPlatform/Extensions/yarn32.png' + ) + .addParameter('string', _('parameter key'), '', true) + .getCodeExtraInformation() + .setFunctionName('gdjs.dialogueTree.getCommandParameterViaKey'); + extension .addExpression( 'CommandParametersCount', @@ -482,6 +564,20 @@ module.exports = { .getCodeExtraInformation() .setFunctionName('gdjs.dialogueTree.getTagParameter'); + extension + .addStrExpression( + 'TagValueViaKey', + _('Get a Tag value found in the dialogue branch, using its key where the pattern is key:value'), + _( + 'Get a Tag found in the dialogue branch, using a key. For example with tags: "bg:park", "time:lunch", asking for "bg" will return "park"' + ), + '', + 'JsPlatform/Extensions/yarn32.png' + ) + .addParameter('string', _('tag key'), '', true) + .getCodeExtraInformation() + .setFunctionName('gdjs.dialogueTree.getTagValueViaKey'); + extension .addStrExpression( 'VisitedBranchTitles', @@ -516,6 +612,102 @@ module.exports = { .getCodeExtraInformation() .setFunctionName('gdjs.dialogueTree.getVariable'); + extension + .addExpression( + 'VariableChildKeys', + _('Get variable number of child keys of a $nested.variable'), + _( + 'Get variable number of child keys. For example with $root.actor.james.id and $root.actor.tom.id, $root.actor has 2 - tom and james' + ), + '', + 'JsPlatform/Extensions/yarn32.png' + ) + .addParameter('string', _('Variable Name'), '', false) + .getCodeExtraInformation() + .setFunctionName('gdjs.dialogueTree.getKeysCount'); + + extension + .addStrExpression( + 'GetChildKeyViaIndex', + _('Get a $nested.variable child key via index'), + _( + 'Get a $nested.variable child key via index. For example with $c.tom.money, $c.tom.experience, targetting "c.tom" with index 1 will return "experience' + ), + '', + 'JsPlatform/Extensions/yarn32.png' + ) + .addParameter('string', _('Nested Variable name'), '', false) + .addParameter('expression', _('index number'), '', false) + .getCodeExtraInformation() + .setFunctionName('gdjs.dialogueTree.getChildKeyViaIndex'); + + extension + .addStrExpression( + 'ActiveActorId', + _('Get the Active Actor Id of the current dialogue line'), + _( + 'Get the Active Actor Id of the current dialogue line. Empty string when no actor detected' + ), + '', + 'JsPlatform/Extensions/yarn32.png' + ) + .getCodeExtraInformation() + .setFunctionName('gdjs.dialogueTree.getActiveLineActorId'); + + extension + .addExpression( + 'ActiveActorParametersCount', + _( + 'Get the number of parameters passed after an actor id on the active line' + ), + _( + 'Get the number of parameters passed after an actor id on the active line' + ), + '', + 'JsPlatform/Extensions/yarn32.png' + ) + .getCodeExtraInformation() + .setFunctionName('gdjs.dialogueTree.getActiveLineActorParametersCount'); + + extension + .addStrExpression( + 'ActiveLineActorParameter', + _('Get a parameter after an actor id on the active line via its index'), + _( + 'Get a parameter after an actor id on the active line via its index. For example the line "tom happy: Its my birthday!" has one parameter "happy" which you can get via index 0' + ), + '', + 'JsPlatform/Extensions/yarn32.png' + ) + .addParameter('expression', _('index number'), '', false) + .getCodeExtraInformation() + .setFunctionName('gdjs.dialogueTree.getActiveLineActorParameterViaIndex'); + + extension + .addExpression( + 'ActorInfo', + _('Get Actor variable via actor ID'), + _('Get Actor variable via actor ID'), + '', + 'JsPlatform/Extensions/yarn32.png' + ) + .addParameter('string', _('Actor ID'), '', false) + .addParameter('string', _('Variable Name'), '', false) + .getCodeExtraInformation() + .setFunctionName('gdjs.dialogueTree.getActorInfo'); + + extension + .addExpression( + 'ActiveActorInfo', + _('Get current active line Actor variable'), + _('Get current active line Actor variable'), + '', + 'JsPlatform/Extensions/yarn32.png' + ) + .addParameter('string', _('Variable Name'), '', false) + .getCodeExtraInformation() + .setFunctionName('gdjs.dialogueTree.getActiveActorInfo'); + extension .addCondition( 'IsCommandCalled', @@ -532,6 +724,22 @@ module.exports = { .getCodeExtraInformation() .setFunctionName('gdjs.dialogueTree.isCommandCalled'); + extension + .addCondition( + 'IsCommandParameterPresent', + _('Command is called with a specific parameter'), + _( + 'Check if a specific parameter is present in the Command that was called. If it is a <>, you can check if withParameter or anotherParameter exists.' + ), + _('Command is called with parameter _PARAM0_'), + '', + 'JsPlatform/Extensions/yarn32.png', + 'JsPlatform/Extensions/yarn32.png' + ) + .addParameter('string', _('Command Parameter'), '', false) + .getCodeExtraInformation() + .setFunctionName('gdjs.dialogueTree.commandHasParameter'); + extension .addCondition( 'IsDialogueLineType', @@ -645,6 +853,18 @@ module.exports = { .getCodeExtraInformation() .setFunctionName('gdjs.dialogueTree.branchTitleHasBeenVisited'); + extension + .addCondition( + 'BranchNodeHasChanged', + _('Branch node has changed'), + _('Current branch node has changed'), + _('Current branch node has changed'), + '', + 'JsPlatform/Extensions/yarn32.png', + 'JsPlatform/Extensions/yarn32.png' + ) + .setFunctionName('gdjs.dialogueTree.branchTitleHasChanged'); + extension .addCondition( 'CompareDialogueStateStringVariable', @@ -711,6 +931,82 @@ module.exports = { .getCodeExtraInformation() .setFunctionName('gdjs.dialogueTree.hasClippedScrollingCompleted'); + extension + .addCondition( + 'HasActiveActorChanged', + _('Active Actor has changed'), + _( + 'Check if the displayed dialogue text active actor has changed from the previous line.' + ), + _('Active actor has changed'), + '', + 'JsPlatform/Extensions/yarn32.png', + 'JsPlatform/Extensions/yarn32.png' + ) + .getCodeExtraInformation() + .setFunctionName('gdjs.dialogueTree.hasActiveActorChanged'); + + extension + .addCondition( + 'LineHasActiveActor', + _('An Actor is currently present in the dialog line'), + _( + 'An Actor is currently present in the dialog line' + ), + _('Actor is present in current dialog line'), + '', + 'JsPlatform/Extensions/yarn32.png', + 'JsPlatform/Extensions/yarn32.png' + ) + .getCodeExtraInformation() + .setFunctionName('gdjs.dialogueTree.lineHasActiveActor'); + + + extension + .addCondition( + 'ActiveActorLineHasParameter', + _('Active Actor line has parameter'), + _( + 'Check if the displayed dialogue text active actor has a parameter. example "tom left: I am on the left!" the parameter "left" exists' + ), + _('Active actor line has a parameter _PARAM0_'), + '', + 'JsPlatform/Extensions/yarn32.png', + 'JsPlatform/Extensions/yarn32.png' + ) + .addParameter('string', _('Active Actor Line Parameter'), '', false) + .getCodeExtraInformation() + .setFunctionName('gdjs.dialogueTree.getActiveLineParameterExists'); + + + extension + .addCondition( + 'DoesActorExist', + _('Does actor with ID exist'), + _('Check if an actor with a specified id has been ecreated'), + _('Actor with ID _PARAM0_ exists'), + '', + 'JsPlatform/Extensions/yarn32.png', + 'JsPlatform/Extensions/yarn32.png' + ) + .addParameter('string', _('Actor ID'), '', false) + .getCodeExtraInformation() + .setFunctionName('gdjs.dialogueTree.getActorExists'); + + extension + .addCondition( + 'DoesVariableExist', + _('Does variable exist'), + _('Check if a dialogue state variable exists or has been set'), + _('Dialogue state variable _PARAM0_ exists'), + '', + 'JsPlatform/Extensions/yarn32.png', + 'JsPlatform/Extensions/yarn32.png' + ) + .addParameter('string', _('Variable'), '', false) + .getCodeExtraInformation() + .setFunctionName('gdjs.dialogueTree.getVariableExists'); + return extension; }, runExtensionSanityTests: function (gd, extension) { diff --git a/Extensions/DialogueTree/dialoguetools.ts b/Extensions/DialogueTree/dialoguetools.ts index 25e2aeccf42c..300c1221be39 100644 --- a/Extensions/DialogueTree/dialoguetools.ts +++ b/Extensions/DialogueTree/dialoguetools.ts @@ -95,6 +95,14 @@ namespace gdjs { return this.dialogueIsRunning; }; + gdjs.dialogueTree.getText = function () { + const dialogueText = gdjs.dialogueTree.dialogueText; + if (!gdjs.dialogueTree.dialogueIsRunning || dialogueText.length === 0) + return ''; + + return dialogueText.substring(this.clipTextStart, dialogueText.length); + }; + /** * Scroll the clipped text. This can be combined with a timer and user input to control how fast the dialogue line text is scrolling. */ @@ -171,9 +179,8 @@ namespace gdjs { * Used with the scrollClippedText to achieve a classic scrolling text, as well as any <> effects to pause scrolling. */ gdjs.dialogueTree.getClippedLineText = function () { - return this.dialogueIsRunning && this.dialogueText.length - ? this.dialogueText.substring(0, this.clipTextEnd + 1) - : ''; + const dialogueText = gdjs.dialogueTree.getText(); + return dialogueText.substring(0, gdjs.dialogueTree.clipTextEnd + 1); }; /** @@ -181,9 +188,7 @@ namespace gdjs { * Note that using this instead getClippedLineText will skip any <> commands entirely. */ gdjs.dialogueTree.getLineText = function () { - return this.dialogueIsRunning && this.dialogueText.length - ? this.dialogueText - : ''; + return gdjs.dialogueTree.getText(); }; /** @@ -414,9 +419,8 @@ namespace gdjs { return; } if (this.dialogueData.select) { - this.selectedOption = gdjs.dialogueTree._normalizedOptionIndex( - optionIndex - ); + this.selectedOption = + gdjs.dialogueTree._normalizedOptionIndex(optionIndex); this.selectedOptionUpdated = true; } }; @@ -548,12 +552,23 @@ namespace gdjs { return this.dialogueData instanceof bondage.CommandResult; }; + gdjs.dialogueTree.clipTextStart = 0; + gdjs.dialogueTree.activeLineActor = ''; + gdjs.dialogueTree.activeLineActorParameters = []; + gdjs.dialogueTree.resetActiveLineActor = function () { + gdjs.dialogueTree.clipTextStart = 0; + gdjs.dialogueTree.activeLineActor = ''; + gdjs.dialogueTree.activeLineActorParameters = []; + }; + /** * This is the main lifecycle function.It runs once only when the user is advancing the dialogue to the next line. * Progress Dialogue to the next line. Hook it to your game input. * Note that this action can be influenced by any <> commands, but they work only if you have at least one isCommandCalled condition. */ gdjs.dialogueTree.goToNextDialogueLine = function () { + gdjs.dialogueTree.resetActiveLineActor(); + if (this.pauseScrolling || !this.dialogueIsRunning) { return; } @@ -578,6 +593,22 @@ namespace gdjs { this.clipTextEnd = 0; this.dialogueText = this.dialogueData.text; } + const text = this.dialogueData.text; + const splitIndex = text.indexOf(':'); + if (splitIndex !== -1) { + const firstHalf = text.substring(0, splitIndex).split(' '); + const [actorId, ...actorParameters] = firstHalf; + const secondHalf = text.substring(splitIndex + 1, text.length); + if (gdjs.dialogueTree.getActorExists(actorId)) { + gdjs.dialogueTree.clipTextStart = splitIndex + 1; + gdjs.dialogueTree.activeLineActor = actorId; + gdjs.dialogueTree.activeLineActorParameters = actorParameters; + } else { + gdjs.dialogueTree.resetActiveLineActor(); + } + } else { + gdjs.dialogueTree.resetActiveLineActor(); + } this.dialogueBranchTags = this.dialogueData.data.tags; this.dialogueBranchTitle = this.dialogueData.data.title; this.dialogueBranchBody = this.dialogueData.data.body; @@ -585,6 +616,8 @@ namespace gdjs { this.dialogueDataType = 'text'; this.dialogueData = this.dialogue.next().value; } else { + gdjs.dialogueTree.resetActiveLineActor(); + if (gdjs.dialogueTree._isLineTypeOptions()) { this.commandCalls = []; this.dialogueDataType = 'options'; @@ -619,6 +652,202 @@ namespace gdjs { } }; + gdjs.dialogueTree.prevActiveLineActor = ''; + gdjs.dialogueTree.prevActiveLineActorParams = []; + /** + * Condition to check if the active line actor has changed + * For example if you have set two actors - tom and james and these three dialogue lines in yarn + * tom: Hi James + * james: Hi, Tom. + * james: how are you doing? + * This condition will be triggered once - when after tom's line. + */ + gdjs.dialogueTree.hasActiveActorChanged = function () { + if (!this.dialogueIsRunning || this.dialogueDataType !== 'text') + return false; + if ( + gdjs.dialogueTree.prevActiveLineActor !== + gdjs.dialogueTree.activeLineActor || + gdjs.dialogueTree.prevActiveLineActorParams !== + gdjs.dialogueTree.activeLineActorParameters + ) { + return true; + } + return false; + }; + + gdjs.dialogueTree.prevDialogueBranchTitle = ''; + /** + * Check if a player has visited a new Dialogue Branch. + */ + gdjs.dialogueTree.branchTitleHasChanged = function () { + return ( + this.dialogueIsRunning && + this.dialogueBranchTitle !== gdjs.dialogueTree.prevDialogueBranchTitle + ); + }; + + gdjs.registerRuntimeScenePostEventsCallback(() => { + gdjs.dialogueTree.prevActiveLineActor = gdjs.dialogueTree.activeLineActor; + gdjs.dialogueTree.prevActiveLineActorParams = + gdjs.dialogueTree.activeLineActorParameters; + gdjs.dialogueTree.prevDialogueBranchTitle = + gdjs.dialogueTree.dialogueBranchTitle; + }); + + /** + * Condition to check if an actor exists + * You can use it to determine if an actor has been set + */ + gdjs.dialogueTree.getActorExists = function (actorId: string) { + return gdjs.dialogueTree.getVariableExists(`a.${actorId}.id`); + }; + + /** + * Command to create a new actor + * You can use it to initiate a new actor in a consistent manner that is acceptable to the actor api + * @param actorId the id that will be used to detect if an actor is talking during a dialogue line. It will also be used as the actor variables root key + * @param actorName use this to set the name of the actor as it will be displayed to the player + * @param actorColor use this to set a color for an actor which the player can associate with. For example can be used to set the text color when speaking + */ + gdjs.dialogueTree.createNewActor = function ( + actorId: string, + actorName: string, + actorColor: string + ) { + if (gdjs.dialogueTree.getActorExists(actorId)) return; + gdjs.dialogueTree.setVariable(`a.${actorId}.id`, actorId); + gdjs.dialogueTree.setVariable(`a.${actorId}.name`, actorName); + gdjs.dialogueTree.setVariable(`a.${actorId}.color`, actorColor); + }; + + /** + * Command to delete an actor + * You can use it to initiate a new actor in a consistent manner that is acceptable to the actor api + * @param actorId the id of the actor that you want deleted + */ + gdjs.dialogueTree.deleteActor = function (actorId: string) { + gdjs.dialogueTree.deleteDialogueStateVariable(`a.${actorId}`); + }; + + /** + * Detect is the currently active line contains a registered character + */ + gdjs.dialogueTree.lineHasActiveActor = function () { + if (this.dialogueIsRunning) { + return ( + gdjs.dialogueTree.activeLineActor != null && + gdjs.dialogueTree.activeLineActor !== '' + ); + } + return false; + }; + + /** + * Returns the id of the actor that is currently speaking. Useful for expressions + * For example if you have a line like this in yarn + * tom happy blink: I am feeling good today! + * this will return "tom" + */ + gdjs.dialogueTree.getActiveLineActorId = function () { + return gdjs.dialogueTree.activeLineActor; + }; + + /** + * Returns a list of words after the active line's actor + * For example if you have a line like this in yarn + * tom happy blink: I am feeling good today! + * This will return ["happy", "blink"], + */ + gdjs.dialogueTree.getActiveLineActorParameters = function () { + return gdjs.dialogueTree.activeLineActorParameters; + }; + + /** + * Returns length of the list of words after the active line's actor + * For example if you have a line like this in yarn + * tom happy blink: I am feeling good today! + * This will return 2 + */ + gdjs.dialogueTree.getActiveLineActorParametersCount = function () { + return gdjs.dialogueTree.activeLineActorParameters.length; + }; + + /** + * Returns a list of words after the active line's actor + * For example if you have a line like this in yarn + * tom happy blink: I am feeling good today! + * This will yield ["happy", "blink"] and if your paramIndex is 1, "blink" will be the result + * @param paramIndex the id that will be used to detect if an actor is talking during a dialogue line. It will also be used as the actor variables root key + */ + gdjs.dialogueTree.getActiveLineActorParameterViaIndex = function ( + paramIndex: number + ) { + const activeLineActorParameters = + gdjs.dialogueTree.activeLineActorParameters; + if ( + paramIndex > -1 && + activeLineActorParameters.length > 0 && + paramIndex < activeLineActorParameters.length + ) { + return activeLineActorParameters[paramIndex] || ''; + } + return ''; + }; + + /** + * Checks if active actor line has a specific parameter + * for example if the line is: + * tom left: I am now standing on the left side + * if we search for the parameter "left" this will return true + * @param query the parameter to look for + */ + gdjs.dialogueTree.getActiveLineParameterExists = function (query: string) { + if (this.dialogueDataType !== 'text') return false; + return gdjs.dialogueTree.activeLineActorParameters.some( + (parameter) => parameter === query + ); + }; + + /** + * Set existing actor's variables. Note that you cannot change an actor's id programatically. + * For example if you have created an actor with id=tom, you can target him via that actorId and change any nested variable of that actor + * @param actorId the actor id of the actor you want to change + * @param infoKey the variable name of that actor you want changing. Example "color" + * @param newValue the new value you want to assign to that variable + */ + gdjs.dialogueTree.setActorInfo = function ( + actorId: string, + infoKey: string, + newValue: string + ) { + if (infoKey != 'id' && gdjs.dialogueTree.getActorExists(actorId)) { + gdjs.dialogueTree.setVariable(`a.${actorId}.${infoKey}`, newValue); + } + }; + + /** + * Get existing actor's variable value. + * For example if you have created an actor with id=tom, you can target him via that actorId and get any nested variable of that actor + * @param actorId the actor id of the actor you want to change + * @param infoKey the variable name of that actor you want getting. Example "color" + */ + gdjs.dialogueTree.getActorInfo = function (actorId: string, infoKey: string) { + if (gdjs.dialogueTree.getActorExists(actorId)) { + return gdjs.dialogueTree.getVariable(`a.${actorId}.${infoKey}`); + } + return ''; + }; + + /** + * Get the active actor's variable value. + * @param infoKey the variable name of the active actor you want getting. Example "color" + */ + gdjs.dialogueTree.getActiveActorInfo = function (infoKey: string) { + const actorId = gdjs.dialogueTree.activeLineActor; + return gdjs.dialogueTree.getActorInfo(actorId, infoKey); + }; + /** * Get the current Dialogue Tree branch title. * @returns The current branch title. @@ -631,7 +860,7 @@ namespace gdjs { }; /** - * Check if the currently parsed Dialogue branch title is a query. + * Check if the currently parsed Dialogue branch title is a query. * @param title The Dialogue Branch name you want to check for. */ gdjs.dialogueTree.branchTitleIs = function (title: string) { @@ -675,6 +904,10 @@ namespace gdjs { this.tagParameters = []; if (this.dialogueIsRunning && this.dialogueBranchTags.length) { return this.dialogueBranchTags.some(function (tag) { + if (tag.includes(':')) { + const splitTag = tag.split(':'); + return splitTag[0] === query; + } const splitTag = tag.match(/([^\(]+)\(([^\)]+)\)/i); gdjs.dialogueTree.tagParameters = splitTag ? splitTag[2].split(',') @@ -698,6 +931,28 @@ namespace gdjs { return ''; }; + /** + * Get a dialogue node tag value via a key, where the pattern of the tag is tagKey:value and what is returned is value + * For example if you have the tags set in yarn as: "bg:park time:noon" + * asking for tagKey "time" will return "noon". This is useful for nodes with alot of tags + * @param tagKey The key of the tag to get. + */ + gdjs.dialogueTree.getTagValueViaKey = function (tagKey: string) { + if (tagKey === '') { + return ''; + } + if (this.dialogueIsRunning && this.dialogueBranchTags.length > 0) { + const parameterWithKey = this.dialogueBranchTags.find((tag) => + tag.startsWith(`${tagKey}:`) + ); + if (parameterWithKey) { + const [_, returnedParam] = parameterWithKey.split(':'); + return returnedParam ? returnedParam : ''; + } + } + return ''; + }; + /** * Get a list of all the titles of visited by the player Branches. Useful for debugging. */ @@ -773,6 +1028,120 @@ namespace gdjs { } }; + /** + * Check if a specific variable has been set/exists. + * @param key The name of the variable you want to check if it exists + */ + gdjs.dialogueTree.getVariableExists = function (key: string) { + return this.runner.variables && key in this.runner.variables.data; + }; + + /** + * Get a list of all subkeys of variable with a key using a $nested.syntax + * for example if you have these two variables: + * $root.actor.james.id and $root.actor.tom.id set to something + * targetting "root.actor" will return ["james", "tom"] + * @param targetKey the key of the variable you want target + */ + gdjs.dialogueTree.getChildKeysOfNestedVariable = function ( + targetKey: string + ) { + const variables = gdjs.dialogueTree.runner.variables.data; + const result = []; + Object.keys(variables).forEach((key) => { + if (key.startsWith(`${targetKey}.`)) { + const subpath = key.substring(targetKey.length + 1, key.length); + const subPathRoot = subpath.split('.')[0]; + + if (!result.includes(subPathRoot)) result.push(subPathRoot); + } + }); + + return result; + }; + + /** + * Delete a dialogue state variable, targetting it via key + * If the variable has siblings using a nested key syntax, they will also be deleted + * for example if you have these two variables: + * $root.actor.james.id and $root.actor.tom.id + * targetting "$root.actor" will delete both of them + * @param targetKey the key of the variable you want target + */ + gdjs.dialogueTree.deleteDialogueStateVariable = (targetKey: string) => { + const variables = this.runner.variables.data; + Object.keys(variables).forEach((key) => { + if (key.startsWith(`${targetKey}.` || key === targetKey)) { + delete variables[key]; + } + }); + }; + + /** + * Get the number of child keys of a nested variable + * for example if you have these two variables: + * $root.actor.james.id and $root.actor.tom.id + * targetting "$root.actor" will return 2 + * @param targetKey the key of the variable you want target + */ + gdjs.dialogueTree.getKeysCount = function (targetKey: string) { + return gdjs.dialogueTree.getChildKeysOfNestedVariable(targetKey).length; + }; + + /** + * Get one of the child keys from a nested variable, using its index in the list + * for example if you have these two variables: + * $root.actor.james.id and $root.actor.tom.id + * targetting "$root.actor" with index of 1 will return "tom" + * @param targetKey the key of the variable you want target + * @param targetIndex the index of the resulting key you want + */ + gdjs.dialogueTree.getChildKeyViaIndex = function ( + targetKey: string, + targetIndex: index + ) { + const childKeys = gdjs.dialogueTree.getChildKeysOfNestedVariable(targetKey); + if (targetIndex < childKeys.length) { + return childKeys[targetIndex]; + } + return ''; + }; + + /** + * Get a command parameter via key, where the pattern of the parameter is key=value and what is returned is value + * For example if you have a command in yarn with <> that was just triggered + * using the paramKey "name" will return "Thomas". This is useful for commands with a lot of parameters + * @param paramKey The key of the parameter to get. + */ + gdjs.dialogueTree.getCommandParameterViaKey = function (paramKey: string) { + if (paramKey === '') { + return ''; + } + if (this.commandParameters && this.commandParameters.length > 0) { + const parameterWithKey = this.commandParameters.find((parameter) => + parameter.startsWith(`${paramKey}=`) + ); + if (parameterWithKey) { + const [_, returnedParam] = parameterWithKey.split('='); + return returnedParam ? returnedParam : ''; + } + } + return ''; + }; + + /** + * Check if the currently executed <> contains a specific parameter. + * @param parameter The name of the Dialogue Branch tag you want to check. + */ + gdjs.dialogueTree.commandHasParameter = function (query: string) { + if (this.commandParameters && this.commandParameters.length > 0) { + return this.commandParameters.some(function (parameter) { + return parameter === query || parameter.startsWith(`${query}=`); + }); + } + return false; + }; + /** * Store the current State of the Dialogue Parser in a specified variable. * Can be used to implement persistence in dialogue through your game's Load/Save function.