@vylos/cli 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/commands/create.ts +8 -1
- package/templates/.gitattributes +8 -0
- package/templates/characters.ts +9 -0
- package/templates/env.d.ts +6 -0
- package/templates/global/actions/wait.ts +12 -0
- package/templates/global/events/intro.ts +27 -0
- package/templates/index.html +13 -0
- package/templates/locations/home/location.ts +9 -0
- package/templates/main.ts +119 -0
- package/templates/package.json +22 -0
- package/templates/player.ts +16 -0
- package/templates/state.ts +39 -0
- package/templates/style.css +2 -0
- package/templates/tsconfig.json +20 -0
- package/templates/vylos.config.ts +11 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vylos/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/DevOpsBenjamin/Vylos"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"tailwindcss": "^4.0.0",
|
|
18
18
|
"@tailwindcss/vite": "^4.0.0",
|
|
19
19
|
"tsx": "^4.19.2",
|
|
20
|
-
"@vylos/core": "0.
|
|
20
|
+
"@vylos/core": "0.4.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"typescript": "^5.7.2"
|
package/src/commands/create.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolve, dirname } from 'path';
|
|
2
|
-
import { existsSync, mkdirSync, cpSync } from 'fs';
|
|
2
|
+
import { existsSync, mkdirSync, cpSync, readFileSync, writeFileSync } from 'fs';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
|
|
5
5
|
export async function create(name: string, targetDir?: string) {
|
|
@@ -24,6 +24,13 @@ export async function create(name: string, targetDir?: string) {
|
|
|
24
24
|
mkdirSync(dest, { recursive: true });
|
|
25
25
|
cpSync(templateDir, dest, { recursive: true });
|
|
26
26
|
|
|
27
|
+
// Replace project name in package.json
|
|
28
|
+
const pkgPath = resolve(dest, 'package.json');
|
|
29
|
+
if (existsSync(pkgPath)) {
|
|
30
|
+
const pkg = readFileSync(pkgPath, 'utf-8');
|
|
31
|
+
writeFileSync(pkgPath, pkg.replace('"my-vylos-game"', `"${name}"`));
|
|
32
|
+
}
|
|
33
|
+
|
|
27
34
|
console.log(` Project created at: ${dest}`);
|
|
28
35
|
console.log(`\n Next steps:`);
|
|
29
36
|
console.log(` cd ${name}`);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
*.png filter=lfs diff=lfs merge=lfs -text
|
|
2
|
+
*.jpg filter=lfs diff=lfs merge=lfs -text
|
|
3
|
+
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
|
4
|
+
*.webp filter=lfs diff=lfs merge=lfs -text
|
|
5
|
+
*.mp4 filter=lfs diff=lfs merge=lfs -text
|
|
6
|
+
*.webm filter=lfs diff=lfs merge=lfs -text
|
|
7
|
+
*.ogg filter=lfs diff=lfs merge=lfs -text
|
|
8
|
+
*.mp3 filter=lfs diff=lfs merge=lfs -text
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { VylosCharacter } from '@vylos/core';
|
|
2
|
+
|
|
3
|
+
/** Extend VylosCharacter with your project-specific fields */
|
|
4
|
+
export interface Character extends VylosCharacter {
|
|
5
|
+
// portrait?: string;
|
|
6
|
+
// role?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const narrator: Character = { id: 'narrator', name: 'Narrator' };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { VylosAction } from '@vylos/core';
|
|
2
|
+
import type { GameState } from '../../state';
|
|
3
|
+
|
|
4
|
+
const wait: VylosAction<GameState> = {
|
|
5
|
+
id: 'wait',
|
|
6
|
+
label: 'Wait 1 Hour',
|
|
7
|
+
execute(state: GameState) {
|
|
8
|
+
state.gameTime += 1;
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default wait;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { VylosEvent, VylosAPI } from '@vylos/core';
|
|
2
|
+
import type { GameState } from '../../state';
|
|
3
|
+
import { narrator } from '../../characters';
|
|
4
|
+
|
|
5
|
+
const intro: VylosEvent<GameState> = {
|
|
6
|
+
id: 'intro',
|
|
7
|
+
conditions: (state) => !state.flags['intro_done'],
|
|
8
|
+
async execute(engine: VylosAPI, state: GameState) {
|
|
9
|
+
await engine.say('Welcome to your Vylos game!', { from: narrator });
|
|
10
|
+
await engine.say('This is a starter template. Edit the events to build your story.');
|
|
11
|
+
|
|
12
|
+
const pick = await engine.choice([
|
|
13
|
+
{ text: 'Sounds good!', value: 'ok' },
|
|
14
|
+
{ text: 'Tell me more', value: 'more' },
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
if (pick === 'more') {
|
|
18
|
+
await engine.say('Events pause at each say() and choice() call.');
|
|
19
|
+
await engine.say('Add locations, backgrounds, and characters to bring your story to life.');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
await engine.say('Your adventure begins here. Good luck!');
|
|
23
|
+
state.flags['intro_done'] = true;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default intro;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
6
|
+
<title>My Vylos Game</title>
|
|
7
|
+
<style>html, body, #app { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; }</style>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="app"></div>
|
|
11
|
+
<script type="module" src="./main.ts"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import './style.css';
|
|
3
|
+
import { createApp, watch } from 'vue';
|
|
4
|
+
import { createPinia } from 'pinia';
|
|
5
|
+
import {
|
|
6
|
+
GameShell,
|
|
7
|
+
createEngine,
|
|
8
|
+
useEngineStateStore,
|
|
9
|
+
ENGINE_INJECT_KEY,
|
|
10
|
+
CONFIG_INJECT_KEY,
|
|
11
|
+
EnginePhase,
|
|
12
|
+
LocationManager,
|
|
13
|
+
ActionManager,
|
|
14
|
+
type EventRunnerCallbacks,
|
|
15
|
+
type TextEntry,
|
|
16
|
+
type VylosCharacter,
|
|
17
|
+
} from '@vylos/core';
|
|
18
|
+
import config from './vylos.config';
|
|
19
|
+
import { useGameStore } from './state';
|
|
20
|
+
|
|
21
|
+
// Locations
|
|
22
|
+
import home from './locations/home/location';
|
|
23
|
+
|
|
24
|
+
// Events
|
|
25
|
+
import intro from './global/events/intro';
|
|
26
|
+
|
|
27
|
+
// Actions
|
|
28
|
+
import wait from './global/actions/wait';
|
|
29
|
+
|
|
30
|
+
const app = createApp(GameShell);
|
|
31
|
+
const pinia = createPinia();
|
|
32
|
+
app.use(pinia);
|
|
33
|
+
|
|
34
|
+
const engineState = useEngineStateStore(pinia);
|
|
35
|
+
const gameState = useGameStore(pinia);
|
|
36
|
+
|
|
37
|
+
// Location manager
|
|
38
|
+
const locationManager = new LocationManager();
|
|
39
|
+
locationManager.registerAll([home]);
|
|
40
|
+
|
|
41
|
+
// Action manager
|
|
42
|
+
const actionManager = new ActionManager();
|
|
43
|
+
actionManager.registerAll([wait]);
|
|
44
|
+
|
|
45
|
+
const callbacks: EventRunnerCallbacks = {
|
|
46
|
+
onSay(text: string, speaker: VylosCharacter | null) {
|
|
47
|
+
engineState.setDialogue({ text, speaker, isNarration: !speaker });
|
|
48
|
+
},
|
|
49
|
+
onChoice(options) {
|
|
50
|
+
engineState.setChoices({ prompt: null, options });
|
|
51
|
+
},
|
|
52
|
+
onSetBackground(path) { engineState.setBackground(path); },
|
|
53
|
+
onSetForeground(path) { engineState.setForeground(path); },
|
|
54
|
+
onShowOverlay() {},
|
|
55
|
+
onHideOverlay() {},
|
|
56
|
+
onSetLocation(id) {
|
|
57
|
+
gameState.state.locationId = id;
|
|
58
|
+
engineState.setLocation(id);
|
|
59
|
+
const bg = locationManager.resolveBackground(id, gameState.state.gameTime);
|
|
60
|
+
if (bg) engineState.setBackground(bg);
|
|
61
|
+
},
|
|
62
|
+
onClear() {
|
|
63
|
+
engineState.setDialogue(null);
|
|
64
|
+
engineState.setChoices(null);
|
|
65
|
+
},
|
|
66
|
+
resolveText(entry: string | TextEntry) {
|
|
67
|
+
return typeof entry === 'string' ? entry : entry['en'] ?? Object.values(entry)[0] ?? '';
|
|
68
|
+
},
|
|
69
|
+
getState() { return gameState.state; },
|
|
70
|
+
setState(s) { gameState.setState(s); },
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const engine = createEngine({ callbacks, projectId: config.id });
|
|
74
|
+
app.provide(ENGINE_INJECT_KEY, engine);
|
|
75
|
+
app.provide(CONFIG_INJECT_KEY, config);
|
|
76
|
+
app.mount('#app');
|
|
77
|
+
|
|
78
|
+
engineState.setPhase(EnginePhase.MainMenu);
|
|
79
|
+
|
|
80
|
+
const stopWatch = watch(() => engineState.phase, (newPhase) => {
|
|
81
|
+
if (newPhase === EnginePhase.Running) {
|
|
82
|
+
stopWatch();
|
|
83
|
+
startGame();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
function startGame() {
|
|
88
|
+
gameState.state.locationId = 'home';
|
|
89
|
+
gameState.state.gameTime = 12;
|
|
90
|
+
engineState.setLocation('home');
|
|
91
|
+
|
|
92
|
+
engine.run([intro], () => gameState.state, {
|
|
93
|
+
onTick(state) {
|
|
94
|
+
const locations = locationManager.getAccessibleFrom(state.locationId, state);
|
|
95
|
+
engineState.setLocations(locations.map(l => ({
|
|
96
|
+
id: l.id,
|
|
97
|
+
name: typeof l.name === 'string' ? l.name : l.name['en'] ?? l.id,
|
|
98
|
+
accessible: true,
|
|
99
|
+
})));
|
|
100
|
+
|
|
101
|
+
const actions = actionManager.getAvailable(state.locationId, state);
|
|
102
|
+
engineState.setActions(actions.map(a => ({
|
|
103
|
+
id: a.id,
|
|
104
|
+
label: typeof a.label === 'string' ? a.label : a.label['en'] ?? a.id,
|
|
105
|
+
locationId: a.locationId ?? '',
|
|
106
|
+
})));
|
|
107
|
+
|
|
108
|
+
const resolveText = (t: string | Record<string, string>) =>
|
|
109
|
+
typeof t === 'string' ? t : t['en'] ?? Object.values(t)[0] ?? '';
|
|
110
|
+
engineState.setDrawableEvents(engine.eventManager.getDrawableEvents(state, resolveText));
|
|
111
|
+
|
|
112
|
+
const bg = locationManager.resolveBackground(state.locationId, state.gameTime);
|
|
113
|
+
if (bg) engineState.setBackground(bg);
|
|
114
|
+
},
|
|
115
|
+
onAction(actionId, state) {
|
|
116
|
+
actionManager.execute(actionId, state);
|
|
117
|
+
},
|
|
118
|
+
}).catch(console.error);
|
|
119
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-vylos-game",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vylos dev",
|
|
8
|
+
"build": "vylos build"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@vylos/core": "^0.4.0",
|
|
12
|
+
"@vylos/cli": "^0.4.0",
|
|
13
|
+
"vue": "^3.5.13",
|
|
14
|
+
"pinia": "^2.3.0",
|
|
15
|
+
"reflect-metadata": "^0.2.2",
|
|
16
|
+
"tailwindcss": "^4.0.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"typescript": "^5.7.2",
|
|
20
|
+
"vite": "^6.0.7"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Character } from './characters';
|
|
2
|
+
|
|
3
|
+
/** Extend Character with your player-specific fields */
|
|
4
|
+
export interface Player extends Character {
|
|
5
|
+
// health: number;
|
|
6
|
+
// gold: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function createDefaultPlayer(): Player {
|
|
10
|
+
return {
|
|
11
|
+
id: 'player',
|
|
12
|
+
name: 'Player',
|
|
13
|
+
// health: 100,
|
|
14
|
+
// gold: 0,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { defineStore } from 'pinia';
|
|
2
|
+
import { ref } from 'vue';
|
|
3
|
+
import type { VylosGameState } from '@vylos/core';
|
|
4
|
+
import { deepMerge } from '@vylos/core';
|
|
5
|
+
import { type Player, createDefaultPlayer } from './player';
|
|
6
|
+
|
|
7
|
+
/** Your game state — extends VylosGameState with your own fields */
|
|
8
|
+
export interface GameState extends VylosGameState {
|
|
9
|
+
player: Player;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function createDefaultState(): GameState {
|
|
13
|
+
return {
|
|
14
|
+
locationId: '',
|
|
15
|
+
gameTime: 8,
|
|
16
|
+
flags: {},
|
|
17
|
+
counters: {},
|
|
18
|
+
player: createDefaultPlayer(),
|
|
19
|
+
inventories: {},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const useGameStore = defineStore('gameState', () => {
|
|
24
|
+
const state = ref<GameState>(createDefaultState());
|
|
25
|
+
|
|
26
|
+
function getState(): GameState {
|
|
27
|
+
return state.value;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function setState(newState: Partial<GameState>) {
|
|
31
|
+
state.value = deepMerge(createDefaultState(), newState) as GameState;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function $reset() {
|
|
35
|
+
state.value = createDefaultState();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return { state, getState, setState, $reset };
|
|
39
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"jsx": "preserve",
|
|
14
|
+
"experimentalDecorators": true,
|
|
15
|
+
"emitDecoratorMetadata": true,
|
|
16
|
+
"baseUrl": "."
|
|
17
|
+
},
|
|
18
|
+
"include": ["./**/*.ts", "./**/*.vue", "./env.d.ts"],
|
|
19
|
+
"exclude": ["node_modules", "dist"]
|
|
20
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { VylosConfig } from '@vylos/core';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
name: 'My Vylos Game',
|
|
5
|
+
id: 'my-game',
|
|
6
|
+
version: '0.1.0',
|
|
7
|
+
languages: ['en'],
|
|
8
|
+
defaultLanguage: 'en',
|
|
9
|
+
defaultLocation: 'home',
|
|
10
|
+
resolution: { width: 1920, height: 1080 },
|
|
11
|
+
} satisfies VylosConfig;
|