Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# Changelog

## [0.8.4]

### _Feature_

- `Persistent.equals`, `Persistent.notEquals` now support lambda or lambda handler as argument
- Added `ScriptCtx.$` to get the namespace
- Added `Script.execute` to execute a script

### Fixed

- Script element is not executed correctly

## [0.8.3]

### _Feature_

- `Persistent.equals`, `Persistent.notEquals`, `Persistent.assign` now support function evaluator as argument
- Added `hidden` and `disabled` config to `Menu` choice
- Added `Menu.hideIf` and `Menu.disableIf` magic methods
- Added `Menu.enableWhen` and `Menu.showWhen`

## [0.8.2]

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "narraleaf-react",
"version": "0.8.2",
"version": "0.8.4",
"description": "A React visual novel player framework",
"main": "./dist/main.js",
"types": "./dist/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion src/game/nlcore/action/actionTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ export type PersistentActionContentType = {
[K in typeof PersistentActionTypes[keyof typeof PersistentActionTypes]]:
K extends "persistent:action" ? any :
K extends "persistent:set" ? [string, unknown | ((value: unknown) => unknown)] :
K extends "persistent:assign" ? [Partial<unknown>] :
K extends "persistent:assign" ? [Partial<unknown> | ((value: unknown) => Partial<unknown>)] :
any;
}
/* Layer */
Expand Down
3 changes: 2 additions & 1 deletion src/game/nlcore/action/actions/persistentAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,13 @@ export class PersistentAction<T extends Values<typeof PersistentActionTypes> = V

