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.
- package/CHANGELOG.md +20 -1
- package/README.md +5 -8
- package/docs/README.md +13 -10
- package/docs/architecture.md +18 -11
- package/docs/cli.md +2 -2
- package/docs/config.md +1 -1
- package/docs/cpp-advanced.md +10 -6
- package/docs/cpp-organization.md +3 -1
- package/docs/cpp-safety.md +11 -7
- package/docs/cpp-types.md +1 -1
- package/docs/examples/_category_.json +5 -0
- package/docs/examples/combat.md +239 -0
- package/docs/examples/data-boot.md +98 -0
- package/docs/examples/hud.md +96 -0
- package/docs/examples/index.md +43 -0
- package/docs/examples/leaderstats.md +140 -0
- package/docs/examples/shop.md +122 -0
- package/docs/examples/sword.md +108 -0
- package/docs/getting-started.md +12 -6
- package/docs/intellisense.md +31 -0
- package/docs/intro.md +2 -2
- package/docs/libraries/_category_.json +5 -0
- package/docs/libraries/dataservice.md +104 -0
- package/docs/libraries/index.md +22 -0
- package/docs/libraries/janitor.md +65 -0
- package/docs/libraries/more.md +79 -0
- package/docs/libraries/net.md +35 -0
- package/docs/libraries/promise.md +27 -0
- package/docs/oop/_category_.json +5 -0
- package/docs/oop/file-tags.md +49 -0
- package/docs/oop/index.md +22 -0
- package/docs/oop/modules.md +86 -0
- package/docs/oop/services.md +83 -0
- package/docs/print-cout.md +91 -0
- package/docs/syntax.md +59 -5
- package/editors/vscode/extension.js +146 -0
- package/editors/vscode/package.json +25 -0
- package/include/cluaupp/libs/janitor.hpp +7 -3
- package/include/cluaupp/roblox.hpp +19 -0
- package/package.json +66 -65
- package/runtime/Janitor/init.luau +4 -34
- package/src/architecture.js +108 -48
- package/src/cli.js +85 -10
- package/src/client/init.client.cpp +5 -0
- package/src/compile.js +20 -4
- package/src/editor-install.js +218 -0
- package/src/emit.js +320 -8
- package/src/intellisense.js +1368 -0
- package/src/layout.js +129 -0
- package/src/lex.js +17 -9
- package/src/libs.js +95 -16
- package/src/lsp.js +227 -0
- package/src/parse.js +187 -6
- package/src/preprocess.js +71 -3
- package/src/server/leaderstats.server.cpp +28 -0
- package/src/shared/config.cpp +5 -0
- package/src/shared/config.h +3 -0
- package/src/understand.js +64 -6
- package/templates/game/.clangd +11 -0
- package/templates/game/.vscode/c_cpp_properties.json +6 -2
- package/templates/game/.vscode/extensions.json +6 -0
- package/templates/game/.vscode/settings.json +16 -2
- package/templates/game/compile_flags.txt +2 -0
- 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/`
|
|
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
|
[](https://www.npmjs.com/package/cluaupp)
|
|
19
12
|
[](https://nodejs.org)
|
|
20
13
|
[](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. [
|
|
8
|
-
6. [
|
|
9
|
-
7. [
|
|
10
|
-
8. [
|
|
11
|
-
9. [
|
|
12
|
-
10. [
|
|
13
|
-
11. [
|
|
14
|
-
12. [
|
|
15
|
-
13. [
|
|
16
|
-
14. [
|
|
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
|
|
package/docs/architecture.md
CHANGED
|
@@ -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` |
|
|
28
|
-
| `hud.client.cpp` |
|
|
29
|
-
| `
|
|
30
|
-
| `boot.legacy.
|
|
31
|
-
| `
|
|
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.
|
|
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.
|
|
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
|
-
-
|
|
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
|
-
|
|
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.
|
|
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 `
|
|
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.
|
|
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).
|
package/docs/cpp-advanced.md
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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).
|
package/docs/cpp-organization.md
CHANGED
|
@@ -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).
|
package/docs/cpp-safety.md
CHANGED
|
@@ -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
|
-
|
|
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::
|
|
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
|
-
[
|
|
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,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).
|