cluaupp 0.2.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -75
- package/README.md +42 -88
- package/docs/README.md +25 -21
- package/docs/architecture.md +13 -76
- package/docs/cli.md +12 -64
- package/docs/comparison.md +6 -24
- package/docs/config.md +6 -7
- package/docs/cpp-advanced.md +6 -3
- package/docs/cpp-types.md +2 -0
- package/docs/examples/combat.md +19 -227
- package/docs/examples/hud.md +13 -81
- package/docs/examples/index.md +9 -16
- package/docs/examples/leaderstats.md +44 -115
- package/docs/getting-started.md +20 -135
- package/docs/intellisense.md +5 -42
- package/docs/intro.md +16 -15
- package/docs/libraries/dataservice.md +4 -1
- package/docs/libraries/index.md +2 -1
- package/docs/libraries/more.md +3 -1
- package/docs/libraries/ui.md +76 -0
- package/docs/migration.md +38 -0
- package/docs/oop/file-tags.md +12 -36
- package/docs/oop/index.md +18 -11
- package/docs/roblox-api.md +4 -56
- package/docs/syntax.md +54 -179
- package/editors/vscode/extension.js +1 -1
- package/editors/vscode/package.json +4 -4
- package/examples/README.md +5 -7
- package/examples/game/.vscode/extensions.json +2 -2
- package/examples/game/.vscode/settings.json +6 -23
- package/examples/game/src/client/hud.client.clpp +54 -0
- package/examples/game/src/client/init.client.clpp +6 -0
- package/examples/game/src/server/combat.server.clpp +85 -0
- package/examples/game/src/server/leaderstats.server.clpp +28 -0
- package/examples/game/src/shared/PlayerData.clh +20 -0
- package/examples/game/src/shared/config.clp +6 -0
- package/examples/game/src/shared/features.clp +88 -0
- package/generated/api.js +15 -1
- package/generated/cli.js +40 -24
- package/generated/clpp/contract.js +5 -0
- package/generated/clpp/index.js +27 -0
- package/generated/clpp/paths.js +121 -0
- package/generated/clpp/postprocess.js +78 -0
- package/generated/clpp/runner.js +102 -0
- package/generated/compile.js +26 -138
- package/generated/emit.js +79 -3
- package/generated/intellisense.js +83 -207
- package/generated/lex.js +3 -0
- package/generated/libs.js +1 -1
- package/generated/lsp.js +1 -8
- package/generated/parse.js +119 -24
- package/generated/preprocess.js +8 -179
- package/generated/system-understander.js +1 -1
- package/generated/transpile.js +3 -50
- package/generated/utils/project.js +13 -28
- package/include/{cluaupp/datatypes.hpp → clpp/datatypes.clh} +5 -1
- package/include/{cluaupp/generated/instances.hpp → clpp/generated/instances.clh} +1 -1
- package/include/{cluaupp/libs/chrono.hpp → clpp/libs/chrono.clh} +9 -9
- package/include/{cluaupp/libs/cmdr.hpp → clpp/libs/cmdr.clh} +4 -4
- package/include/{cluaupp/libs/dataservice.hpp → clpp/libs/dataservice.clh} +8 -15
- package/include/{cluaupp/libs/display.hpp → clpp/libs/display.clh} +1 -2
- package/include/{cluaupp/libs/ezvisual.hpp → clpp/libs/ezvisual.clh} +1 -3
- package/include/{cluaupp/libs/formatnumber.hpp → clpp/libs/formatnumber.clh} +2 -3
- package/include/{cluaupp/libs/fusion.hpp → clpp/libs/fusion.clh} +5 -5
- package/include/{cluaupp/libs/iris.hpp → clpp/libs/iris.clh} +2 -2
- package/include/{cluaupp/libs/janitor.hpp → clpp/libs/janitor.clh} +1 -2
- package/include/{cluaupp/libs/module3d.hpp → clpp/libs/module3d.clh} +2 -4
- package/include/{cluaupp/libs/net.hpp → clpp/libs/net.clh} +2 -4
- package/include/{cluaupp/libs/promise.hpp → clpp/libs/promise.clh} +1 -4
- package/include/{cluaupp/libs/spring.hpp → clpp/libs/spring.clh} +1 -2
- package/include/{cluaupp/libs/statemachine.hpp → clpp/libs/statemachine.clh} +2 -3
- package/include/{cluaupp/libs/stickybillboard.hpp → clpp/libs/stickybillboard.clh} +1 -2
- package/include/{cluaupp/libs/topbarplus.hpp → clpp/libs/topbarplus.clh} +3 -4
- package/include/clpp/libs.clh +24 -0
- package/include/clpp/roblox.clh +31 -0
- package/package.json +20 -16
- package/src/api.ts +15 -1
- package/src/cli.ts +39 -25
- package/src/clpp/contract.ts +39 -0
- package/src/clpp/index.ts +18 -0
- package/src/clpp/paths.ts +117 -0
- package/src/clpp/postprocess.ts +93 -0
- package/src/clpp/runner.ts +102 -0
- package/src/compile.ts +26 -149
- package/src/intellisense.ts +82 -211
- package/src/libs.ts +1 -1
- package/src/lsp.ts +1 -5
- package/src/preprocess.ts +8 -180
- package/src/system-understander.ts +1 -1
- package/src/transpile.ts +9 -52
- package/src/utils/project.ts +12 -28
- package/templates/game/.vscode/extensions.json +2 -2
- package/templates/game/.vscode/settings.json +6 -23
- package/templates/game/src/client/init.client.clpp +6 -0
- package/templates/game/src/server/leaderstats.server.clpp +29 -0
- package/templates/game/src/shared/config.clp +6 -0
- package/examples/game/.clangd +0 -25
- package/examples/game/.vscode/c_cpp_properties.json +0 -27
- package/examples/game/compile_flags.txt +0 -6
- package/examples/game/src/client/hud.client.cpp +0 -12
- package/examples/game/src/client/init.client.cpp +0 -6
- package/examples/game/src/server/combat.server.cpp +0 -23
- package/examples/game/src/server/leaderstats.server.cpp +0 -29
- package/examples/game/src/shared/config.cpp +0 -5
- package/examples/game/src/shared/config.h +0 -5
- package/include/cluaupp/libs.hpp +0 -25
- package/include/cluaupp/roblox.hpp +0 -47
- package/src/architecture.ts +0 -500
- package/src/ast.ts +0 -37
- package/src/emit.ts +0 -531
- package/src/emitter/luau-codegen.ts +0 -187
- package/src/emitter/translators.ts +0 -168
- package/src/headers.ts +0 -233
- package/src/layout.ts +0 -114
- package/src/lex.ts +0 -155
- package/src/parse.ts +0 -630
- package/src/parser/collector.ts +0 -148
- package/src/parser/index.ts +0 -27
- package/src/tree-sitter-cpp.d.ts +0 -4
- package/src/understand.ts +0 -322
- package/templates/game/.vscode/c_cpp_properties.json +0 -27
- package/templates/game/src/client/init.client.cpp +0 -6
- package/templates/game/src/server/leaderstats.server.cpp +0 -29
- package/templates/game/src/shared/config.cpp +0 -5
- package/templates/game/src/shared/config.h +0 -5
- /package/include/{cluaupp/generated/enums.hpp → clpp/generated/enums.clh} +0 -0
- /package/include/{cluaupp/libs/math.hpp → clpp/libs/math.clh} +0 -0
- /package/include/{cluaupp/libs/twinkle.hpp → clpp/libs/twinkle.clh} +0 -0
- /package/include/{cluaupp/libs/vfx.hpp → clpp/libs/vfx.clh} +0 -0
package/docs/examples/combat.md
CHANGED
|
@@ -3,237 +3,29 @@ title: Combat (server validation)
|
|
|
3
3
|
sidebar_position: 4
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Combat
|
|
6
|
+
# Combat
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Live source: [`examples/game/src/server/combat.server.clpp`](../../examples/game/src/server/combat.server.clpp). Language: [CL++](https://kartzrbx.github.io/CLPP/).
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
This sample uses exclusive CL++:
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
- `guard` / `match` instead of nested null checks
|
|
13
|
+
- `signal<Player*, int> OnHit` and `OnHit~>Connect` / `OnHit::Fire`
|
|
14
|
+
- `observable int combo`
|
|
15
|
+
- `[[server]]` on `WatchWorkspace`
|
|
16
|
+
- `spawn { task::wait(1); … }`
|
|
13
17
|
|
|
14
|
-
`
|
|
18
|
+
The client never decides damage. Touched parts fire `OnHit`; the server prints combo and destroys the part.
|
|
15
19
|
|
|
16
|
-
```
|
|
17
|
-
#
|
|
20
|
+
```clpp
|
|
21
|
+
#include <clpp/roblox.clh>
|
|
22
|
+
#include <clpp/libs/janitor.clh>
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
struct CombatServer {
|
|
25
|
+
Janitor* janitor;
|
|
26
|
+
signal<Player*, int> OnHit;
|
|
27
|
+
void PlayerEntered(Player* player);
|
|
28
|
+
void BindPart(BasePart* part);
|
|
29
|
+
void WatchWorkspace();
|
|
30
|
+
};
|
|
23
31
|
```
|
|
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).
|
package/docs/examples/hud.md
CHANGED
|
@@ -5,92 +5,24 @@ sidebar_position: 6
|
|
|
5
5
|
|
|
6
6
|
# HUD
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Live source: [`examples/game/src/client/hud.client.clpp`](../../examples/game/src/client/hud.client.clpp). Fusion API: [CL++](https://kartzrbx.github.io/CLPP/).
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Client-only (`[[client]]`). `Fusion:scoped` / `Fusion:Value` / `Fusion:Computed` / `Fusion:New` build a coins label. `guard` fails if `LocalPlayer` is missing.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
```cpp
|
|
17
|
-
#include <cluaupp/roblox.hpp>
|
|
18
|
-
#include <cluaupp/libs/janitor.hpp>
|
|
19
|
-
#include <cluaupp/libs/dataservice.hpp>
|
|
20
|
-
#include <cluaupp/libs/formatnumber.hpp>
|
|
21
|
-
#include <cluaupp/libs/twinkle.hpp>
|
|
22
|
-
|
|
23
|
-
Players* Players = GetService<Players>();
|
|
24
|
-
Janitor* janitor = new Janitor();
|
|
25
|
-
|
|
26
|
-
TextLabel* FindLabel(PlayerGui* playerGui, string name) {
|
|
27
|
-
if (playerGui == nullptr) {
|
|
28
|
-
return nullptr;
|
|
29
|
-
}
|
|
30
|
-
Instance* gui = playerGui->FindFirstChild("Hud");
|
|
31
|
-
if (gui == nullptr) {
|
|
32
|
-
return nullptr;
|
|
33
|
-
}
|
|
34
|
-
return gui->FindFirstChild(name);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
void Render(Data* data, TextLabel* moneyLabel, TextLabel* levelLabel) {
|
|
38
|
-
if (data == nullptr) {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
int money = data->Get(DataService::Client.Paths.Currencies.Money);
|
|
42
|
-
int level = data->Get(DataService::Client.Paths.Currencies.Level);
|
|
43
|
-
if (moneyLabel != nullptr) {
|
|
44
|
-
moneyLabel->Text = FormatNumber::Abbreviate(money);
|
|
45
|
-
}
|
|
46
|
-
if (levelLabel != nullptr) {
|
|
47
|
-
levelLabel->Text = FormatNumber::Comma(level);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
void OnCurrenciesChanged() {
|
|
52
|
-
Player* player = Players->LocalPlayer;
|
|
53
|
-
if (player == nullptr) {
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
PlayerGui* playerGui = player->FindFirstChildOfClass("PlayerGui");
|
|
57
|
-
TextLabel* moneyLabel = FindLabel(playerGui, "MoneyLabel");
|
|
58
|
-
TextLabel* levelLabel = FindLabel(playerGui, "LevelLabel");
|
|
59
|
-
Render(DataService::Client.Get(), moneyLabel, levelLabel);
|
|
60
|
-
}
|
|
12
|
+
```clpp
|
|
13
|
+
#include <clpp/roblox.clh>
|
|
14
|
+
#include <clpp/libs/fusion.clh>
|
|
61
15
|
|
|
16
|
+
[[client]]
|
|
62
17
|
void init() {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
PlayerGui* playerGui = player->FindFirstChildOfClass("PlayerGui");
|
|
68
|
-
if (playerGui == nullptr) {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
Data* data = DataService::Client.WaitForData();
|
|
73
|
-
if (data == nullptr) {
|
|
74
|
-
cout::warn << "HUD: profile missing" << endl;
|
|
18
|
+
Players* players = GetService<Players>();
|
|
19
|
+
Player* localPlayer = players.LocalPlayer;
|
|
20
|
+
guard (localPlayer != null) else {
|
|
21
|
+
report("LocalPlayer missing");
|
|
75
22
|
return;
|
|
76
23
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
Render(data, moneyLabel, levelLabel);
|
|
81
|
-
|
|
82
|
-
if (moneyLabel != nullptr) {
|
|
83
|
-
Twinkle::Fade(moneyLabel, true);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
janitor->Add(data->GetChangedSignal(DataService::Client.Paths.Currencies).Connect(OnCurrenciesChanged));
|
|
24
|
+
auto scope = Fusion:scoped();
|
|
25
|
+
auto coins = Fusion:Value(scope, 0);
|
|
26
|
+
coins(100);
|
|
87
27
|
}
|
|
88
28
|
```
|
|
89
|
-
|
|
90
|
-
## Rules
|
|
91
|
-
|
|
92
|
-
- Call `DataService::Client.Init()` in [Data boot](data-boot.md) first. Script order in StarterPlayerScripts is not a contract — `WaitForData` yields until the profile exists.
|
|
93
|
-
- Do not `data->Set` Money from the HUD. Display only.
|
|
94
|
-
- `Twinkle::Fade` is optional. See [more libraries](../libraries/more.md).
|
|
95
|
-
|
|
96
|
-
`HudClient.client.cpp` is a LocalScript (`init.client.luau`).
|
package/docs/examples/index.md
CHANGED
|
@@ -5,28 +5,21 @@ sidebar_position: 1
|
|
|
5
5
|
|
|
6
6
|
# Examples
|
|
7
7
|
|
|
8
|
-
High-quality Cluaupp systems you can copy. Each page is a full service
|
|
8
|
+
High-quality Cluaupp systems you can copy. Each page is a full service in **CL++**. Live sample: [`examples/game`](../../examples/game). Language reference: [kartzrbx.github.io/CLPP](https://kartzrbx.github.io/CLPP/).
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
These are **not** dumps of `int main()`. Entry is `void init()`. There are no lambdas and no custom C++ classes — named functions + structs.
|
|
10
|
+
These are **not** dumps of `int main()`. Entry is `void init()`. Types are `struct` + `Class::` methods. Canonical Leaderstats: [handbook](https://kartzrbx.github.io/Cluaupp/docs/leaderstats.html).
|
|
13
11
|
|
|
14
12
|
## Suggested layout
|
|
15
13
|
|
|
16
14
|
```
|
|
17
15
|
src/
|
|
18
|
-
shared/
|
|
19
|
-
shared/
|
|
20
|
-
shared/
|
|
21
|
-
server/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
client/controllers/combat/CombatClient.client.cpp
|
|
26
|
-
server/services/shop/ShopServer.server.cpp
|
|
27
|
-
client/controllers/shop/ShopClient.client.cpp
|
|
28
|
-
client/controllers/hud/HudClient.client.cpp
|
|
29
|
-
server/services/weapons/SwordServer.server.cpp
|
|
16
|
+
shared/PlayerData.clh
|
|
17
|
+
shared/config.clp
|
|
18
|
+
shared/features.clp
|
|
19
|
+
server/leaderstats.server.clpp
|
|
20
|
+
server/combat.server.clpp
|
|
21
|
+
client/init.client.clpp
|
|
22
|
+
client/hud.client.clpp
|
|
30
23
|
```
|
|
31
24
|
|
|
32
25
|
Boot DataService **once**. Other services `WaitFor` after that.
|
|
@@ -5,136 +5,65 @@ sidebar_position: 3
|
|
|
5
5
|
|
|
6
6
|
# Leaderstats
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Canonical copy (three files): [Docs → Example: Leaderstats](https://kartzrbx.github.io/Cluaupp/docs/leaderstats.html).
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Roblox shows the player list from a Folder named exactly `leaderstats` under the Player. This service **creates** those values, then mirrors DataService coins into them.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
It does **not** call `DataService.Init`. Boot that in [Data boot](data-boot.md) with your `PlayerData` Template.
|
|
13
|
+
|
|
14
|
+
## Files
|
|
15
|
+
|
|
16
|
+
| File | Role |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| `shared/PlayerData.h` | Template structs (`Currencies.Coins`, inventory `LuaArray`) |
|
|
19
|
+
| `server/LeaderstatsServer.h` | `struct LeaderstatsServer` — fields + method decls (same stem) |
|
|
20
|
+
| `server/LeaderstatsServer.server.cpp` | `Class::` bodies + `void init()` |
|
|
21
|
+
|
|
22
|
+
## Rules
|
|
13
23
|
|
|
14
24
|
| Rule | Why |
|
|
15
25
|
| --- | --- |
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
| `
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
26
|
+
| Same-stem header | `LeaderstatsServer.h` + `LeaderstatsServer.server.cpp`. A differently named `leaderstats.h` is a `require`. |
|
|
27
|
+
| `.server.cpp` | Untagged cpp is a ModuleScript; `init()` will not run by itself. |
|
|
28
|
+
| `Paths.Currencies.Coins` | After `Init`. A local `int Coins` is not a Data path. |
|
|
29
|
+
| `string_concat` or string `+` | Luau `..`. Do not write `..` in the `.cpp`. |
|
|
30
|
+
| Janitor keyed by player name | Leaving the game must `Destroy` the folder. |
|
|
31
|
+
| Lambda or named function | `GetChangedSignal(...).Connect([coinsValue](int n) { ... })` is valid. |
|
|
22
32
|
|
|
23
|
-
##
|
|
33
|
+
## Header
|
|
24
34
|
|
|
25
35
|
```cpp
|
|
36
|
+
#pragma once
|
|
26
37
|
#include <cluaupp/roblox.hpp>
|
|
27
38
|
#include <cluaupp/libs/janitor.hpp>
|
|
28
39
|
#include <cluaupp/libs/dataservice.hpp>
|
|
29
|
-
#include <cluaupp/libs/formatnumber.hpp>
|
|
30
|
-
|
|
31
|
-
Players* Players = GetService<Players>();
|
|
32
|
-
Janitor* janitor = new Janitor();
|
|
33
|
-
|
|
34
|
-
void EnsureStat(Folder* leaderstats, string name) {
|
|
35
|
-
Instance* existing = leaderstats->FindFirstChild(name);
|
|
36
|
-
if (existing != nullptr) {
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
StringValue* stat = new StringValue(leaderstats);
|
|
40
|
-
stat->Name = name;
|
|
41
|
-
stat->Value = "0";
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
void SetStat(Folder* leaderstats, string name, int value) {
|
|
45
|
-
StringValue* stat = leaderstats->FindFirstChild(name);
|
|
46
|
-
if (stat == nullptr) {
|
|
47
|
-
cout::warn << "leaderstats missing " << name << endl;
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
stat->Value = FormatNumber::Abbreviate(value);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
Folder* EnsureLeaderstats(Player* player) {
|
|
54
|
-
Folder* leaderstats = player->FindFirstChild("leaderstats");
|
|
55
|
-
if (leaderstats != nullptr) {
|
|
56
|
-
return leaderstats;
|
|
57
|
-
}
|
|
58
|
-
leaderstats = new Folder(player);
|
|
59
|
-
leaderstats->Name = "leaderstats";
|
|
60
|
-
return leaderstats;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
void ApplyCurrencies(Player* player) {
|
|
64
|
-
Data* data = DataService::Server.Get(player);
|
|
65
|
-
if (data == nullptr) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
Folder* leaderstats = player->FindFirstChild("leaderstats");
|
|
69
|
-
if (leaderstats == nullptr) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
int money = data->Get(DataService::Server.Paths.Currencies.Money);
|
|
73
|
-
int level = data->Get(DataService::Server.Paths.Currencies.Level);
|
|
74
|
-
SetStat(leaderstats, "Money", money);
|
|
75
|
-
SetStat(leaderstats, "Level", level);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
void OnCurrenciesChanged() {
|
|
79
|
-
for (Player* player : Players->GetPlayers()) {
|
|
80
|
-
ApplyCurrencies(player);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
void SetupPlayer(Player* player) {
|
|
85
|
-
Data* data = DataService::Server.WaitFor(player);
|
|
86
|
-
if (data == nullptr) {
|
|
87
|
-
cout::warn << "no profile for " << player->Name << endl;
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
Folder* leaderstats = EnsureLeaderstats(player);
|
|
92
|
-
EnsureStat(leaderstats, "Money");
|
|
93
|
-
EnsureStat(leaderstats, "Level");
|
|
94
|
-
ApplyCurrencies(player);
|
|
95
40
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
41
|
+
struct LeaderstatsServer {
|
|
42
|
+
static constexpr int STARTING_COINS = 0;
|
|
43
|
+
Janitor* janitor;
|
|
44
|
+
string GetPlayerJanitorKey(Player* player);
|
|
45
|
+
void UpdateLeaderstatsWithValues(IntValue* currentValue, int newValue);
|
|
46
|
+
Folder* EnsurePlayerLeaderstatsFolder(Player* player);
|
|
47
|
+
void PlayerEntered(Player* player);
|
|
48
|
+
};
|
|
49
|
+
```
|
|
101
50
|
|
|
102
|
-
|
|
103
|
-
if (janitor->Get(player->Name)) {
|
|
104
|
-
janitor->Remove(player->Name);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
51
|
+
## `PlayerEntered`
|
|
107
52
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
53
|
+
```cpp
|
|
54
|
+
void LeaderstatsServer::PlayerEntered(Player* player) {
|
|
55
|
+
Folder* leaderstatsFolder = EnsurePlayerLeaderstatsFolder(player);
|
|
56
|
+
Data* playerData = DataService::Server.WaitFor(player);
|
|
57
|
+
IntValue* coinsValue = static_cast<IntValue*>(leaderstatsFolder->FindFirstChild("Coins"));
|
|
58
|
+
if (coinsValue) {
|
|
59
|
+
playerData->GetChangedSignal(DataService::Server.Paths.Currencies.Coins).Connect(
|
|
60
|
+
[coinsValue](int newValue) {
|
|
61
|
+
UpdateLeaderstatsWithValues(coinsValue, newValue);
|
|
62
|
+
}
|
|
63
|
+
);
|
|
115
64
|
}
|
|
116
|
-
janitor->Add(
|
|
117
|
-
janitor->Add(Players->PlayerRemoving.Connect(OnPlayerRemoving));
|
|
118
|
-
game->BindToClose(OnClose);
|
|
65
|
+
janitor->Add(leaderstatsFolder, "Destroy", GetPlayerJanitorKey(player));
|
|
119
66
|
}
|
|
120
67
|
```
|
|
121
68
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
`LeaderstatsServer.server.cpp` becomes a service folder: `init.luau` (Script, RunContext **Server**), `Main`, `PlayersManager`, `DataController` / `CacheController`, Types. Your functions stay in the domain controller. See [services](../oop/services.md).
|
|
125
|
-
|
|
126
|
-
## Bugs this example avoids
|
|
127
|
-
|
|
128
|
-
| Broken | Correct |
|
|
129
|
-
| --- | --- |
|
|
130
|
-
| `if (!existing) { return; }` then never create the value | `EnsureStat` **creates** when missing |
|
|
131
|
-
| `if (!money) { money->Value = ... }` | That writes only when the child is **nil** (crash / no-op). Set when the child **exists** |
|
|
132
|
-
| `DataService.Get` before `WaitFor` on join | `WaitFor` in `SetupPlayer`, `Get` in the refresh path |
|
|
133
|
-
| `int main()` | `void init()` |
|
|
134
|
-
| One global connection, never removed | Section janitor destroyed on `PlayerRemoving` |
|
|
135
|
-
|
|
136
|
-
`Paths.Currencies` is valid **after** your Template was passed to `Init`. The library header does not define those fields.
|
|
137
|
-
|
|
138
|
-
## IntValue vs StringValue
|
|
139
|
-
|
|
140
|
-
Use `IntValue` if you want the default numeric sort and no abbreviation. Use `StringValue` + `FormatNumber::Abbreviate` for `1.5K`. Do not mix both names (`Money` twice).
|
|
69
|
+
`init()` constructs `LeaderstatsServer`, assigns `janitor = new Janitor()`, runs `PlayerEntered` for everyone already in the game, then `PlayerAdded` + `BindToClose`. Full listing is on the [handbook page](https://kartzrbx.github.io/Cluaupp/docs/leaderstats.html).
|