@directivegames/genesys.sdk 3.2.2 → 3.2.4

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.
Files changed (82) hide show
  1. package/README.md +60 -60
  2. package/dist/src/core/cli.js +22 -22
  3. package/dist/src/templates/scripts/genesys/genesys-mcp.js +25 -25
  4. package/dist/src/templates/scripts/genesys/mcp/editor-functions.js +4 -4
  5. package/dist/src/templates/src/templates/vehicle/src/ui-hints.js +30 -30
  6. package/package.json +176 -176
  7. package/scripts/post-install.ts +143 -143
  8. package/src/asset-pack/.gitattributes +88 -88
  9. package/src/asset-pack/eslint.config.js +45 -45
  10. package/src/asset-pack/gitignore +11 -11
  11. package/src/asset-pack/scripts/postinstall.ts +81 -81
  12. package/src/asset-pack/tsconfig.json +33 -33
  13. package/src/templates/.cursor/mcp.json +20 -20
  14. package/src/templates/.cursorignore +2 -2
  15. package/src/templates/.gitattributes +88 -88
  16. package/src/templates/.vscode/settings.json +6 -6
  17. package/src/templates/AGENTS.md +86 -86
  18. package/src/templates/README.md +24 -24
  19. package/src/templates/eslint.config.js +45 -45
  20. package/src/templates/gitignore +11 -11
  21. package/src/templates/index.html +34 -34
  22. package/src/templates/pnpm-lock.yaml +3676 -3676
  23. package/src/templates/scripts/genesys/build-project.ts +51 -51
  24. package/src/templates/scripts/genesys/calc-bounding-box.ts +272 -272
  25. package/src/templates/scripts/genesys/common.ts +46 -46
  26. package/src/templates/scripts/genesys/const.ts +9 -9
  27. package/src/templates/scripts/genesys/dev/dump-default-scene.ts +11 -11
  28. package/src/templates/scripts/genesys/dev/generate-manifest.ts +146 -146
  29. package/src/templates/scripts/genesys/dev/launcher.ts +46 -46
  30. package/src/templates/scripts/genesys/dev/storage-provider.ts +229 -229
  31. package/src/templates/scripts/genesys/dev/update-template-scenes.ts +84 -84
  32. package/src/templates/scripts/genesys/doc-server.ts +16 -16
  33. package/src/templates/scripts/genesys/genesys-mcp.ts +526 -526
  34. package/src/templates/scripts/genesys/mcp/doc-tools.ts +86 -86
  35. package/src/templates/scripts/genesys/mcp/editor-functions.ts +151 -151
  36. package/src/templates/scripts/genesys/mcp/editor-tools.ts +73 -73
  37. package/src/templates/scripts/genesys/mcp/get-scene-state.ts +35 -35
  38. package/src/templates/scripts/genesys/mcp/run-subprocess.ts +30 -30
  39. package/src/templates/scripts/genesys/mcp/search-actors.ts +858 -858
  40. package/src/templates/scripts/genesys/mcp/search-assets.ts +380 -380
  41. package/src/templates/scripts/genesys/mcp/utils.ts +281 -281
  42. package/src/templates/scripts/genesys/misc.ts +42 -42
  43. package/src/templates/scripts/genesys/mock.ts +6 -6
  44. package/src/templates/scripts/genesys/place-actors.ts +179 -179
  45. package/src/templates/scripts/genesys/post-install.ts +30 -30
  46. package/src/templates/scripts/genesys/prefab.schema.json +84 -84
  47. package/src/templates/scripts/genesys/remove-engine-comments.ts +134 -134
  48. package/src/templates/scripts/genesys/run-mcp-inspector.bat +4 -4
  49. package/src/templates/scripts/genesys/storageProvider.ts +182 -182
  50. package/src/templates/scripts/genesys/validate-prefabs.ts +138 -138
  51. package/src/templates/src/index.ts +22 -22
  52. package/src/templates/src/templates/firstPerson/assets/default.genesys-scene +165 -165
  53. package/src/templates/src/templates/firstPerson/src/game.ts +39 -39
  54. package/src/templates/src/templates/firstPerson/src/player.ts +63 -63
  55. package/src/templates/src/templates/fps/assets/default.genesys-scene +9459 -9459
  56. package/src/templates/src/templates/fps/src/game.ts +39 -39
  57. package/src/templates/src/templates/fps/src/player.ts +69 -69
  58. package/src/templates/src/templates/fps/src/weapon.ts +54 -54
  59. package/src/templates/src/templates/freeCamera/assets/default.genesys-scene +165 -165
  60. package/src/templates/src/templates/freeCamera/src/game.ts +39 -39
  61. package/src/templates/src/templates/freeCamera/src/player.ts +45 -45
  62. package/src/templates/src/templates/sideScroller/assets/default.genesys-scene +121 -121
  63. package/src/templates/src/templates/sideScroller/src/const.ts +45 -45
  64. package/src/templates/src/templates/sideScroller/src/game.ts +122 -122
  65. package/src/templates/src/templates/sideScroller/src/level-generator.ts +361 -361
  66. package/src/templates/src/templates/sideScroller/src/player.ts +125 -125
  67. package/src/templates/src/templates/thirdPerson/assets/default.genesys-scene +165 -165
  68. package/src/templates/src/templates/thirdPerson/src/game.ts +39 -39
  69. package/src/templates/src/templates/thirdPerson/src/player.ts +61 -61
  70. package/src/templates/src/templates/vehicle/assets/default.genesys-scene +225 -225
  71. package/src/templates/src/templates/vehicle/src/base-vehicle.ts +145 -145
  72. package/src/templates/src/templates/vehicle/src/game.ts +43 -43
  73. package/src/templates/src/templates/vehicle/src/mesh-vehicle.ts +191 -191
  74. package/src/templates/src/templates/vehicle/src/player.ts +109 -109
  75. package/src/templates/src/templates/vehicle/src/primitive-vehicle.ts +266 -266
  76. package/src/templates/src/templates/vehicle/src/ui-hints.ts +101 -101
  77. package/src/templates/src/templates/vr-game/assets/default.genesys-scene +246 -246
  78. package/src/templates/src/templates/vr-game/src/auto-imports.ts +1 -1
  79. package/src/templates/src/templates/vr-game/src/game.ts +66 -66
  80. package/src/templates/src/templates/vr-game/src/sample-vr-actor.ts +26 -26
  81. package/src/templates/tsconfig.json +34 -34
  82. package/src/templates/vite.config.ts +52 -52
