cluaupp 0.1.2 → 0.1.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 (64) hide show
  1. package/CHANGELOG.md +20 -1
  2. package/README.md +5 -8
  3. package/docs/README.md +13 -10
  4. package/docs/architecture.md +18 -11
  5. package/docs/cli.md +2 -2
  6. package/docs/config.md +1 -1
  7. package/docs/cpp-advanced.md +10 -6
  8. package/docs/cpp-organization.md +3 -1
  9. package/docs/cpp-safety.md +11 -7
  10. package/docs/cpp-types.md +1 -1
  11. package/docs/examples/_category_.json +5 -0
  12. package/docs/examples/combat.md +239 -0
  13. package/docs/examples/data-boot.md +98 -0
  14. package/docs/examples/hud.md +96 -0
  15. package/docs/examples/index.md +43 -0
  16. package/docs/examples/leaderstats.md +140 -0
  17. package/docs/examples/shop.md +122 -0
  18. package/docs/examples/sword.md +108 -0
  19. package/docs/getting-started.md +12 -6
  20. package/docs/intellisense.md +31 -0
  21. package/docs/intro.md +2 -2
  22. package/docs/libraries/_category_.json +5 -0
  23. package/docs/libraries/dataservice.md +104 -0
  24. package/docs/libraries/index.md +22 -0
  25. package/docs/libraries/janitor.md +65 -0
  26. package/docs/libraries/more.md +79 -0
  27. package/docs/libraries/net.md +35 -0
  28. package/docs/libraries/promise.md +27 -0
  29. package/docs/oop/_category_.json +5 -0
  30. package/docs/oop/file-tags.md +49 -0
  31. package/docs/oop/index.md +22 -0
  32. package/docs/oop/modules.md +86 -0
  33. package/docs/oop/services.md +83 -0
  34. package/docs/print-cout.md +91 -0
  35. package/docs/syntax.md +59 -5
  36. package/editors/vscode/extension.js +146 -0
  37. package/editors/vscode/package.json +25 -0
  38. package/include/cluaupp/libs/janitor.hpp +7 -3
  39. package/include/cluaupp/roblox.hpp +19 -0
  40. package/package.json +66 -65
  41. package/runtime/Janitor/init.luau +4 -34
  42. package/src/architecture.js +108 -48
  43. package/src/cli.js +85 -10
  44. package/src/client/init.client.cpp +5 -0
  45. package/src/compile.js +20 -4
  46. package/src/editor-install.js +218 -0
  47. package/src/emit.js +320 -8
  48. package/src/intellisense.js +1368 -0
  49. package/src/layout.js +129 -0
  50. package/src/lex.js +17 -9
  51. package/src/libs.js +95 -16
  52. package/src/lsp.js +227 -0
  53. package/src/parse.js +187 -6
  54. package/src/preprocess.js +71 -3
  55. package/src/server/leaderstats.server.cpp +28 -0
  56. package/src/shared/config.cpp +5 -0
  57. package/src/shared/config.h +3 -0
  58. package/src/understand.js +64 -6
  59. package/templates/game/.clangd +11 -0
  60. package/templates/game/.vscode/c_cpp_properties.json +6 -2
  61. package/templates/game/.vscode/extensions.json +6 -0
  62. package/templates/game/.vscode/settings.json +16 -2
  63. package/templates/game/compile_flags.txt +2 -0
  64. package/docs/libraries.md +0 -80
package/CHANGELOG.md CHANGED
@@ -1,10 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.4
4
+
5
+ - Quoted `#include "Header.h"` of project files becomes `const Header = require(...)`. Header-only structs emit a constructor (`TemplateData()`); const headers bind `PLAYER_DATA_VERSION` from the module. Boot scripts require services (`LeaderstatsServer:init()`).
6
+ - Keep `const` for injected `require` / `GetService` and `const function` for C++ functions (not `local`).
7
+
8
+ ## 0.1.3
9
+
10
+ - Architecture: generated services keep a fixed declaration order (requires, types, constants, variables, functions, cleanup, return) without section banners. Domain `Stop` runs `janitor:Cleanup()`.
11
+ - IntelliSense: `cluaupp init` / `cluaupp intellisense` install Microsoft `ms-vscode.cpptools` (VSIX on Cursor, where the marketplace omits it), write `.vscode/c_cpp_properties.json` (LLVM clang++, C++20, `include/` + `src/`, `compile_commands.json`), and disable clangd so the two engines do not fight. `build`/`watch` only refresh `compile_commands.json`.
12
+ - `Class::method` on game types becomes `Class:method()` (`self`). Datatype/library statics stay dotted (`CFrame.lookAt`, `Color3.fromRGB`, `FormatNumber.Abbreviate`).
13
+ - `switch` / `case` / `default` / `break` compile to a one-shot `repeat` with `if` / `elseif` / `else`. The discriminant is evaluated once; stacked `case` labels share a body; `break` leaves the switch even from inside an `if`.
14
+ - Janitor typed border re-exports `_impl` (`export type Janitor = Impl.Janitor; return Impl`). It no longer invents `Has` or requires Promise for a parallel type that did not match howmanysmall.
15
+ - Emit: injected `require` / `GetService` are `const`. C++ functions emit as `const function` instead of `local function`.
16
+ - `.server.cpp` emits `init.luau` + `init.meta.json` with `RunContext.Server`. `init.server.luau` is Legacy and is pruned.
17
+ - `cout << ... << endl` emits `print(...)`. `cerr <<` emits `warn`. `cout::print` / `cout::warn` / `cout::error` / `cout::ping` map to the Roblox globals.
18
+ - Docs: [print and cout](docs/print-cout.md); [Libraries](docs/libraries/index.md) how-tos (DataService Init, Janitor, Promise, Net); [OOP structure](docs/oop/index.md) (file tags, services, modules); [Examples](docs/examples/index.md) (leaderstats, combat validation, shop, HUD, sword). GitHub Pages Learn tabs: OOP, Libraries, print/cout, Examples — generated into `site/` (`guide/oop.html`, `guide/examples.html`).
19
+
3
20
  ## 0.1.2