return super.executeAction(gameState, injection);
} else if (action.is<PersistentAction<"persistent:assign">>(PersistentAction, "persistent:assign")) {
const [value] = (action.contentNode as ContentNode<PersistentActionContentType["persistent:assign"]>).getContent() as [Partial<PersistentContent>];
const [arg0] = (action.contentNode as ContentNode<PersistentActionContentType["persistent:assign"]>).getContent() as [Partial<PersistentContent> | ((value: PersistentContent) => Partial<PersistentContent>)];
const namespace = gameState.getStorable().getNamespace(
action.callee.getNamespaceName()
) as Namespace<PersistentContent>;
const prevValue: Partial<PersistentContent> = {};

const value = typeof arg0 === "function" ? arg0(namespace.getContent()) : arg0;
Object.keys(value).forEach((key: string) => {
prevValue[key] = namespace.get(key);
namespace.set(key, value[key]);
Expand Down
1 change: 1 addition & 0 deletions src/game/nlcore/elements/built-in/Gallery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ export class Gallery<Metadata extends Record<string, any>> extends Service<Galle
game: ctx.game,
liveGame: ctx.liveGame,
storable: ctx.storable,
$: ctx.$,
};
const parsedMetadata = typeof metadata === "function" ? metadata(context) : metadata;
this.unlocked[name] = parsedMetadata;
Expand Down
16 changes: 12 additions & 4 deletions src/game/nlcore/elements/condition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ export class Lambda<T = any> {
return value instanceof Lambda && "handler" in value;
}

/**@internal */
public static isLambdaHandler(value: any): value is LambdaHandler {
return typeof value === "function";
}

/**@internal */
public static from<T>(obj: Lambda<T> | LambdaHandler<T>): Lambda<T> {
return Lambda.isLambda(obj) ? obj : new Lambda(obj);
Expand All @@ -40,11 +45,14 @@ export class Lambda<T = any> {

/**@internal */
getCtx({ gameState }: { gameState: GameState }): LambdaCtx {
const liveGame = gameState.game.getLiveGame();
const storable = liveGame.getStorable();
return {
gameState,
game: gameState.game,
liveGame: gameState.game.getLiveGame(),
storable: gameState.game.getLiveGame().getStorable(),
liveGame,
storable,
$: (namespace: string) => storable.getNamespace(namespace),
};
}

Expand Down Expand Up @@ -123,7 +131,7 @@ export class Condition<Closed extends true | false = false> extends Actionable<n
}

this.conditions.ElseIf.push({
condition: Lambda.isLambda(condition) ? condition : new Lambda(condition),
condition: Lambda.from(condition),
action: this.construct(Array.isArray(action) ? action : [action])
});
return this.chain() as Closed extends false ? Proxied<Condition<true>, Chained<LogicAction.Actions>> : never;
Expand Down Expand Up @@ -206,7 +214,7 @@ export class Condition<Closed extends true | false = false> extends Actionable<n
private createIfCondition(
condition: Lambda | LambdaHandler<boolean>, action: ActionStatements
): Proxied<Condition, Chained<LogicAction.Actions>> {
this.conditions.If.condition = condition instanceof Lambda ? condition : new Lambda(condition);
this.conditions.If.condition = Lambda.from(condition);
this.conditions.If.action = this.construct(action);

const chained = this.chain();
Expand Down
108 changes: 102 additions & 6 deletions src/game/nlcore/elements/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,37 @@ import {Sentence, SentencePrompt} from "@core/elements/character/sentence";
import {Word} from "@core/elements/character/word";
import {MenuAction} from "@core/action/actions/menuAction";
import Actions = LogicAction.Actions;
import { ActionStatements } from "./type";
import { ActionStatements, LambdaHandler } from "./type";
import { Narrator } from "./character";
import { Lambda } from "./condition";
import { StaticScriptWarning } from "../common/Utils";

/* eslint-disable @typescript-eslint/no-empty-object-type */
export type MenuConfig = {};
export type MenuChoice = {
action: ActionStatements;
prompt: SentencePrompt | Sentence;
config?: {
disabled?: Lambda<boolean> | LambdaHandler<boolean>;
hidden?: Lambda<boolean> | LambdaHandler<boolean>;
};
};

export type Choice = {
action: Actions[];
prompt: Sentence;
config: ChoiceConfig;
};

export type MenuData = {
prompt: Sentence | null;
choices: Choice[];
}
};

export type ChoiceConfig = {
disabled?: Lambda<boolean>;
hidden?: Lambda<boolean>;
};

export class Menu extends Actionable<any, Menu> {
/**@internal */
Expand Down Expand Up @@ -83,11 +95,18 @@ export class Menu extends Actionable<any, Menu> {
public choose(arg0: Sentence | MenuChoice | SentencePrompt, arg1?: ActionStatements): Proxied<Menu, Chained<LogicAction.Actions>> {
const chained = this.chain();
if (Sentence.isSentence(arg0) && arg1) {
chained.choices.push({prompt: Sentence.toSentence(arg0), action: this.narrativeToActions(arg1)});
chained.choices.push({prompt: Sentence.toSentence(arg0), action: this.narrativeToActions(arg1), config: {}});
} else if ((Word.isWord(arg0) || Array.isArray(arg0) || typeof arg0 === "string") && arg1) {
chained.choices.push({prompt: Sentence.toSentence(arg0), action: this.narrativeToActions(arg1)});
chained.choices.push({prompt: Sentence.toSentence(arg0), action: this.narrativeToActions(arg1), config: {}});
} else if (typeof arg0 === "object" && "prompt" in arg0 && "action" in arg0) {
chained.choices.push({prompt: Sentence.toSentence(arg0.prompt), action: this.narrativeToActions(arg0.action)});
chained.choices.push({
prompt: Sentence.toSentence(arg0.prompt),
action: this.narrativeToActions(arg0.action),
config: {
disabled: arg0.config?.disabled ? Lambda.from(arg0.config.disabled) : undefined,
hidden: arg0.config?.hidden ? Lambda.from(arg0.config.hidden) : undefined
}
});
} else {
console.warn("No valid choice added to menu, ", {
arg0,
Expand All @@ -97,6 +116,82 @@ export class Menu extends Actionable<any, Menu> {
return chained;
}

/**
* Magic method to hide the last choice if the condition is true
* @example
* ```ts
* menu.choose(
* // ...
* ).hideIf(persis.isTrue("flag"));
* ```
*
* **Note**: This method will override the last choice's config.hidden
*/
public hideIf(condition: Lambda<boolean> | LambdaHandler<boolean>): Proxied<Menu, Chained<LogicAction.Actions>> {
const lastChoice = this.choices[this.choices.length - 1];
if (!lastChoice) {
throw new StaticScriptWarning("Trying to configure the last choice of a menu, but no choice added. This may be caused by calling `menu.hideIf` before `menu.choose`");
}
lastChoice.config.hidden = Lambda.from(condition);
return this.chain();
}

/**
* Magic method to disable the last choice if the condition is true
* @example
* ```ts
* menu.choose(
* // ...
* ).disableIf(persis.isTrue("flag"));
* ```
*/
public disableIf(condition: Lambda<boolean> | LambdaHandler<boolean>): Proxied<Menu, Chained<LogicAction.Actions>> {
const lastChoice = this.choices[this.choices.length - 1];
if (!lastChoice) {
throw new StaticScriptWarning("Trying to configure the last choice of a menu, but no choice added. This may be caused by calling `menu.disableIf` before `menu.choose`");
}
lastChoice.config.disabled = Lambda.from(condition);
return this.chain();
}

/**
* Add a choice, only enable when the condition is true
* @example
* ```ts
* menu.enableWhen(persis.isTrue("flag"), "Go left", [
* character.say("I went left")
* ]);
* ```
*/
public enableWhen(condition: Lambda<boolean> | LambdaHandler<boolean>, prompt: Sentence, action: ActionStatements): Proxied<Menu, Chained<LogicAction.Actions>> {
return this.choose({
prompt,
action,
config: {
disabled: Lambda.from(condition)
}
});
}

/**
* Add a choice, only show when the condition is true
* @example
* ```ts
* menu.showWhen(persis.isTrue("flag"), "Go left", [
* character.say("I went left")
* ]);
* ```
*/
public showWhen(condition: Lambda<boolean> | LambdaHandler<boolean>, prompt: Sentence, action: ActionStatements): Proxied<Menu, Chained<LogicAction.Actions>> {
return this.choose({
prompt,
action,
config: {
hidden: Lambda.from(condition)
}
});
}

/**@internal */
public override fromChained(chained: Proxied<Menu, Chained<LogicAction.Actions>>): LogicAction.Actions[] {
return [
Expand Down Expand Up @@ -150,7 +245,8 @@ export class Menu extends Actionable<any, Menu> {
return this.choices.map(choice => {
return {
action: this.constructNodes(choice.action),
prompt: choice.prompt
prompt: choice.prompt,
config: choice.config ?? {}
};
});
}
Expand Down
Loading