package/README.md CHANGED
@@ -1,60 +1,60 @@
1
- [![CI](https://github.com/directivegames/genesys.sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/directivegames/genesys.sdk/actions/workflows/ci.yml)
2
- [![Publish](https://github.com/directivegames/genesys.sdk/actions/workflows/publish.yml/badge.svg)](https://github.com/directivegames/genesys.sdk/actions/workflows/publish.yml)
3
-
4
- ![Version](https://gist.githubusercontent.com/haowang1013/d024d16ac57a7bfd7038ae32bf239d07/raw/genesys.sdk.svg)
5
-
6
- # User Workflow
7
- - Download and install the latest version from the [Release](https://github.com/directivegames/genesys.sdk/releases) page.
8
- - Create an empty folder on your disk to store the project.
9
- - Open the empty folder created earlier in this app, select a template and create a project, then start the file server.
10
- - Launch the web editor and cursor to develop.
11
-
12
- # Developer
13
-
14
- ## Run Locally
15
- - `pnpm install`
16
- - `pnpm dev`: to run the dev version, connects to localhost:3000 if available
17
- - run `pnpm dev` on [genesys.ai](https://github.com/directivegames/genesys.ai) to start localhost:3000
18
- - if localhost:3000 is started after the sdk, refresh the sdk with Ctrl+R/Ctrl+Shift+R
19
- - `pnpm dev:watch`: same as `pnpm dev` but with auto-restart on file changes using nodemon
20
- - `pnpm dev:hosted`: connects to the hosted genesys.ai site.
21
- - `pnpm dist`: to build the app locally. Installer will be generated in the `dist` folder that you can use to install the app directly on your machine.
22
-
23
- ## CLI Tools
24
- The SDK includes command-line tools for creating and building projects without launching the GUI.
25
-
26
- ### Available Commands
27
- ```bash
28
- # Show help and available commands
29
- genesys-sdk --help
30
-
31
- # Show version
32
- genesys-sdk --version
33
-
34
- # Create a new project
35
- genesys-sdk new -n <project-name> -t <template-id> [-p <parent-path>]
36
-
37
- # Build an existing project (with TypeScript type checking)
38
- genesys-sdk build [-p <project-path>]
39
-
40
- # Build without type checking (faster)
41
- genesys-sdk build [-p <project-path>] --no-tsc
42
-
43
- # List available project templates
44
- genesys-sdk list-templates
45
- ```
46
-
47
- ## New Project Template
48
- - The logic to setup a new project is in [new-project.ts](src/backend/tools//new-project.ts), it contains a few steps:
49
- - Generate `game.ts` and `default.genesys-scene` based on the selected template.
50
- - Generate `package.json` and `{project}.code-workspace` from code.
51
- - Copy the files from `assets/new-project` to the project folder.
52
- - Run `pnpm install` and `pnpm build` in the project folder.
53
-
54
- ## Add Electron API
55
- Electron API is for exposing code that relies on the nodejs environment to the frontend environment.
56
-
57
- To add a new one, following this:
58
- - Define the API in [api.ts](src/api.ts)
59
- - Add the API wrapper in [preload.ts](src/preload.ts)
60
- - Implement the API in [handler.ts](src/backend/handler.ts)
1
+ [![CI](https://github.com/directivegames/genesys.sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/directivegames/genesys.sdk/actions/workflows/ci.yml)
2
+ [![Publish](https://github.com/directivegames/genesys.sdk/actions/workflows/publish.yml/badge.svg)](https://github.com/directivegames/genesys.sdk/actions/workflows/publish.yml)
3
+
4
+ ![Version](https://gist.githubusercontent.com/haowang1013/d024d16ac57a7bfd7038ae32bf239d07/raw/genesys.sdk.svg)
5
+
6
+ # User Workflow
7
+ - Download and install the latest version from the [Release](https://github.com/directivegames/genesys.sdk/releases) page.
8
+ - Create an empty folder on your disk to store the project.
9
+ - Open the empty folder created earlier in this app, select a template and create a project, then start the file server.
10
+ - Launch the web editor and cursor to develop.
11
+
12
+ # Developer
13
+
14
+ ## Run Locally
15
+ - `pnpm install`
16
+ - `pnpm dev`: to run the dev version, connects to localhost:3000 if available
17
+ - run `pnpm dev` on [genesys.ai](https://github.com/directivegames/genesys.ai) to start localhost:3000
18
+ - if localhost:3000 is started after the sdk, refresh the sdk with Ctrl+R/Ctrl+Shift+R
19
+ - `pnpm dev:watch`: same as `pnpm dev` but with auto-restart on file changes using nodemon
20
+ - `pnpm dev:hosted`: connects to the hosted genesys.ai site.
21
+ - `pnpm dist`: to build the app locally. Installer will be generated in the `dist` folder that you can use to install the app directly on your machine.
22
+
23
+ ## CLI Tools
24
+ The SDK includes command-line tools for creating and building projects without launching the GUI.
25
+
26
+ ### Available Commands
27
+ ```bash
28
+ # Show help and available commands
29
+ genesys-sdk --help
30
+
31
+ # Show version
32
+ genesys-sdk --version
33
+
34
+ # Create a new project
35
+ genesys-sdk new -n <project-name> -t <template-id> [-p <parent-path>]
36
+
37
+ # Build an existing project (with TypeScript type checking)
38
+ genesys-sdk build [-p <project-path>]
39
+
40
+ # Build without type checking (faster)
41
+ genesys-sdk build [-p <project-path>] --no-tsc
42
+
43
+ # List available project templates
44
+ genesys-sdk list-templates
45
+ ```
46
+
47
+ ## New Project Template
48
+ - The logic to setup a new project is in [new-project.ts](src/backend/tools//new-project.ts), it contains a few steps:
49
+ - Generate `game.ts` and `default.genesys-scene` based on the selected template.
50
+ - Generate `package.json` and `{project}.code-workspace` from code.
51
+ - Copy the files from `assets/new-project` to the project folder.
52
+ - Run `pnpm install` and `pnpm build` in the project folder.
53
+
54
+ ## Add Electron API
55
+ Electron API is for exposing code that relies on the nodejs environment to the frontend environment.
56
+
57
+ To add a new one, following this:
58
+ - Define the API in [api.ts](src/api.ts)
59
+ - Add the API wrapper in [preload.ts](src/preload.ts)
60
+ - Implement the API in [handler.ts](src/backend/handler.ts)
@@ -276,28 +276,28 @@ async function main() {
276
276
  printTemplates();
277
277
  });
278
278
  // Add examples to help
279
- program.addHelpText('after', `
280
- Examples:
281
- # Create a new FPS game project in current directory
282
- $ genesys-sdk new -n my-fps-game -t fps
283
-
284
- # Create a new FPS game project in a specific directory
285
- $ genesys-sdk new -p ./projects -n my-fps-game -t fps
286
-
287
- # Build current directory project with type checking
288
- $ genesys-sdk build
289
-
290
- # Build a specific project with type checking
291
- $ genesys-sdk build -p ./projects/my-game
292
-
293
- # Create a new asset pack in current directory
294
- $ genesys-sdk new-asset-pack -n my-asset-pack
295
-
296
- # Create a new asset pack in a specific directory
297
- $ genesys-sdk new-asset-pack -p ./packs -n my-asset-pack
298
-
299
- # List all available templates
300
- $ genesys-sdk list-templates
279
+ program.addHelpText('after', `
280
+ Examples:
281
+ # Create a new FPS game project in current directory
282
+ $ genesys-sdk new -n my-fps-game -t fps
283
+
284
+ # Create a new FPS game project in a specific directory
285
+ $ genesys-sdk new -p ./projects -n my-fps-game -t fps
286
+
287
+ # Build current directory project with type checking
288
+ $ genesys-sdk build
289
+
290
+ # Build a specific project with type checking
291
+ $ genesys-sdk build -p ./projects/my-game
292
+
293
+ # Create a new asset pack in current directory
294
+ $ genesys-sdk new-asset-pack -n my-asset-pack
295
+
296
+ # Create a new asset pack in a specific directory
297
+ $ genesys-sdk new-asset-pack -p ./packs -n my-asset-pack
298
+
299
+ # List all available templates
300
+ $ genesys-sdk list-templates
301
301
  `);
302
302
  // Parse arguments
303
303
  await program.parseAsync(process.argv);
@@ -27,9 +27,9 @@ const server = new McpServer({
27
27
  logging: {} // to support sendLoggingMessage
28
28
  }
29
29
  });
30
- const classRule = `
31
- The class name should start with '${ENGINE.Prefix.GAME}' or '${ENGINE.Prefix.ENGINE}' to indicate if it is a class from game project or engine.
32
- If it starts with neither, it will be matched against all registered classes. If none is found, an error will be returned.
30
+ const classRule = `
31
+ The class name should start with '${ENGINE.Prefix.GAME}' or '${ENGINE.Prefix.ENGINE}' to indicate if it is a class from game project or engine.
32
+ If it starts with neither, it will be matched against all registered classes. If none is found, an error will be returned.
33
33
  `;
34
34
  function requestEditorReload(editorId) {
35
35
  const ws = getClientWebSocket(editorId);
@@ -126,12 +126,12 @@ server.registerTool('addGltf', {
126
126
  inputSchema: {
127
127
  editorId: EditorIdSchema,
128
128
  gltfs: z.array(z.object({
129
- path: z.string().describe(`Path or URL to the GLTF model to add.
130
- Unless explicitly told by user, otherwise it should always be one of these
131
- - fetched from the result of searchAssets tool
132
- - copied from an existing path of an existing placed GLTF actor.
133
- - use a verified URL to a GLTF model
134
- Never make up a relative path to a file, because that won't be resolved by the editor.
129
+ path: z.string().describe(`Path or URL to the GLTF model to add.
130
+ Unless explicitly told by user, otherwise it should always be one of these
131
+ - fetched from the result of searchAssets tool
132
+ - copied from an existing path of an existing placed GLTF actor.
133
+ - use a verified URL to a GLTF model
134
+ Never make up a relative path to a file, because that won't be resolved by the editor.
135
135
  `),
136
136
  transform: TransformSchema.optional().describe('Transform of the GLTF model'),
137
137
  info: ActorInfoSchema.describe('Information about the GLTF model actor.'),
@@ -208,11 +208,11 @@ server.registerTool('placePrefab', {
208
208
  };
209
209
  });
210
210
  server.registerTool('searchAssets', {
211
- description: `Search for assets in the project and engine directories.
212
- When the user requests to place something in the scene, unless it is clearly a primitive, always try calling this first to see if any assets are fitting the request.
213
- Before using the assets found, **always** call the \`getAssetsMetadata\` tool to get the metadata about the assets, unless the given metadata is already fetched.
214
- Here is the list of assets types, including:
215
- ${Object.entries(assetDescriptions).map(([type, description]) => `- ${type}: ${description}`).join('\n')}
211
+ description: `Search for assets in the project and engine directories.
212
+ When the user requests to place something in the scene, unless it is clearly a primitive, always try calling this first to see if any assets are fitting the request.
213
+ Before using the assets found, **always** call the \`getAssetsMetadata\` tool to get the metadata about the assets, unless the given metadata is already fetched.
214
+ Here is the list of assets types, including:
215
+ ${Object.entries(assetDescriptions).map(([type, description]) => `- ${type}: ${description}`).join('\n')}
216
216
  `,
217
217
  inputSchema: {
218
218
  acceptedTypes: z.array(z.enum(Object.values(AssetType))).describe('List of types of assets to search for. An empty array means all types.'),
@@ -261,10 +261,10 @@ server.registerTool('placeJsClassActor', {
261
261
  actors: z.array(z.object({
262
262
  className: z.string().describe(`The name of the JavaScript class to place. ${classRule}`),
263
263
  constructorParams: z.array(z.record(z.any())).optional()
264
- .describe(`Optional constructor parameters to pass to the actor class.
265
- Use the getAssetsMetadata tool to discover what parameters each actor class requires,
266
- and the parameters should be an array that corresponds to the "jsClassConstructorParams" from the result of the getAssetsMetadata tool.
267
- If the actor class has no constructor parameters, the array should be empty.
264
+ .describe(`Optional constructor parameters to pass to the actor class.
265
+ Use the getAssetsMetadata tool to discover what parameters each actor class requires,
266
+ and the parameters should be an array that corresponds to the "jsClassConstructorParams" from the result of the getAssetsMetadata tool.
267
+ If the actor class has no constructor parameters, the array should be empty.
268
268
  `),
269
269
  info: ActorInfoSchema.describe('Information about the actor.'),
270
270
  }).strict().describe('A JavaScript class actor to place.')).describe('A list of JavaScript class actors to place. Each actor can have a transform and constructor parameters.'),
@@ -290,8 +290,8 @@ server.registerTool('generateTemplateCode', {
290
290
  description: 'Generate template code for a class of actor or component. When the user asks to create a new actor or component, always try calling this first to see if any template code is available, and iterate on the code it generates until it is correct.',
291
291
  inputSchema: {
292
292
  className: z.string().describe('The name of the class to generate template code for.'),
293
- filePath: z.string().describe(`The path to the file to generate the template code for.
294
- If it is a relative path, it will be relative to the 'src/' directory, so don't include the extra 'src/' in the path.
293
+ filePath: z.string().describe(`The path to the file to generate the template code for.
294
+ If it is a relative path, it will be relative to the 'src/' directory, so don't include the extra 'src/' in the path.
295
295
  The file name should be lower camel case, such as 'myActor.ts', or 'fancyMovementComponent.ts'.`),
296
296
  baseClassName: z.string().describe(`The name of the base class to generate the template code for. ${classRule}`),
297
297
  },
@@ -324,11 +324,11 @@ The file name should be lower camel case, such as 'myActor.ts', or 'fancyMovemen
324
324
  };
325
325
  });
326
326
  server.registerTool('getSceneState', {
327
- description: `
328
- Get the current state of the scene to understand the state of the current scene and its actors.
329
- **DO NOT** try reading the scene file directly unless explicitly asked by the user.
330
- The first time calling this tool, always call without specifiedActors and getDetailedComponentsInfo to have an overview of the scene.
331
- And then call this tool with \`specifiedActors\`, and set \`getDetailedComponentsInfo\` to true, to get more detailed information about specific actors, including their bounding boxes info, etc.
327
+ description: `
328
+ Get the current state of the scene to understand the state of the current scene and its actors.
329
+ **DO NOT** try reading the scene file directly unless explicitly asked by the user.
330
+ The first time calling this tool, always call without specifiedActors and getDetailedComponentsInfo to have an overview of the scene.
331
+ And then call this tool with \`specifiedActors\`, and set \`getDetailedComponentsInfo\` to true, to get more detailed information about specific actors, including their bounding boxes info, etc.
332
332
  `,
333
333
  inputSchema: {
334
334
  editorId: EditorIdSchema,
@@ -66,10 +66,10 @@ function shutdown() {
66
66
  });
67
67
  clients.clear();
68
68
  }
69
- export const EditorIdSchema = z.string().optional().describe(`
70
- ID of the editor that should respond.
71
- If not provided, will proceed if there is only one editor connected, will return error if there are multiple editors connected.
72
- Can always try without providing this, and only ask the user to provide it if there are multiple editors connected.
69
+ export const EditorIdSchema = z.string().optional().describe(`
70
+ ID of the editor that should respond.
71
+ If not provided, will proceed if there is only one editor connected, will return error if there are multiple editors connected.
72
+ Can always try without providing this, and only ask the user to provide it if there are multiple editors connected.
73
73
  `);
74
74
  export function getClientWebSocket(editorId) {
75
75
  if (clients.size === 0) {
@@ -44,36 +44,36 @@ let UIHints = class UIHints extends ENGINE.Actor {
44
44
  }
45
45
  });
46
46
  // Set the HTML content
47
- this.uiElement.setHTML(`
48
- <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
49
- <div style="font-weight: bold; color: #4CAF50; font-size: 18px;">
50
- 🎮 Vehicle Game Controls
51
- </div>
52
- <button id="closeHints" style="
53
- background: #f44336;
54
- color: white;
55
- border: none;
56
- border-radius: 50%;
57
- width: 30px;
58
- height: 30px;
59
- cursor: pointer;
60
- font-size: 16px;
61
- font-weight: bold;
62
- ">×</button>
63
- </div>
64
- <div style="margin-bottom: 12px;">
65
- <span style="color: #FFA726; font-weight: bold;">Movement:</span>
66
- <br>• WASD or Arrow Keys - Move/Drive
67
- <br>• Mouse - Camera control
68
- </div>
69
- <div style="margin-bottom: 12px;">
70
- <span style="color: #FFA726; font-weight: bold;">Vehicle Interaction:</span>
71
- <br>• E - Enter/Exit vehicle (when nearby)
72
- <br>• F - Flip vehicle (when inside)
73
- </div>
74
- <div style="font-size: 14px; color: #CCCCCC; margin-top: 15px; text-align: center; font-style: italic;">
75
- 💡 Walk near a vehicle and press E to enter
76
- </div>
47
+ this.uiElement.setHTML(`
48
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
49
+ <div style="font-weight: bold; color: #4CAF50; font-size: 18px;">
50
+ 🎮 Vehicle Game Controls
51
+ </div>
52
+ <button id="closeHints" style="
53
+ background: #f44336;
54
+ color: white;
55
+ border: none;
56
+ border-radius: 50%;
57
+ width: 30px;
58
+ height: 30px;
59
+ cursor: pointer;
60
+ font-size: 16px;
61
+ font-weight: bold;
62
+ ">×</button>
63
+ </div>
64
+ <div style="margin-bottom: 12px;">
65
+ <span style="color: #FFA726; font-weight: bold;">Movement:</span>
66
+ <br>• WASD or Arrow Keys - Move/Drive
67
+ <br>• Mouse - Camera control
68
+ </div>
69
+ <div style="margin-bottom: 12px;">
70
+ <span style="color: #FFA726; font-weight: bold;">Vehicle Interaction:</span>
71
+ <br>• E - Enter/Exit vehicle (when nearby)
72
+ <br>• F - Flip vehicle (when inside)
73
+ </div>
74
+ <div style="font-size: 14px; color: #CCCCCC; margin-top: 15px; text-align: center; font-style: italic;">
75
+ 💡 Walk near a vehicle and press E to enter
76
+ </div>
77
77
  `);
78
78
  // Add close button functionality
79
79
  const closeButton = this.uiElement.querySelector('#closeHints');