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
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: HUD
|
|
3
|
+
sidebar_position: 6
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# HUD
|
|
7
|
+
|
|
8
|
+
Client-only: wait for the replicated profile, write labels, listen for currency changes. No `Set` on persisted paths from the client.
|
|
9
|
+
|
|
10
|
+
Put a ScreenGui named `Hud` in StarterGui with `MoneyLabel` and `LevelLabel` (`TextLabel`).
|
|
11
|
+
|
|
12
|
+
Define callbacks **above** `init()` so clangd and the subset both see them (no lambdas).
|
|
13
|
+
|
|
14
|
+
## `HudClient.client.cpp`
|
|
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
|
+
}
|
|
61
|
+
|
|
62
|
+
void init() {
|
|
63
|
+
Player* player = Players->LocalPlayer;
|
|
64
|
+
if (player == nullptr) {
|
|
65
|
+
return;
|
|
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;
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
TextLabel* moneyLabel = FindLabel(playerGui, "MoneyLabel");
|
|
79
|
+
TextLabel* levelLabel = FindLabel(playerGui, "LevelLabel");
|
|
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));
|
|
87
|
+
}
|
|
88
|
+
```
|
|
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`).
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Examples
|
|
3
|
+
sidebar_position: 1
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Examples
|
|
7
|
+
|
|
8
|
+
High-quality Cluaupp systems you can copy. Each page is a full service: C++ that the subset actually compiles, server authority, Janitor cleanup, and the file names Studio expects.
|
|
9
|
+
|
|
10
|
+
These are **not** dumps of `int main()`. Entry is `void init()`. There are no lambdas and no custom C++ classes — named functions + structs.
|
|
11
|
+
|
|
12
|
+
## Suggested layout
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
src/
|
|
16
|
+
shared/constants/TemplateData.hpp
|
|
17
|
+
shared/constants/CombatConfig.hpp
|
|
18
|
+
shared/constants/ShopCatalog.hpp
|
|
19
|
+
server/boot/DataBoot.server.cpp
|
|
20
|
+
client/boot/DataBoot.client.cpp
|
|
21
|
+
server/services/leaderstats/LeaderstatsServer.server.cpp
|
|
22
|
+
server/services/combat/CombatServer.server.cpp
|
|
23
|
+
client/controllers/combat/CombatClient.client.cpp
|
|
24
|
+
server/services/shop/ShopServer.server.cpp
|
|
25
|
+
client/controllers/shop/ShopClient.client.cpp
|
|
26
|
+
client/controllers/hud/HudClient.client.cpp
|
|
27
|
+
server/services/weapons/SwordServer.server.cpp
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Boot DataService **once**. Other services `WaitFor` after that.
|
|
31
|
+
|
|
32
|
+
## Catalog
|
|
33
|
+
|
|
34
|
+
| Example | Teaches |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| [Data boot](data-boot.md) | `Server.Init` / `Client.Init`, your Template, not `main()` |
|
|
37
|
+
| [Leaderstats](leaderstats.md) | Folder `leaderstats`, FormatNumber, `GetChangedSignal`, per-player Janitor |
|
|
38
|
+
| [Combat (server validation)](combat.md) | Client sends **intent**; server checks range, cooldown, health, then `TakeDamage` |
|
|
39
|
+
| [Shop](shop.md) | `Net` buy remote, prices on the server, DataService debit |
|
|
40
|
+
| [HUD](hud.md) | Client `WaitForData`, labels, Twinkle |
|
|
41
|
+
| [Sword / Touched](sword.md) | Hitbox on the **server**, debounce, no client damage |
|
|
42
|
+
|
|
43
|
+
Read with [Libraries](../libraries/index.md), [OOP structure](../oop/index.md), and [Safety](../cpp-safety.md).
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Leaderstats
|
|
3
|
+
sidebar_position: 3
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Leaderstats
|
|
7
|
+
|
|
8
|
+
Roblox shows the player list from a Folder named exactly `leaderstats` under the Player, with `IntValue` / `StringValue` children. This service **creates** those values, then mirrors DataService currencies into them.
|
|
9
|
+
|
|
10
|
+
It does **not** call `DataService.Init`. Boot that in [Data boot](data-boot.md).
|
|
11
|
+
|
|
12
|
+
## Why this shape
|
|
13
|
+
|
|
14
|
+
| Rule | Why |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| Create the Folder if missing | `FindFirstChild` is not a constructor |
|
|
17
|
+
| Create the stat if missing | Returning early when it is absent never shows Money |
|
|
18
|
+
| `StringValue` + `FormatNumber::Abbreviate` | Player list wants a string like `1.5K` |
|
|
19
|
+
| Janitor per player, keyed by `player->Name` | Leaving the game must `Destroy` the section janitor |
|
|
20
|
+
| `GetChangedSignal(Paths.Currencies)` | HUD / list update without polling |
|
|
21
|
+
| Named function, not a lambda | Cluaupp has no lambdas. The shared callback refreshes **all** players (currency writes are rare) |
|
|
22
|
+
|
|
23
|
+
## `LeaderstatsServer.server.cpp`
|
|
24
|
+
|
|
25
|
+
```cpp
|
|
26
|
+
#include <cluaupp/roblox.hpp>
|
|
27
|
+
#include <cluaupp/libs/janitor.hpp>
|
|
28
|
+
#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
|
+
|
|
96
|
+
Janitor* section = new Janitor();
|
|
97
|
+
section->Add(data->GetChangedSignal(DataService::Server.Paths.Currencies).Connect(OnCurrenciesChanged));
|
|
98
|
+
section->Add(leaderstats, "Destroy");
|
|
99
|
+
janitor->Add(section, "Destroy", player->Name);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
void OnPlayerRemoving(Player* player) {
|
|
103
|
+
if (janitor->Get(player->Name)) {
|
|
104
|
+
janitor->Remove(player->Name);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
void OnClose() {
|
|
109
|
+
janitor->Destroy();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
void init() {
|
|
113
|
+
for (Player* player : Players->GetPlayers()) {
|
|
114
|
+
SetupPlayer(player);
|
|
115
|
+
}
|
|
116
|
+
janitor->Add(Players->PlayerAdded.Connect(SetupPlayer));
|
|
117
|
+
janitor->Add(Players->PlayerRemoving.Connect(OnPlayerRemoving));
|
|
118
|
+
game->BindToClose(OnClose);
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Output
|
|
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).
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Shop
|
|
3
|
+
sidebar_position: 5
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Shop
|
|
7
|
+
|
|
8
|
+
The client sends **which product**. The server looks up the price, checks Money, then `Set`. Never let the client send the new balance or the price.
|
|
9
|
+
|
|
10
|
+
## Catalog header
|
|
11
|
+
|
|
12
|
+
`src/shared/constants/ShopCatalog.hpp`
|
|
13
|
+
|
|
14
|
+
Cluaupp has no maps. A function with early returns is the catalog.
|
|
15
|
+
|
|
16
|
+
```cpp
|
|
17
|
+
#pragma once
|
|
18
|
+
|
|
19
|
+
const int PRODUCT_HEALTH_PACK = 1;
|
|
20
|
+
const int PRODUCT_SPEED_TOME = 2;
|
|
21
|
+
|
|
22
|
+
int PriceOf(int productId) {
|
|
23
|
+
if (productId == PRODUCT_HEALTH_PACK) {
|
|
24
|
+
return 50;
|
|
25
|
+
}
|
|
26
|
+
if (productId == PRODUCT_SPEED_TOME) {
|
|
27
|
+
return 200;
|
|
28
|
+
}
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`PriceOf` lives in a header so the **server** is the copy that matters. The client may include it for UI labels, but a patched client cannot change what the server charges.
|
|
34
|
+
|
|
35
|
+
## Server — `ShopServer.server.cpp`
|
|
36
|
+
|
|
37
|
+
```cpp
|
|
38
|
+
#include <cluaupp/roblox.hpp>
|
|
39
|
+
#include <cluaupp/libs/janitor.hpp>
|
|
40
|
+
#include <cluaupp/libs/net.hpp>
|
|
41
|
+
#include <cluaupp/libs/dataservice.hpp>
|
|
42
|
+
#include "../../../shared/constants/ShopCatalog.hpp"
|
|
43
|
+
|
|
44
|
+
Janitor* janitor = new Janitor();
|
|
45
|
+
NetEvent* Buy = Net::Event("Buy");
|
|
46
|
+
|
|
47
|
+
void ApplyProduct(Player* player, int productId) {
|
|
48
|
+
Model* character = player->Character;
|
|
49
|
+
if (character == nullptr) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
Humanoid* humanoid = character->FindFirstChildOfClass("Humanoid");
|
|
53
|
+
if (humanoid == nullptr) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (productId == PRODUCT_HEALTH_PACK) {
|
|
57
|
+
humanoid->Health = humanoid->MaxHealth;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (productId == PRODUCT_SPEED_TOME) {
|
|
61
|
+
humanoid->WalkSpeed = 24;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
void OnBuy(Player* player, int productId) {
|
|
67
|
+
int price = PriceOf(productId);
|
|
68
|
+
if (price < 1) {
|
|
69
|
+
cout::warn << "unknown product " << productId << endl;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
Data* data = DataService::Server.WaitFor(player);
|
|
73
|
+
if (data == nullptr) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
int money = data->Get(DataService::Server.Paths.Currencies.Money);
|
|
77
|
+
if (money < price) {
|
|
78
|
+
cout::ping << player->Name << " cannot afford " << productId << endl;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
data->Set(DataService::Server.Paths.Currencies.Money, money - price);
|
|
82
|
+
ApplyProduct(player, productId);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
void init() {
|
|
86
|
+
Buy->On(OnBuy);
|
|
87
|
+
janitor->Add(Buy);
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Debit **before** (or atomically with) the effect. If you grant the item first and the `Set` fails, the player dupes.
|
|
92
|
+
|
|
93
|
+
## Client — `ShopClient.client.cpp`
|
|
94
|
+
|
|
95
|
+
```cpp
|
|
96
|
+
#include <cluaupp/roblox.hpp>
|
|
97
|
+
#include <cluaupp/libs/janitor.hpp>
|
|
98
|
+
#include <cluaupp/libs/net.hpp>
|
|
99
|
+
#include "../../../shared/constants/ShopCatalog.hpp"
|
|
100
|
+
|
|
101
|
+
Janitor* janitor = new Janitor();
|
|
102
|
+
NetEvent* Buy = Net::Event("Buy");
|
|
103
|
+
|
|
104
|
+
void OnHealthPackClicked() {
|
|
105
|
+
Buy->FireServer(PRODUCT_HEALTH_PACK);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
void init() {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Wire `OnHealthPackClicked` to a `TextButton->MouseButton1Click.Connect(OnHealthPackClicked)` when you have the GUI. The remote payload is only the product id.
|
|
114
|
+
|
|
115
|
+
## Rules
|
|
116
|
+
|
|
117
|
+
- `productId < 1` / unknown id → `PriceOf` returns `0` → reject.
|
|
118
|
+
- Do not `FireServer(price)` or `FireServer(newMoney)`.
|
|
119
|
+
- [Leaderstats](leaderstats.md) updates from `GetChangedSignal` after `Set`.
|
|
120
|
+
- Same Net name `"Buy"` on both sides.
|
|
121
|
+
|
|
122
|
+
See [Safety](../cpp-safety.md).
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Sword (Touched)
|
|
3
|
+
sidebar_position: 7
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Sword (Touched)
|
|
7
|
+
|
|
8
|
+
Melee that uses a **server** `Touched` on the tool handle. The client can play animations; it must not tell the server how much damage to apply.
|
|
9
|
+
|
|
10
|
+
Same authority as [combat](combat.md), without a Net remote: while the Tool is equipped, `Touched` runs on the server.
|
|
11
|
+
|
|
12
|
+
## Config
|
|
13
|
+
|
|
14
|
+
`src/shared/constants/SwordConfig.hpp`
|
|
15
|
+
|
|
16
|
+
```cpp
|
|
17
|
+
#pragma once
|
|
18
|
+
|
|
19
|
+
const double SWORD_COOLDOWN = 0.35;
|
|
20
|
+
const int SWORD_DAMAGE = 18;
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Script inside the Tool
|
|
24
|
+
|
|
25
|
+
Put the source next to the Tool so `script->Parent` is the `Tool`. Use **`.legacy.server.cpp`** so Cluaupp emits a 1:1 Script (no Main/Controller split) that Rojo can parent under `ClassicSword`.
|
|
26
|
+
|
|
27
|
+
`src/server/tools/Sword.legacy.server.cpp`
|
|
28
|
+
|
|
29
|
+
```cpp
|
|
30
|
+
#include <cluaupp/roblox.hpp>
|
|
31
|
+
#include "../../shared/constants/SwordConfig.hpp"
|
|
32
|
+
|
|
33
|
+
Players* Players = GetService<Players>();
|
|
34
|
+
Tool* tool = script->Parent;
|
|
35
|
+
|
|
36
|
+
void OnTouched(Instance* hit) {
|
|
37
|
+
if (hit == nullptr) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
Player* attacker = Players->GetPlayerFromCharacter(tool->Parent);
|
|
42
|
+
if (attacker == nullptr) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
NumberValue* last = attacker->FindFirstChild("LastSwordAt");
|
|
47
|
+
if (last != nullptr) {
|
|
48
|
+
if (tick() - last->Value < SWORD_COOLDOWN) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
Instance* model = hit->FindFirstAncestorOfClass("Model");
|
|
54
|
+
if (model == nullptr) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
Player* victim = Players->GetPlayerFromCharacter(model);
|
|
58
|
+
if (victim == nullptr) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (victim == attacker) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
Humanoid* humanoid = model->FindFirstChildOfClass("Humanoid");
|
|
66
|
+
if (humanoid == nullptr) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (humanoid->Health <= 0) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (last == nullptr) {
|
|
74
|
+
last = new NumberValue(attacker);
|
|
75
|
+
last->Name = "LastSwordAt";
|
|
76
|
+
}
|
|
77
|
+
last->Value = tick();
|
|
78
|
+
humanoid->TakeDamage(SWORD_DAMAGE);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
void init() {
|
|
82
|
+
BasePart* handle = tool->FindFirstChild("Handle");
|
|
83
|
+
if (handle == nullptr) {
|
|
84
|
+
cout::warn << "ClassicSword missing Handle" << endl;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
handle->Touched.Connect(OnTouched);
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`tool->Parent` is the Character while equipped, `nil` in the backpack — `GetPlayerFromCharacter` then fails and the swing is ignored. That is what you want.
|
|
92
|
+
|
|
93
|
+
## Why `.legacy`
|
|
94
|
+
|
|
95
|
+
A tagged `Sword.server.cpp` in `src/server/` becomes a service folder in ServerScriptService. A Tool Script must live **under the Tool**. Legacy keeps one file you can Rojo-map into `ClassicSword`.
|
|
96
|
+
|
|
97
|
+
## Rules
|
|
98
|
+
|
|
99
|
+
| Do | Do not |
|
|
100
|
+
| --- | --- |
|
|
101
|
+
| `TakeDamage(SWORD_DAMAGE)` on the server | `FireServer(damage)` from a LocalScript |
|
|
102
|
+
| Skip `victim == attacker` | Damage the owner when the handle clips their own parts |
|
|
103
|
+
| Cooldown on the attacker | Trust `Touched` rate (it fires every physics step) |
|
|
104
|
+
| `GetPlayerFromCharacter` | Treat every Humanoid as a player if you only want PvP |
|
|
105
|
+
|
|
106
|
+
Client: a LocalScript in the same Tool can play an AnimationTrack on `Activated`. It still must not replicate a damage number.
|
|
107
|
+
|
|
108
|
+
For ranged hits (mouse target → remote), use [combat](combat.md).
|
package/docs/getting-started.md
CHANGED
|
@@ -122,13 +122,15 @@ If a file defines `void init()`, Cluaupp calls `init()` at the end of the `.luau
|
|
|
122
122
|
|
|
123
123
|
## IntelliSense
|
|
124
124
|
|
|
125
|
-
`cluaupp init`
|
|
125
|
+
`cluaupp init` writes the C++ IntelliSense config and installs Microsoft `ms-vscode.cpptools` (via VSIX on Cursor). Then reload the window.
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
```bash
|
|
128
|
+
cluaupp intellisense
|
|
129
|
+
```
|
|
128
130
|
|
|
129
|
-
|
|
131
|
+
`#include <cluaupp/roblox.hpp>` at the top of each source file. The header is **not** compiled to Luau.
|
|
130
132
|
|
|
131
|
-
|
|
133
|
+
See [IntelliSense](intellisense.md).
|
|
132
134
|
|
|
133
135
|
## Watch
|
|
134
136
|
|
|
@@ -153,17 +155,21 @@ void init() {
|
|
|
153
155
|
}
|
|
154
156
|
```
|
|
155
157
|
|
|
156
|
-
Wally is optional. Fusion, Cmdr, DataServiceV2, and the rest ship **inside** `libs/` (full GitHub source). See [Libraries](libraries.md).
|
|
158
|
+
Wally is optional. Fusion, Cmdr, DataServiceV2, and the rest ship **inside** `libs/` (full GitHub source). See [Libraries](libraries/index.md) — start with [DataService Init](libraries/dataservice.md) and [Janitor](libraries/janitor.md).
|
|
157
159
|
|
|
158
|
-
See [Libraries](libraries.md), [types](cpp-types.md), [safety](cpp-safety.md).
|
|
160
|
+
See [Libraries](libraries/index.md), [OOP](oop/index.md), [types](cpp-types.md), [safety](cpp-safety.md).
|
|
159
161
|
|
|
160
162
|
## Next
|
|
161
163
|
|
|
162
164
|
- [C++ → Luau syntax](syntax.md)
|
|
165
|
+
- [print and cout](print-cout.md)
|
|
163
166
|
- [C++ types](cpp-types.md)
|
|
164
167
|
- [const](cpp-const.md)
|
|
165
168
|
- [Safety](cpp-safety.md)
|
|
166
169
|
- [Organization](cpp-organization.md)
|
|
167
170
|
- [Architecture](architecture.md)
|
|
171
|
+
- [OOP structure](oop/index.md)
|
|
172
|
+
- [Libraries](libraries/index.md)
|
|
173
|
+
- [Examples](examples/index.md)
|
|
168
174
|
- [Roblox API](roblox-api.md)
|
|
169
175
|
- [CLI](cli.md)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# IntelliSense
|
|
2
|
+
|
|
3
|
+
`cluaupp init` and `cluaupp intellisense` install the same setup that works in Cursor:
|
|
4
|
+
|
|
5
|
+
1. Microsoft [C/C++](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) (`ms-vscode.cpptools`)
|
|
6
|
+
2. `.vscode/c_cpp_properties.json` — LLVM `clang++`, C++20, `include/` + `src/`, `compile_commands.json`
|
|
7
|
+
3. Forced include of `include/cluaupp/roblox.hpp` so `GetService`, `Player`, `Janitor` complete
|
|
8
|
+
4. The Cluaupp completion engine (Roblox APIs + your `struct`s)
|
|
9
|
+
|
|
10
|
+
Cursor’s marketplace does not list `ms-vscode.cpptools`. Cluaupp downloads the official VSIX from [vscode-cpptools releases](https://github.com/microsoft/vscode-cpptools/releases) and runs `cursor --install-extension`.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
cluaupp intellisense
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Reload: Command Palette → **Developer: Reload Window**. Status bar `{} C++` should read **IntelliSense: Ready**.
|
|
19
|
+
|
|
20
|
+
`cluaupp build` / `watch` only refresh `compile_commands.json` (no download). They never keep deleted files in the compilation database.
|
|
21
|
+
|
|
22
|
+
## Config that ships
|
|
23
|
+
|
|
24
|
+
| File | Role |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| `.vscode/c_cpp_properties.json` | compiler path, include path, `compileCommands` |
|
|
27
|
+
| `.vscode/settings.json` | `C_Cpp.intelliSenseEngine: default`, `clangd.enable: false` |
|
|
28
|
+
| `.vscode/extensions.json` | recommends `ms-vscode.cpptools` |
|
|
29
|
+
| `compile_commands.json` | one entry per `src/` file |
|
|
30
|
+
|
|
31
|
+
`#include <cluaupp/roblox.hpp>` at the top of each source file. The header is not compiled to Luau.
|
package/docs/intro.md
CHANGED
|
@@ -7,7 +7,7 @@ sidebar_position: 1
|
|
|
7
7
|
|
|
8
8
|
**The definitive merge of C++ and modern Luau.** You write a C++ subset. Cluaupp emits [Luau](https://luau.org/getting-started) with `--!strict`, `local`, and `const`, and calls the Roblox API the way Studio does.
|
|
9
9
|
|
|
10
|
-
This site is built with [Moonwave](https://eryn.io/moonwave/) for local markdown
|
|
10
|
+
This site is built with [Moonwave](https://eryn.io/moonwave/) for local markdown (`npm run docs`). The **public** site is generated into `site/` and deployed to [GitHub Pages](https://kartzrbx.github.io/Cluaupp/) (Learn tabs, OOP, Examples, API). The language is in the same spirit as [roblox-ts](https://roblox-ts.com): a familiar syntax, a restricted subset, readable output.
|
|
11
11
|
|
|
12
12
|
## What you get
|
|
13
13
|
|
|
@@ -34,4 +34,4 @@ void init() {
|
|
|
34
34
|
}
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
Read [Getting started](getting-started.md), then [
|
|
37
|
+
Read [Getting started](getting-started.md), then [Examples](examples/index.md), [Libraries](libraries/index.md), and [OOP structure](oop/index.md).
|