4
21
 
5
22
  - Watch does not write `out/` while any file fails to compile (Studio keeps the last good scripts).
6
23
  - Watch/build skip rewriting Luau whose contents did not change (a space in C++ no longer floods Rojo).
7
- - `libs/` is fill-only: missing files are restored, existing files are never overwritten or deleted. Fixes Rojo 7 crashing on `libs/ArrayIndexer` when watch/build raced a live serve.
24
+ - Watch never copies `libs/` or headers. Changing a constant no longer makes Rojo 7 crash on `libs/ArrayIndexer`.
25
+ - `libs/` on `cluaupp build` is fill-only: missing files are restored, existing files are never overwritten or deleted.
26
+ - Filename tags: `.server.cpp` → Script (`init.server.luau`), `.client.cpp` → LocalScript (`init.client.luau`), `.plugin.cpp` → Plugin, `.legacy` / `.legacy.client` / `.legacy.server` → Legacy. Bare `init.luau` is not used for `.server` (Rojo would make a ModuleScript).
8
27
  - Dropped `$optional` from `default.project.json` (Rojo 7.7 failed to deserialize it).
9
28
 
10
29
  ## 0.1.1
package/README.md CHANGED
@@ -8,13 +8,6 @@
8
8
 
9
9
  [Docs](https://kartzrbx.github.io/Cluaupp/) · [Learn](https://kartzrbx.github.io/Cluaupp/learn/) · [API](https://kartzrbx.github.io/Cluaupp/api/classes/)
10
10
 
11
- The cinematic docs site deploys to [Vercel](https://vercel.com) from `site/` (`vercel.json`). Code blocks use **Dark Modern**, **Tokyo Night**, and **Dracula**. Ask Cluaupp (AI Gateway) is the floating chat on the Vercel deploy.
12
-
13
- ```bash
14
- cp .env.example .env.local # set AI_GATEWAY_API_KEY
15
- npx vercel
16
- ```
17
-
18
11
  [![npm version](https://img.shields.io/npm/v/cluaupp.svg)](https://www.npmjs.com/package/cluaupp)
19
12
  [![Node.js](https://img.shields.io/node/v/cluaupp.svg)](https://nodejs.org)
20
13
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
@@ -78,7 +71,7 @@ src/client/*.client.cpp → out/client/*.client.luau LocalScript (or a servic
78
71
  src/shared/config.* → out/shared/Config.luau typed config module
79
72
  ```
80
73
 
81
- A tiny `leaderstats.server.cpp` becomes `Main` / `PlayersManager` / `CacheController`. A combat `.server.cpp` becomes `CombatController` with your `TakeDamage` logic. Filename tags: `.server.cpp`, `.client.cpp`, `.legacy.server.cpp`, `.legacy.client.cpp`, or no tag (ModuleScript). See [Architecture](docs/architecture.md).
74
+ A tiny `leaderstats.server.cpp` becomes `Main` / `PlayersManager` / `CacheController`. A combat `.server.cpp` becomes `CombatController` with your `TakeDamage` logic. Filename tags: `.server.cpp`, `.client.cpp`, `.legacy.server.cpp`, `.legacy.client.cpp`, or no tag (ModuleScript). See [Architecture](docs/architecture.md) and [OOP structure](docs/oop/index.md).
82
75
 
83
76
  ## Documentation
84
77
 
@@ -89,8 +82,12 @@ A tiny `leaderstats.server.cpp` becomes `Main` / `PlayersManager` / `CacheContro
89
82
  | [Learn](https://kartzrbx.github.io/Cluaupp/learn/) | C++ subset, Luau output, safety, architecture |
90
83
  | [CLI](docs/cli.md) | `init`, `build`, `watch`, flags |
91
84
  | [Syntax](docs/syntax.md) | C++ subset → Luau (`local`, `const`, types) |
85
+ | [print and cout](docs/print-cout.md) | `cout <<`, `cout::warn`, `endl` |
92
86
  | [Roblox API](docs/roblox-api.md) | Vector3, CFrame, UDim2, GetService, Instance.new |
93
87
  | [Architecture](docs/architecture.md) | PascalCase services, Types modules, not a 200-line dump |
88
+ | [OOP structure](docs/oop/index.md) | File tags, services, modules, structs |
89
+ | [Libraries](docs/libraries/index.md) | DataService Init, Janitor, Promise, Net |
90
+ | [Examples](docs/examples/index.md) | Leaderstats, combat validation, shop, HUD, sword |
94
91
  | [C++ types](docs/cpp-types.md) | Typing, `const`, safety, organization |
95
92
  | [Comparison](docs/comparison.md) | vs roblox-ts and WASM toolchains |
96
93
  | [Contributing](CONTRIBUTING.md) | Tests, layout, how to ship |
package/docs/README.md CHANGED
@@ -4,16 +4,19 @@
4
4
  2. [Getting started](getting-started.md) — npm, Rojo, Wally
5
5
  3. [CLI](cli.md) — `init`, `build`, `watch`
6
6
  4. [Syntax](syntax.md) — C++ subset, `local`, `const`
7
- 5. [C++ types](cpp-types.md) — primitives, Instances, `nullptr`
8
- 6. [const](cpp-const.md) — immutability
9
- 7. [Safety](cpp-safety.md) — client trust, Janitor, Net, DataService
10
- 8. [Organization](cpp-organization.md) — server / client / shared
11
- 9. [Advanced](cpp-advanced.md) — `::` vs `:`, Wally, performance
12
- 10. [Architecture](architecture.md) — PascalCase services, Types modules, vs roblox-ts dumps
13
- 11. [Libraries](libraries.md) — Janitor, Promise, Net, Module3D, Twinkle, DataServiceV2
14
- 12. [Roblox API](roblox-api.md) — Vector3, CFrame, UDim2, classes
15
- 13. [Config](config.md) — `cluaupp.config.json`
16
- 14. [Comparison](comparison.md) — roblox-ts and WASM
7
+ 5. [print and cout](print-cout.md) — `cout <<`, `cout::warn`, `endl`
8
+ 6. [C++ types](cpp-types.md) — primitives, Instances, `nullptr`
9
+ 7. [const](cpp-const.md) — immutability
10
+ 8. [Safety](cpp-safety.md) — client trust, Janitor, Net, DataService
11
+ 9. [Organization](cpp-organization.md) — server / client / shared
12
+ 10. [Advanced](cpp-advanced.md) — `::` vs `:`, Wally, performance
13
+ 11. [Architecture](architecture.md) — PascalCase services, Types modules, vs roblox-ts dumps
14
+ 12. [OOP structure](oop/index.md) — file tags, services, modules, structs
15
+ 13. [Libraries](libraries/index.md) — DataService Init, Janitor, Promise, Net, more
16
+ 14. [Examples](examples/index.md) — leaderstats, combat validation, shop, HUD, sword
17
+ 15. [Roblox API](roblox-api.md) — Vector3, CFrame, UDim2, classes
18
+ 16. [Config](config.md) — `cluaupp.config.json`
19
+ 17. [Comparison](comparison.md) — roblox-ts and WASM
17
20
 
18
21
  Moonwave site (local): `npx moonwave dev`
19
22
 
@@ -24,13 +24,16 @@ Leaderstats was the example. A combat `.server.cpp` is a different system and ge
24
24
 
25
25
  | Source | Meaning | Output |
26
26
  | --- | --- | --- |
27
- | `combat.server.cpp` | Server **system** | `Combat/` Script: `init.server.luau` + Main / Controllers / Types |
28
- | `hud.client.cpp` | Client **system** | `Hud/` LocalScript folder |
29
- | `boot.legacy.server.cpp` | Server script, no split | `boot.server.luau` (Rojo Script, `init()` still runs) |
30
- | `boot.legacy.client.cpp` | Client script, no split | `boot.client.luau` |
31
- | `damage.cpp` (no tag) | **Module** | `Damage.luau` ModuleScript `return { ... }` — does not auto-run |
27
+ | `combat.server.cpp` | **Script** RunContext Server | `Combat/init.luau` + `init.meta.json` |
28
+ | `hud.client.cpp` | **LocalScript** | `Hud/init.client.luau` |
29
+ | `tools.plugin.cpp` | **Script** RunContext Plugin | `Tools/init.luau` + `init.meta.json` |
30
+ | `boot.legacy.cpp` | Legacy Script | `boot.server.luau` |
31
+ | `boot.legacy.server.cpp` | Legacy Script | `boot.server.luau` |
32
+ | `boot.legacy.client.cpp` | Legacy LocalScript | `boot.client.luau` |
33
+ | `boot.legacy.plugin.cpp` | Plugin | `boot.luau` + Plugin meta |
34
+ | `damage.cpp` (no tag) | **ModuleScript** | `Damage.luau` |
32
35
 
33
- Trivial entry files (`init.client.cpp` that only `print`) stay a single LocalScript. The planner does not invent Managers for a hello-world.
36
+ Trivial entry files (`init.client.cpp` that only `print`) stay a single LocalScript (`init.client.luau`). The planner does not invent Managers for a hello-world.
34
37
 
35
38
  ## How the planner reasons
36
39
 
@@ -63,6 +66,8 @@ Generated files start with that trace:
63
66
 
64
67
  If the C++ creates Coins/Level, CacheController is generated (higher quality than copying `CreateLeaderstats`). Combat logic is **not** rewritten into leaderstats — `ApplyDamage` stays in `CombatController.luau`.
65
68
 
69
+ The domain controller keeps **every** user function. CacheController is generated beside it; it must not delete `SetupPlayerManager` while `init` still calls that name. `GetChangedSignal(Paths.Currencies)` stays in `DataController`.
70
+
66
71
  ## What leaderstats becomes
67
72
 
68
73
  ```
@@ -71,7 +76,8 @@ src/server/leaderstats.server.cpp
71
76
 
72
77
  ```
73
78
  out/server/LeaderStats/
74
- init.server.luau
79
+ init.luau
80
+ init.meta.json
75
81
  Main.luau
76
82
  PlayersManager.luau
77
83
  CacheController.luau
@@ -86,7 +92,8 @@ src/server/combat.server.cpp -- TakeDamage, Humanoid
86
92
 
87
93
  ```
88
94
  out/server/Combat/
89
- init.server.luau
95
+ init.luau
96
+ init.meta.json
90
97
  Main.luau
91
98
  CombatController.luau -- your ApplyDamage / OnHit
92
99
  CombatTypes.luau
@@ -98,13 +105,13 @@ No PlayersManager unless you actually listen to players. No CacheController unle
98
105
 
99
106
  - PascalCase folders and modules; camelCase locals
100
107
  - `--!strict` everywhere
101
- - Public API `Start` / `Stop`
108
+ - Public API `Start` / `Stop`. `Start` is idempotent (`Stop` first). Domain `Stop` is `janitor:Cleanup()` (unbind, janitor stays reusable). `OnClose` / `Destroy` stay for `BindToClose`. `PlayersManager.Stop` disconnects and runs `onLeave`. `CacheController.ClearAll` drops the cache.
102
109
  - Janitor owns connections in the Manager
103
- - Types modules `return {}`
110
+ - Module layout in generated Luau (no section banners): requires, types, constants, variables, functions, cleanup, return.
104
111
  - Transparent `require(script.Parent.X)`
105
112
 
106
113
  ## Config
107
114
 
108
115
  `cluaupp.config.json`: `"architecture": true` (default). `"architecture": false` forces 1:1 dumps (or use `.legacy.server.cpp` / `.legacy.client.cpp` per file).
109
116
 
110
- See [organization](cpp-organization.md) and [comparison](comparison.md).
117
+ How to **write** a service, tags, and module-style OOP: [OOP structure](oop/index.md). Copy-paste systems: [Examples](examples/index.md). Also [organization](cpp-organization.md) and [comparison](comparison.md).
package/docs/cli.md CHANGED
@@ -23,7 +23,7 @@ cluaupp init my-game
23
23
 
24
24
  ## `cluaupp build [folder]`
25
25
 
26
- Transpiles `src/**/*.{cpp,h,hpp}` into `out/`. Script files become PascalCase **service folders** (`LeaderStats/Main.luau`, `PlayersManager.luau`, `CacheController.luau`, `LeaderStatsTypes.luau`) plus `init.server.luau` for Rojo. Shared `const` headers become `Config.luau`.
26
+ Transpiles `src/**/*.{cpp,h,hpp}` into `out/`. Script files become PascalCase **service folders** (`LeaderStats/Main.luau`, `PlayersManager.luau`, `CacheController.luau`, `LeaderStatsTypes.luau`) plus `init.luau` and `init.meta.json` (`RunContext.Server`) for Rojo. Shared `const` headers become `Config.luau`.
27
27
 
28
28
  - `--!strict` on every module
29
29
  - `#include "file.h"` inlines that header into the current file
@@ -41,7 +41,7 @@ cluaupp build ./my-game
41
41
 
42
42
  ## `cluaupp watch [folder]`
43
43
 
44
- Runs a `build` and rebuilds when anything under `src/` changes, including deletes. Parse errors are printed; the watcher stays alive and **does not write `out/`** until the project compiles cleanly (Studio keeps the last good scripts). Rebuilds are debounced and do **not** recopy `libs/`. Deleted `.cpp` files prune their `out/` artifacts on the next successful compile.
44
+ Runs a compile (without copying `libs/`) and rebuilds when anything under `src/` changes, including deletes. Parse errors are printed; the watcher stays alive and **does not write `out/`** until the project compiles cleanly (Studio keeps the last good scripts). A second watcher in the same game exits. Deleted `.cpp` files prune their `out/` artifacts on the next successful compile.
45
45
 
46
46
  ## `cluaupp --version` / `cluaupp -v`
47
47
 
package/docs/config.md CHANGED
@@ -28,4 +28,4 @@ The template ships `default.project.json` mapping:
28
28
  - `out/client` → `StarterPlayer.StarterPlayerScripts.Cluaupp`
29
29
  - `out/shared` → `ReplicatedStorage.Cluaupp`
30
30
 
31
- `*.server.luau` and `*.client.luau` files are classified by Rojo as Script and LocalScript. A folder with `init.server.luau` becomes a Script named after the folder (`LeaderStats`) with ModuleScript children (`Main`, `PlayersManager`, `CacheController`, `LeaderStatsTypes`).
31
+ `*.server.cpp` becomes a folder with `init.luau` + `init.meta.json` (`className` Script, `RunContext` Server). `.client` stays LocalScript via `init.client.luau`. A `.legacy.server.cpp` dump is still `*.server.luau` (Legacy).
@@ -9,7 +9,7 @@ The subset is intentional. What exists is enough for game scripts; what is missi
9
9
 
10
10
  ## Supported
11
11
 
12
- - Functions, prototypes (headers only), `if` / `else` / `while` / range-`for`
12
+ - Functions, prototypes (headers only), `if` / `else` / `while` / range-`for` / `switch` (`case`, `default`, `break`)
13
13
  - `new Class(parent)`, `GetService<T>()`, `::` statics (`CFrame::lookAt`, `Enum::Material::Plastic`)
14
14
  - `->` methods and properties, `.` members, `Connect`
15
15
  - `const`, `auto`, `nullptr`, arithmetic, `&&` `||` `!=`
@@ -18,7 +18,7 @@ The subset is intentional. What exists is enough for game scripts; what is missi
18
18
 
19
19
  ## Not supported (yet)
20
20
 
21
- Full C++: templates besides `GetService<T>`, `class` bodies as emitted types, `std::`, overloading as two runtimes, `switch`, C-style `for`, macros, pointer arithmetic.
21
+ Full C++: templates besides `GetService<T>`, `class` bodies as emitted types, `std::`, overloading as two runtimes, C-style `for`, macros, pointer arithmetic.
22
22
 
23
23
  If you need a custom type, it is usually a **ModuleScript in shared** (a `.cpp` of functions) or a Wally package, not a C++ class the compiler would lower to a metatable.
24
24
 
@@ -45,12 +45,16 @@ CluauppLibs already contains the full Janitor, Fusion, Cmdr, DataServiceV2, …
45
45
  #include <cluaupp/libs/dataservice.hpp>
46
46
 
47
47
  void Grant(Player* player, int amount) {
48
- auto coins = DataService::Get(player, "Currencies.Coins");
49
- DataService::Set(player, "Currencies.Coins", coins + amount);
48
+ Data* data = DataService::Server.WaitFor(player);
49
+ if (data == nullptr) {
50
+ return;
51
+ }
52
+ int coins = data->Get(DataService::Server.Paths.Currencies.Money);
53
+ data->Set(DataService::Server.Paths.Currencies.Money, coins + amount);
50
54
  }
51
55
  ```
52
56
 
53
- Keep path strings in `config.h`. Match the tokens your DataServiceV2 template already uses (`DataService.Paths.Currencies.Coins` in Luau).
57
+ `Paths.Currencies.Money` exists because **your** Template passed to `Init` had that field. Full copies: [Examples](examples/index.md).
54
58
 
55
59
  ## Performance notes
56
60
 
@@ -68,4 +72,4 @@ Keep path strings in `config.h`. Match the tokens your DataServiceV2 template al
68
72
  5. **Headers declare, scripts define.** Prototypes in `.h`, bodies in `.cpp`.
69
73
  6. **Do not share mutable statics across server and client** — use Net or DataService.
70
74
 
71
- See also: [syntax](syntax.md), [libraries](libraries.md), [comparison](comparison.md).
75
+ See also: [syntax](syntax.md), [print and cout](print-cout.md), [libraries](libraries/index.md), [OOP](oop/index.md), [examples](examples/index.md), [comparison](comparison.md).
@@ -47,7 +47,7 @@ const string REMOTE_COINS = "Coins";
47
47
 
48
48
  `leaderstats.server.cpp` creates leaderstats. It does not also open the shop UI. Name files after the system: `inventory.server.cpp`, `shop.client.cpp`.
49
49
 
50
- `*.server.cpp` / `*.client.cpp` are **tags** (the same keys roblox-ts uses). Untagged `.cpp` is a ModuleScript. `.legacy.server.cpp` / `.legacy.client.cpp` skip the service split. See [Architecture](architecture.md).
50
+ `*.server.cpp` / `*.client.cpp` are **tags** (the same keys roblox-ts uses). Untagged `.cpp` is a ModuleScript. `.legacy.server.cpp` / `.legacy.client.cpp` skip the service split. See [Architecture](architecture.md) and [OOP structure](oop/index.md).
51
51
 
52
52
  ## CluauppLibs vs extra Wally
53
53
 
@@ -58,4 +58,6 @@ const string REMOTE_COINS = "Coins";
58
58
 
59
59
  Do not install a second Janitor from Wally unless you have a reason — CluauppLibs already has howmanysmall/Janitor. DataServiceV2 still bundles its own janitor/quicknet/signal inside the DataService folder.
60
60
 
61
+ How-tos: [Libraries](libraries/index.md). Service layout: [OOP](oop/index.md). Copy-paste: [Examples](examples/index.md).
62
+
61
63
  Next: [advanced Cluaupp](cpp-advanced.md).
@@ -9,7 +9,7 @@ Safety in Cluaupp is not a sandbox flag. It is a set of habits the compiler and
9
9
 
10
10
  ## 1. Never trust the client
11
11
 
12
- [Net](libraries.md) lets a client `FireServer`. The server must validate:
12
+ [Net](libraries/net.md) lets a client `FireServer`. The server must validate:
13
13
 
14
14
  ```cpp
15
15
  void OnBuy(Player* player, int productId) {
@@ -17,19 +17,23 @@ void OnBuy(Player* player, int productId) {
17
17
  return;
18
18
  }
19
19
  int price = PriceOf(productId);
20
- int coins = DataService::Get(player, "Currencies.Coins");
20
+ Data* data = DataService::Server.WaitFor(player);
21
+ if (data == nullptr) {
22
+ return;
23
+ }
24
+ int coins = data->Get(DataService::Server.Paths.Currencies.Money);
21
25
  if (coins < price) {
22
26
  return;
23
27
  }
24
- DataService::Set(player, "Currencies.Coins", coins - price);
28
+ data->Set(DataService::Server.Paths.Currencies.Money, coins - price);
25
29
  }
26
30
  ```
27
31
 
28
- Do not store coins only on the client. Do not let the client pass the new coin total — pass the *intent* (`productId`).
32
+ Do not store coins only on the client. Do not let the client pass the new coin total — pass the *intent* (`productId`). Full systems: [Shop](examples/shop.md), [Combat](examples/combat.md).
29
33
 
30
34
  ## 2. Clean up with Janitor
31
35
 
32
- Every `Connect` that outlives a player, a GUI, or a tool needs a Janitor. Leaving a step, destroying a character, or closing a menu should `Cleanup()` or `LinkToInstance`.
36
+ Every `Connect` that outlives a player, a GUI, or a tool needs a [Janitor](libraries/janitor.md). Leaving a step, destroying a character, or closing a menu should `Cleanup()` or `LinkToInstance`.
33
37
 
34
38
  ```cpp
35
39
  auto* janitor = new Janitor();
@@ -45,7 +49,7 @@ Leaked connections duplicate effects: double coins, stacked cameras, lingering h
45
49
 
46
50
  ## 4. One writer for persisted data
47
51
 
48
- [DataServiceV2](https://wally.run/package/kartzrbx/dataservicev2) distinguishes `Get()` (merged, includes transient admin overlays) from `GetPersisted()` (what ProfileStore will save). Use `SetTransient` for test panels so you never persist cheat values. Only the server writes persisted paths.
52
+ [DataService](libraries/dataservice.md) distinguishes `Get()` (merged, includes transient admin overlays) from `GetPersisted()` (what ProfileStore will save). Use `SetTransient` for test panels so you never persist cheat values. Only the server writes persisted paths. The save **shape** is your Template — the library does not ship `Currencies`.
49
53
 
50
54
  ## 5. Buffers, not ad-hoc strings
51
55
 
@@ -55,4 +59,4 @@ Leaked connections duplicate effects: double coins, stacked cameras, lingering h
55
59
 
56
60
  Remote names (`Net::Event("Coins")`) are public protocol. Keep them `const`, short, and unique. Changing a name without a migration breaks old clients.
57
61
 
58
- Next: [organization](cpp-organization.md).
62
+ Next: [organization](cpp-organization.md), [OOP](oop/index.md), [Examples](examples/index.md).
package/docs/cpp-types.md CHANGED
@@ -46,7 +46,7 @@ player.Name = "Kartz"
46
46
  player:FindFirstChild("leaderstats")
47
47
  ```
48
48
 
49
- There is no pointer arithmetic, no `delete`, no `std::unique_ptr`. Lifetime is Roblox’s: parented Instances live until `Destroy` or until a [Janitor](libraries.md) cleans them.
49
+ There is no pointer arithmetic, no `delete`, no `std::unique_ptr`. Lifetime is Roblox’s: parented Instances live until `Destroy` or until a [Janitor](libraries/janitor.md) cleans them.
50
50
 
51
51
  ## `nullptr`
52
52
 
@@ -0,0 +1,5 @@
1
+ {
2
+ "label": "Examples",
3
+ "position": 21,
4
+ "collapsed": false
5
+ }
@@ -0,0 +1,239 @@
1
+ ---
2
+ title: Combat (server validation)
3
+ sidebar_position: 4
4
+ ---
5
+
6
+ # Combat (server validation)
7
+
8
+ The client never sends **damage**. It sends **who I want to hit**. The server decides range, cooldown, alive state, and the damage constant.
9
+
10
+ Pair with [Data boot](data-boot.md) if a kill should grant Money.
11
+
12
+ ## Shared config
13
+
14
+ `src/shared/constants/CombatConfig.hpp`
15
+
16
+ ```cpp
17
+ #pragma once
18
+
19
+ const double ATTACK_RANGE = 12;
20
+ const double ATTACK_COOLDOWN = 0.4;
21
+ const int ATTACK_DAMAGE = 12;
22
+ const int KILL_REWARD = 5;
23
+ ```
24
+
25
+ Keep numbers in a header. Both server and client can include range for FX; **only the server** applies `TakeDamage`.
26
+
27
+ ## Server — `CombatServer.server.cpp`
28
+
29
+ ```cpp
30
+ #include <cluaupp/roblox.hpp>
31
+ #include <cluaupp/libs/janitor.hpp>
32
+ #include <cluaupp/libs/net.hpp>
33
+ #include <cluaupp/libs/dataservice.hpp>
34
+ #include "../../../shared/constants/CombatConfig.hpp"
35
+
36
+ Players* Players = GetService<Players>();
37
+ Janitor* janitor = new Janitor();
38
+ NetEvent* Attack = Net::Event("Attack");
39
+
40
+ BasePart* RootPart(Model* character) {
41
+ if (character == nullptr) {
42
+ return nullptr;
43
+ }
44
+ return character->FindFirstChild("HumanoidRootPart");
45
+ }
46
+
47
+ Humanoid* HumanoidOf(Model* character) {
48
+ if (character == nullptr) {
49
+ return nullptr;
50
+ }
51
+ return character->FindFirstChildOfClass("Humanoid");
52
+ }
53
+
54
+ bool InRange(Model* a, Model* b, double maxRange) {
55
+ BasePart* rootA = RootPart(a);
56
+ BasePart* rootB = RootPart(b);
57
+ if (rootA == nullptr) {
58
+ return false;
59
+ }
60
+ if (rootB == nullptr) {
61
+ return false;
62
+ }
63
+ Vector3 delta = rootA->Position - rootB->Position;
64
+ if (delta.Magnitude > maxRange) {
65
+ return false;
66
+ }
67
+ return true;
68
+ }
69
+
70
+ bool OnCooldown(Player* attacker) {
71
+ NumberValue* last = attacker->FindFirstChild("LastAttackAt");
72
+ if (last == nullptr) {
73
+ return false;
74
+ }
75
+ if (tick() - last->Value < ATTACK_COOLDOWN) {
76
+ return true;
77
+ }
78
+ return false;
79
+ }
80
+
81
+ void MarkAttack(Player* attacker) {
82
+ NumberValue* last = attacker->FindFirstChild("LastAttackAt");
83
+ if (last == nullptr) {
84
+ last = new NumberValue(attacker);
85
+ last->Name = "LastAttackAt";
86
+ }
87
+ last->Value = tick();
88
+ }
89
+
90
+ void RewardKill(Player* attacker) {
91
+ Data* data = DataService::Server.Get(attacker);
92
+ if (data == nullptr) {
93
+ return;
94
+ }
95
+ int money = data->Get(DataService::Server.Paths.Currencies.Money);
96
+ data->Set(DataService::Server.Paths.Currencies.Money, money + KILL_REWARD);
97
+ }
98
+
99
+ void OnAttack(Player* attacker, int targetUserId) {
100
+ if (attacker == nullptr) {
101
+ return;
102
+ }
103
+ if (targetUserId < 1) {
104
+ return;
105
+ }
106
+ if (OnCooldown(attacker)) {
107
+ return;
108
+ }
109
+
110
+ Player* target = Players->GetPlayerByUserId(targetUserId);
111
+ if (target == nullptr) {
112
+ return;
113
+ }
114
+ if (target == attacker) {
115
+ return;
116
+ }
117
+
118
+ Model* attackerChar = attacker->Character;
119
+ Model* targetChar = target->Character;
120
+ Humanoid* attackerHum = HumanoidOf(attackerChar);
121
+ Humanoid* targetHum = HumanoidOf(targetChar);
122
+ if (attackerHum == nullptr) {
123
+ return;
124
+ }
125
+ if (targetHum == nullptr) {
126
+ return;
127
+ }
128
+ if (attackerHum->Health <= 0) {
129
+ return;
130
+ }
131
+ if (targetHum->Health <= 0) {
132
+ return;
133
+ }
134
+ if (InRange(attackerChar, targetChar, ATTACK_RANGE) == false) {
135
+ cout::ping << attacker->Name << " out of range" << endl;
136
+ return;
137
+ }
138
+
139
+ MarkAttack(attacker);
140
+ double healthBefore = targetHum->Health;
141
+ targetHum->TakeDamage(ATTACK_DAMAGE);
142
+ if (healthBefore > 0) {
143
+ if (targetHum->Health <= 0) {
144
+ RewardKill(attacker);
145
+ }
146
+ }
147
+ }
148
+
149
+ void init() {
150
+ Attack->On(OnAttack);
151
+ janitor->Add(Attack);
152
+ }
153
+ ```
154
+
155
+ ## Client — `CombatClient.client.cpp`
156
+
157
+ The LocalScript only picks a target and fires. It does **not** pass `ATTACK_DAMAGE`.
158
+
159
+ ```cpp
160
+ #include <cluaupp/roblox.hpp>
161
+ #include <cluaupp/libs/janitor.hpp>
162
+ #include <cluaupp/libs/net.hpp>
163
+
164
+ Players* Players = GetService<Players>();
165
+ UserInputService* UserInput = GetService<UserInputService>();
166
+ Janitor* janitor = new Janitor();
167
+ NetEvent* Attack = Net::Event("Attack");
168
+
169
+ Player* PlayerFromPart(Instance* part) {
170
+ if (part == nullptr) {
171
+ return nullptr;
172
+ }
173
+ Instance* model = part->FindFirstAncestorOfClass("Model");
174
+ if (model == nullptr) {
175
+ return nullptr;
176
+ }
177
+ Player* fromModel = Players->GetPlayerFromCharacter(model);
178
+ if (fromModel != nullptr) {
179
+ return fromModel;
180
+ }
181
+ Instance* outer = model->FindFirstAncestorOfClass("Model");
182
+ if (outer == nullptr) {
183
+ return nullptr;
184
+ }
185
+ return Players->GetPlayerFromCharacter(outer);
186
+ }
187
+
188
+ void OnInputBegan(InputObject* input, bool gameProcessed) {
189
+ if (gameProcessed) {
190
+ return;
191
+ }
192
+ if (input->UserInputType != Enum::UserInputType::MouseButton1) {
193
+ return;
194
+ }
195
+ Player* localPlayer = Players->LocalPlayer;
196
+ if (localPlayer == nullptr) {
197
+ return;
198
+ }
199
+ Mouse* mouse = localPlayer->GetMouse();
200
+ if (mouse == nullptr) {
201
+ return;
202
+ }
203
+ Player* target = PlayerFromPart(mouse->Target);
204
+ if (target == nullptr) {
205
+ return;
206
+ }
207
+ if (target == localPlayer) {
208
+ return;
209
+ }
210
+ Attack->FireServer(target->UserId);
211
+ }
212
+
213
+ void init() {
214
+ janitor->Add(UserInput->InputBegan.Connect(OnInputBegan));
215
+ }
216
+ ```
217
+
218
+ ## What the server rejects
219
+
220
+ | Client cheat | Server check |
221
+ | --- | --- |
222
+ | `FireServer(99999)` damage | Damage is `ATTACK_DAMAGE` in the header — not an argument |
223
+ | Hit a player across the map | `delta.Magnitude > ATTACK_RANGE` |
224
+ | Spam click | `LastAttackAt` + `ATTACK_COOLDOWN` |
225
+ | Target userId `0` / self | `targetUserId < 1`, `target == attacker` |
226
+ | Hit a dead / missing character | Humanoid nil or `Health <= 0` |
227
+ | Fire before spawn | `Character` / `HumanoidRootPart` missing |
228
+
229
+ `Net::Event("Attack")` must be the **same string** on both sides. Treat that name as public protocol.
230
+
231
+ ## Cooldown storage
232
+
233
+ `LastAttackAt` is a `NumberValue` on the Player: session-only, no DataStore. Do not persist combat cadence. For anti-exploit that must survive respawn, use DataService `SetTransient` on a path **you** added to the Template — still not a library field.
234
+
235
+ ## No lambdas
236
+
237
+ `Attack->On(OnAttack)` needs a named function. The first argument of a server `On` is the `Player` who fired.
238
+
239
+ See [Net](../libraries/net.md) and [Safety](../cpp-safety.md).
@@ -0,0 +1,98 @@
1
+ ---
2
+ title: Data boot
3
+ sidebar_position: 2
4
+ ---
5
+
6
+ # Data boot
7
+
8
+ Start DataService **once** on the server and once on the client. Gameplay scripts (`leaderstats`, combat, shop) only `WaitFor` — they do not call `Init`.
9
+
10
+ The save **shape is yours**. Do not put `Currencies` on the library `DataPath` type. Put it on a shared struct and pass that as `.Template`.
11
+
12
+ ## Shared template
13
+
14
+ `src/shared/constants/TemplateData.hpp`
15
+
16
+ ```cpp
17
+ #pragma once
18
+
19
+ struct TemplateData {
20
+ struct Currencies {
21
+ int Money = 0;
22
+ int Level = 1;
23
+ } Currencies;
24
+ };
25
+ ```
26
+
27
+ `src/server/configurations/PlayerDataVersion.hpp`
28
+
29
+ ```cpp
30
+ #pragma once
31
+
32
+ const string PLAYER_DATA_VERSION = "PlayerData_v1";
33
+ ```
34
+
35
+ Bump the store name when you **intentionally** wipe saves. Changing a field default in `TemplateData` does not migrate old profiles by itself.
36
+
37
+ ## Server — `DataBoot.server.cpp`
38
+
39
+ Use `void init()`, not `int main()`. Cluaupp only auto-calls `init()`.
40
+
41
+ ```cpp
42
+ #include <cluaupp/roblox.hpp>
43
+ #include <cluaupp/libs/dataservice.hpp>
44
+ #include "../../shared/constants/TemplateData.hpp"
45
+ #include "../configurations/PlayerDataVersion.hpp"
46
+
47
+ void init() {
48
+ TemplateData playerData = TemplateData {};
49
+ DataService::Server.Init(DataServiceOptions {
50
+ .Template = playerData,
51
+ .StoreName = PLAYER_DATA_VERSION,
52
+ .UseMock = true,
53
+ });
54
+ }
55
+ ```
56
+
57
+ | Field | In production |
58
+ | --- | --- |
59
+ | `.Template` | Same struct the rest of the game reads through `Paths` |
60
+ | `.StoreName` | Stable DataStore name (version it when you wipe) |
61
+ | `.UseMock` | `true` in Studio so you do not hit the live store |
62
+
63
+ After `Init`, `DataService::Server.Paths.Currencies.Money` exists because the Template had that table — not because the library shipped those fields.
64
+
65
+ ## Client — `DataBoot.client.cpp`
66
+
67
+ ```cpp
68
+ #include <cluaupp/roblox.hpp>
69
+ #include <cluaupp/libs/dataservice.hpp>
70
+
71
+ void init() {
72
+ DataService::Client.Init();
73
+ }
74
+ ```
75
+
76
+ Client `Init` has no Template. The server already owns the document.
77
+
78
+ ## What other scripts do
79
+
80
+ ```cpp
81
+ Data* data = DataService::Server.WaitFor(player);
82
+ if (data == nullptr) {
83
+ return;
84
+ }
85
+
86
+ int money = data->Get(DataService::Server.Paths.Currencies.Money);
87
+ ```
88
+
89
+ - Server gameplay: `WaitFor(player)` then `Get` / `Set`.
90
+ - Client HUD: `WaitForData()` then `Get`.
91
+ - Never `Init` twice. A second `Init` fights the first store.
92
+
93
+ ## File tags
94
+
95
+ `DataBoot.server.cpp` → Script **RunContext Server** (`init.luau` + `init.meta.json`).
96
+ `DataBoot.client.cpp` → LocalScript (`init.client.luau`).
97
+
98
+ See [file tags](../oop/file-tags.md).