redscript-mc 1.0.0 → 1.1.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/.github/ISSUE_TEMPLATE/bug_report.yml +72 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +57 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +17 -25
- package/CHANGELOG.md +58 -0
- package/CONTRIBUTING.md +140 -0
- package/README.md +28 -19
- package/README.zh.md +28 -19
- package/dist/__tests__/cli.test.js +10 -10
- package/dist/__tests__/codegen.test.js +1 -1
- package/dist/__tests__/diagnostics.test.js +5 -5
- package/dist/__tests__/e2e.test.js +146 -5
- package/dist/__tests__/formatter.test.d.ts +1 -0
- package/dist/__tests__/formatter.test.js +40 -0
- package/dist/__tests__/lowering.test.js +36 -3
- package/dist/__tests__/mc-integration.test.js +255 -10
- package/dist/__tests__/mc-syntax.test.js +3 -3
- package/dist/__tests__/nbt.test.js +2 -2
- package/dist/__tests__/optimizer-advanced.test.js +3 -3
- package/dist/__tests__/runtime.test.js +1 -1
- package/dist/ast/types.d.ts +21 -3
- package/dist/cli.js +25 -7
- package/dist/codegen/mcfunction/index.d.ts +1 -1
- package/dist/codegen/mcfunction/index.js +8 -2
- package/dist/codegen/structure/index.js +7 -1
- package/dist/formatter/index.d.ts +1 -0
- package/dist/formatter/index.js +26 -0
- package/dist/ir/builder.d.ts +2 -1
- package/dist/ir/types.d.ts +7 -2
- package/dist/ir/types.js +1 -1
- package/dist/lowering/index.d.ts +2 -0
- package/dist/lowering/index.js +183 -8
- package/dist/mc-test/runner.d.ts +2 -2
- package/dist/mc-test/runner.js +3 -3
- package/dist/mc-test/setup.js +2 -2
- package/dist/parser/index.d.ts +2 -0
- package/dist/parser/index.js +75 -7
- package/docs/COMPILATION_STATS.md +24 -24
- package/docs/IMPLEMENTATION_GUIDE.md +1 -1
- package/docs/STRUCTURE_TARGET.md +1 -1
- package/editors/vscode/.vscodeignore +1 -0
- package/editors/vscode/icons/mcrs.svg +7 -0
- package/editors/vscode/icons/redscript-icons.json +10 -0
- package/editors/vscode/out/extension.js +152 -9
- package/editors/vscode/package.json +10 -3
- package/editors/vscode/src/hover.ts +55 -2
- package/editors/vscode/src/symbols.ts +42 -0
- package/package.json +1 -1
- package/src/__tests__/cli.test.ts +10 -10
- package/src/__tests__/codegen.test.ts +1 -1
- package/src/__tests__/diagnostics.test.ts +5 -5
- package/src/__tests__/e2e.test.ts +134 -5
- package/src/__tests__/lowering.test.ts +48 -3
- package/src/__tests__/mc-integration.test.ts +285 -10
- package/src/__tests__/mc-syntax.test.ts +3 -3
- package/src/__tests__/nbt.test.ts +2 -2
- package/src/__tests__/optimizer-advanced.test.ts +3 -3
- package/src/__tests__/runtime.test.ts +1 -1
- package/src/ast/types.ts +20 -3
- package/src/cli.ts +10 -10
- package/src/codegen/mcfunction/index.ts +9 -2
- package/src/codegen/structure/index.ts +8 -1
- package/src/examples/capture_the_flag.mcrs +208 -0
- package/src/examples/{counter.rs → counter.mcrs} +1 -1
- package/src/examples/hunger_games.mcrs +301 -0
- package/src/examples/new_features_demo.mcrs +193 -0
- package/src/examples/parkour_race.mcrs +233 -0
- package/src/examples/rpg.mcrs +13 -0
- package/src/examples/{shop.rs → shop.mcrs} +1 -1
- package/src/examples/{showcase_game.rs → showcase_game.mcrs} +3 -3
- package/src/examples/{turret.rs → turret.mcrs} +1 -1
- package/src/examples/zombie_survival.mcrs +314 -0
- package/src/ir/builder.ts +3 -1
- package/src/ir/types.ts +8 -2
- package/src/lowering/index.ts +156 -8
- package/src/mc-test/runner.ts +3 -3
- package/src/mc-test/setup.ts +2 -2
- package/src/parser/index.ts +81 -8
- package/src/stdlib/README.md +155 -147
- package/src/stdlib/bossbar.mcrs +68 -0
- package/src/stdlib/{cooldown.rs → cooldown.mcrs} +1 -1
- package/src/stdlib/effects.mcrs +64 -0
- package/src/stdlib/interactions.mcrs +195 -0
- package/src/stdlib/inventory.mcrs +38 -0
- package/src/stdlib/mobs.mcrs +99 -0
- package/src/stdlib/particles.mcrs +52 -0
- package/src/stdlib/sets.mcrs +20 -0
- package/src/stdlib/spawn.mcrs +41 -0
- package/src/stdlib/teams.mcrs +68 -0
- package/src/stdlib/world.mcrs +92 -0
- package/src/examples/rpg.rs +0 -13
- package/src/stdlib/mobs.rs +0 -99
- /package/src/examples/{arena.rs → arena.mcrs} +0 -0
- /package/src/examples/{pvp_arena.rs → pvp_arena.mcrs} +0 -0
- /package/src/examples/{quiz.rs → quiz.mcrs} +0 -0
- /package/src/examples/{stdlib_demo.rs → stdlib_demo.mcrs} +0 -0
- /package/src/examples/{world_manager.rs → world_manager.mcrs} +0 -0
- /package/src/stdlib/{combat.rs → combat.mcrs} +0 -0
- /package/src/stdlib/{math.rs → math.mcrs} +0 -0
- /package/src/stdlib/{player.rs → player.mcrs} +0 -0
- /package/src/stdlib/{strings.rs → strings.mcrs} +0 -0
- /package/src/stdlib/{timer.rs → timer.mcrs} +0 -0
- /package/src/templates/{combat.rs → combat.mcrs} +0 -0
- /package/src/templates/{economy.rs → economy.mcrs} +0 -0
- /package/src/templates/{mini-game-framework.rs → mini-game-framework.mcrs} +0 -0
- /package/src/templates/{quest.rs → quest.mcrs} +0 -0
- /package/src/test_programs/{zombie_game.rs → zombie_game.mcrs} +0 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// Capture the Flag Mini-Game
|
|
3
|
+
// ============================================
|
|
4
|
+
// 场景:两队对抗,抢夺对方旗帜带回己方基地得分
|
|
5
|
+
// Scenario: Two teams compete to capture enemy flag
|
|
6
|
+
// ============================================
|
|
7
|
+
|
|
8
|
+
import "../stdlib/teams.mcrs"
|
|
9
|
+
import "../stdlib/effects.mcrs"
|
|
10
|
+
import "../stdlib/world.mcrs"
|
|
11
|
+
import "../stdlib/inventory.mcrs"
|
|
12
|
+
import "../stdlib/particles.mcrs"
|
|
13
|
+
|
|
14
|
+
// ===== 配置 =====
|
|
15
|
+
const RED_BASE_X: int = -50;
|
|
16
|
+
const RED_BASE_Z: int = 0;
|
|
17
|
+
const BLUE_BASE_X: int = 50;
|
|
18
|
+
const BLUE_BASE_Z: int = 0;
|
|
19
|
+
const BASE_Y: int = 64;
|
|
20
|
+
const WIN_SCORE: int = 3;
|
|
21
|
+
|
|
22
|
+
// ===== 游戏状态 =====
|
|
23
|
+
struct GameState {
|
|
24
|
+
running: int,
|
|
25
|
+
red_score: int,
|
|
26
|
+
blue_score: int,
|
|
27
|
+
red_flag_taken: int,
|
|
28
|
+
blue_flag_taken: int
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let game: GameState = GameState {
|
|
32
|
+
running: 0,
|
|
33
|
+
red_score: 0,
|
|
34
|
+
blue_score: 0,
|
|
35
|
+
red_flag_taken: 0,
|
|
36
|
+
blue_flag_taken: 0
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// ===== 初始化 =====
|
|
40
|
+
@load
|
|
41
|
+
fn init() {
|
|
42
|
+
// 创建记分板
|
|
43
|
+
scoreboard_add_objective("ctf_team", "dummy");
|
|
44
|
+
scoreboard_add_objective("ctf_flag", "dummy");
|
|
45
|
+
|
|
46
|
+
// 创建队伍
|
|
47
|
+
setup_two_teams();
|
|
48
|
+
|
|
49
|
+
// 世界设置
|
|
50
|
+
set_day();
|
|
51
|
+
weather_clear();
|
|
52
|
+
enable_keep_inventory();
|
|
53
|
+
|
|
54
|
+
announce("§e[CTF] §f夺旗战已加载!使用 /trigger start 开始游戏");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ===== 开始游戏 =====
|
|
58
|
+
fn start_game() {
|
|
59
|
+
game.running = 1;
|
|
60
|
+
game.red_score = 0;
|
|
61
|
+
game.blue_score = 0;
|
|
62
|
+
|
|
63
|
+
// 随机分配队伍
|
|
64
|
+
assign_teams();
|
|
65
|
+
|
|
66
|
+
// 传送到各自基地
|
|
67
|
+
tp(@a[team=red], RED_BASE_X, BASE_Y, RED_BASE_Z);
|
|
68
|
+
tp(@a[team=blue], BLUE_BASE_X, BASE_Y, BLUE_BASE_Z);
|
|
69
|
+
|
|
70
|
+
// 给装备
|
|
71
|
+
foreach (p in @a) {
|
|
72
|
+
clear_inventory(p);
|
|
73
|
+
give_kit_warrior(p);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 放置旗帜(盔甲架)
|
|
77
|
+
place_flags();
|
|
78
|
+
|
|
79
|
+
announce("§e[CTF] §a游戏开始!抢夺敌方旗帜!");
|
|
80
|
+
title(@a, "§6夺旗战开始!");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fn place_flags() {
|
|
84
|
+
// 红方旗帜(红色旗帜方块)
|
|
85
|
+
setblock(RED_BASE_X, BASE_Y + 1, RED_BASE_Z, "minecraft:red_banner");
|
|
86
|
+
// 蓝方旗帜
|
|
87
|
+
setblock(BLUE_BASE_X, BASE_Y + 1, BLUE_BASE_Z, "minecraft:blue_banner");
|
|
88
|
+
|
|
89
|
+
game.red_flag_taken = 0;
|
|
90
|
+
game.blue_flag_taken = 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
fn assign_teams() {
|
|
94
|
+
let count: int = 0;
|
|
95
|
+
foreach (p in @a) {
|
|
96
|
+
if (count % 2 == 0) {
|
|
97
|
+
team_join(p, "red");
|
|
98
|
+
scoreboard_set(p, "ctf_team", 1);
|
|
99
|
+
} else {
|
|
100
|
+
team_join(p, "blue");
|
|
101
|
+
scoreboard_set(p, "ctf_team", 2);
|
|
102
|
+
}
|
|
103
|
+
count = count + 1;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ===== 每 tick 检查 =====
|
|
108
|
+
@tick
|
|
109
|
+
fn game_tick() {
|
|
110
|
+
if (game.running == 1) {
|
|
111
|
+
check_flag_pickup();
|
|
112
|
+
check_flag_capture();
|
|
113
|
+
check_win();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
fn check_flag_pickup() {
|
|
118
|
+
// 检查蓝队玩家是否靠近红旗
|
|
119
|
+
foreach (p in @a[team=blue]) {
|
|
120
|
+
// 如果在红方基地附近且旗帜未被拿走
|
|
121
|
+
let dist: int = distance_to(p, RED_BASE_X, BASE_Y, RED_BASE_Z);
|
|
122
|
+
if (dist < 3) {
|
|
123
|
+
if (game.red_flag_taken == 0) {
|
|
124
|
+
game.red_flag_taken = 1;
|
|
125
|
+
tag_add(p, "has_flag");
|
|
126
|
+
setblock(RED_BASE_X, BASE_Y + 1, RED_BASE_Z, "minecraft:air");
|
|
127
|
+
announce("§e[CTF] §9蓝队 §f拿到了 §c红方旗帜§f!");
|
|
128
|
+
glow(p, 9999);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 检查红队玩家是否靠近蓝旗
|
|
134
|
+
foreach (p in @a[team=red]) {
|
|
135
|
+
let dist: int = distance_to(p, BLUE_BASE_X, BASE_Y, BLUE_BASE_Z);
|
|
136
|
+
if (dist < 3) {
|
|
137
|
+
if (game.blue_flag_taken == 0) {
|
|
138
|
+
game.blue_flag_taken = 1;
|
|
139
|
+
tag_add(p, "has_flag");
|
|
140
|
+
setblock(BLUE_BASE_X, BASE_Y + 1, BLUE_BASE_Z, "minecraft:air");
|
|
141
|
+
announce("§e[CTF] §c红队 §f拿到了 §9蓝方旗帜§f!");
|
|
142
|
+
glow(p, 9999);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
fn check_flag_capture() {
|
|
149
|
+
// 蓝队带红旗回蓝方基地
|
|
150
|
+
foreach (p in @a[team=blue, tag=has_flag]) {
|
|
151
|
+
let dist: int = distance_to(p, BLUE_BASE_X, BASE_Y, BLUE_BASE_Z);
|
|
152
|
+
if (dist < 5) {
|
|
153
|
+
game.blue_score = game.blue_score + 1;
|
|
154
|
+
announce("§e[CTF] §9蓝队 §a得分!§f (" + game.blue_score + "/" + WIN_SCORE + ")");
|
|
155
|
+
tag_remove(p, "has_flag");
|
|
156
|
+
effect_clear(p, "minecraft:glowing");
|
|
157
|
+
place_flags();
|
|
158
|
+
happy(p);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 红队带蓝旗回红方基地
|
|
163
|
+
foreach (p in @a[team=red, tag=has_flag]) {
|
|
164
|
+
let dist: int = distance_to(p, RED_BASE_X, BASE_Y, RED_BASE_Z);
|
|
165
|
+
if (dist < 5) {
|
|
166
|
+
game.red_score = game.red_score + 1;
|
|
167
|
+
announce("§e[CTF] §c红队 §a得分!§f (" + game.red_score + "/" + WIN_SCORE + ")");
|
|
168
|
+
tag_remove(p, "has_flag");
|
|
169
|
+
effect_clear(p, "minecraft:glowing");
|
|
170
|
+
place_flags();
|
|
171
|
+
happy(p);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
fn check_win() {
|
|
177
|
+
if (game.red_score >= WIN_SCORE) {
|
|
178
|
+
end_game("red");
|
|
179
|
+
}
|
|
180
|
+
if (game.blue_score >= WIN_SCORE) {
|
|
181
|
+
end_game("blue");
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
fn end_game(winner: string) {
|
|
186
|
+
game.running = 0;
|
|
187
|
+
|
|
188
|
+
if (winner == "red") {
|
|
189
|
+
title(@a, "§c红队获胜!");
|
|
190
|
+
announce("§e[CTF] §c红队 §6赢得了比赛!");
|
|
191
|
+
} else {
|
|
192
|
+
title(@a, "§9蓝队获胜!");
|
|
193
|
+
announce("§e[CTF] §9蓝队 §6赢得了比赛!");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// 清理
|
|
197
|
+
foreach (p in @a[tag=has_flag]) {
|
|
198
|
+
tag_remove(p, "has_flag");
|
|
199
|
+
effect_clear(p);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ===== 辅助函数 =====
|
|
204
|
+
fn distance_to(target: selector, x: int, y: int, z: int) -> int {
|
|
205
|
+
// 简化的距离计算(使用记分板)
|
|
206
|
+
let dist: int = scoreboard_get(target, "ctf_dist");
|
|
207
|
+
return dist;
|
|
208
|
+
}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// Hunger Games - 饥饿游戏
|
|
3
|
+
// ============================================
|
|
4
|
+
// 场景:玩家从中央补给点开始,搜刮物资生存
|
|
5
|
+
// 边界逐渐缩小,最后存活者获胜
|
|
6
|
+
// Scenario: Battle royale with shrinking border
|
|
7
|
+
// ============================================
|
|
8
|
+
|
|
9
|
+
import "../stdlib/effects.mcrs"
|
|
10
|
+
import "../stdlib/world.mcrs"
|
|
11
|
+
import "../stdlib/bossbar.mcrs"
|
|
12
|
+
import "../stdlib/particles.mcrs"
|
|
13
|
+
|
|
14
|
+
// ===== 配置 =====
|
|
15
|
+
const CENTER_X: int = 0;
|
|
16
|
+
const CENTER_Y: int = 64;
|
|
17
|
+
const CENTER_Z: int = 0;
|
|
18
|
+
const INITIAL_BORDER: int = 200;
|
|
19
|
+
const FINAL_BORDER: int = 20;
|
|
20
|
+
const GRACE_PERIOD: int = 600; // 30秒无敌时间
|
|
21
|
+
const SHRINK_INTERVAL: int = 1200; // 每60秒缩圈
|
|
22
|
+
|
|
23
|
+
// ===== 游戏状态 =====
|
|
24
|
+
struct HGState {
|
|
25
|
+
running: int,
|
|
26
|
+
phase: int, // 0=等待, 1=倒计时, 2=游戏中, 3=结束
|
|
27
|
+
countdown: int,
|
|
28
|
+
grace_timer: int,
|
|
29
|
+
border_timer: int,
|
|
30
|
+
current_border: int,
|
|
31
|
+
alive_count: int
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let game: HGState = HGState {
|
|
35
|
+
running: 0,
|
|
36
|
+
phase: 0,
|
|
37
|
+
countdown: 0,
|
|
38
|
+
grace_timer: 0,
|
|
39
|
+
border_timer: 0,
|
|
40
|
+
current_border: INITIAL_BORDER,
|
|
41
|
+
alive_count: 0
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
@load
|
|
45
|
+
fn init() {
|
|
46
|
+
scoreboard_add_objective("hg_kills", "dummy");
|
|
47
|
+
scoreboard_add_objective("hg_alive", "dummy");
|
|
48
|
+
|
|
49
|
+
// 设置世界边界
|
|
50
|
+
// worldborder center 0 0
|
|
51
|
+
// worldborder set 200
|
|
52
|
+
|
|
53
|
+
announce("§c[饥饿游戏] §f已加载!等待玩家加入...");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ===== 开始游戏 =====
|
|
57
|
+
fn start_countdown() {
|
|
58
|
+
game.phase = 1;
|
|
59
|
+
game.countdown = 200; // 10秒倒计时
|
|
60
|
+
|
|
61
|
+
// 传送所有玩家到中央
|
|
62
|
+
foreach (p in @a) {
|
|
63
|
+
tp(p, CENTER_X, CENTER_Y + 50, CENTER_Z); // 高空开始
|
|
64
|
+
scoreboard_set(p, "hg_kills", 0);
|
|
65
|
+
scoreboard_set(p, "hg_alive", 1);
|
|
66
|
+
|
|
67
|
+
// 清空背包
|
|
68
|
+
clear(p);
|
|
69
|
+
|
|
70
|
+
// 冻结玩家
|
|
71
|
+
effect(p, "minecraft:slowness", 15, 255);
|
|
72
|
+
effect(p, "minecraft:jump_boost", 15, 250); // 防止跳跃
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 创建边界 bossbar
|
|
76
|
+
create_progress_bar("hg_border", "§c边界缩小中", INITIAL_BORDER);
|
|
77
|
+
|
|
78
|
+
announce("§c[饥饿游戏] §e游戏即将开始!");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
fn start_game() {
|
|
82
|
+
game.phase = 2;
|
|
83
|
+
game.running = 1;
|
|
84
|
+
game.grace_timer = GRACE_PERIOD;
|
|
85
|
+
game.border_timer = SHRINK_INTERVAL;
|
|
86
|
+
game.current_border = INITIAL_BORDER;
|
|
87
|
+
|
|
88
|
+
// 计算存活人数
|
|
89
|
+
game.alive_count = 0;
|
|
90
|
+
foreach (p in @a) {
|
|
91
|
+
game.alive_count = game.alive_count + 1;
|
|
92
|
+
|
|
93
|
+
// 清除效果
|
|
94
|
+
effect_clear(p);
|
|
95
|
+
|
|
96
|
+
// 缓降
|
|
97
|
+
slow_fall(p, 100);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
title(@a, "§c游戏开始!");
|
|
101
|
+
subtitle(@a, "§7搜刮物资,成为最后的生存者!");
|
|
102
|
+
|
|
103
|
+
announce("§c[饥饿游戏] §a游戏开始!§730秒内无法互相伤害");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ===== 每 tick =====
|
|
107
|
+
@tick
|
|
108
|
+
fn game_tick() {
|
|
109
|
+
if (game.phase == 1) {
|
|
110
|
+
countdown_tick();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (game.phase == 2) {
|
|
114
|
+
main_game_tick();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
fn countdown_tick() {
|
|
119
|
+
game.countdown = game.countdown - 1;
|
|
120
|
+
|
|
121
|
+
// 倒计时提示
|
|
122
|
+
if (game.countdown == 100) {
|
|
123
|
+
title(@a, "§e5");
|
|
124
|
+
playsound("minecraft:block.note_block.pling", "player", @a);
|
|
125
|
+
}
|
|
126
|
+
if (game.countdown == 80) {
|
|
127
|
+
title(@a, "§e4");
|
|
128
|
+
playsound("minecraft:block.note_block.pling", "player", @a);
|
|
129
|
+
}
|
|
130
|
+
if (game.countdown == 60) {
|
|
131
|
+
title(@a, "§e3");
|
|
132
|
+
playsound("minecraft:block.note_block.pling", "player", @a);
|
|
133
|
+
}
|
|
134
|
+
if (game.countdown == 40) {
|
|
135
|
+
title(@a, "§c2");
|
|
136
|
+
playsound("minecraft:block.note_block.pling", "player", @a);
|
|
137
|
+
}
|
|
138
|
+
if (game.countdown == 20) {
|
|
139
|
+
title(@a, "§c1");
|
|
140
|
+
playsound("minecraft:block.note_block.pling", "player", @a);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (game.countdown <= 0) {
|
|
144
|
+
start_game();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
fn main_game_tick() {
|
|
149
|
+
// 无敌时间
|
|
150
|
+
if (game.grace_timer > 0) {
|
|
151
|
+
game.grace_timer = game.grace_timer - 1;
|
|
152
|
+
|
|
153
|
+
if (game.grace_timer == 0) {
|
|
154
|
+
announce("§c[饥饿游戏] §4无敌时间结束!可以开始战斗!");
|
|
155
|
+
title(@a, "§c战斗开始!");
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 边界缩小
|
|
160
|
+
game.border_timer = game.border_timer - 1;
|
|
161
|
+
if (game.border_timer <= 0) {
|
|
162
|
+
shrink_border();
|
|
163
|
+
game.border_timer = SHRINK_INTERVAL;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 更新 bossbar
|
|
167
|
+
update_bar("hg_border", game.current_border);
|
|
168
|
+
|
|
169
|
+
// 检查边界伤害
|
|
170
|
+
check_border_damage();
|
|
171
|
+
|
|
172
|
+
// 检查存活人数
|
|
173
|
+
check_alive();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
fn shrink_border() {
|
|
177
|
+
if (game.current_border > FINAL_BORDER) {
|
|
178
|
+
game.current_border = game.current_border - 20;
|
|
179
|
+
|
|
180
|
+
if (game.current_border < FINAL_BORDER) {
|
|
181
|
+
game.current_border = FINAL_BORDER;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// worldborder set game.current_border 60
|
|
185
|
+
announce("§c[饥饿游戏] §e边界正在缩小!当前大小:" + game.current_border);
|
|
186
|
+
|
|
187
|
+
// 更新 bossbar
|
|
188
|
+
bossbar_set_max("hg_border", INITIAL_BORDER);
|
|
189
|
+
bossbar_set_value("hg_border", game.current_border);
|
|
190
|
+
|
|
191
|
+
// 颜色变化
|
|
192
|
+
if (game.current_border < 50) {
|
|
193
|
+
bossbar_set_color("hg_border", "red");
|
|
194
|
+
} else {
|
|
195
|
+
if (game.current_border < 100) {
|
|
196
|
+
bossbar_set_color("hg_border", "yellow");
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
fn check_border_damage() {
|
|
203
|
+
// 对边界外的玩家造成伤害
|
|
204
|
+
// 需要检查玩家是否在边界外
|
|
205
|
+
foreach (p in @a) {
|
|
206
|
+
// 简化:使用 execute positioned
|
|
207
|
+
let dist: int = 0; // 需要计算距离中心的距离
|
|
208
|
+
// 如果超出边界,造成伤害
|
|
209
|
+
// effect(p, "minecraft:wither", 1, 0);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
fn check_alive() {
|
|
214
|
+
let alive: int = 0;
|
|
215
|
+
let winner: selector = @a;
|
|
216
|
+
|
|
217
|
+
foreach (p in @a[gamemode=survival]) {
|
|
218
|
+
let is_alive: int = scoreboard_get(p, "hg_alive");
|
|
219
|
+
if (is_alive == 1) {
|
|
220
|
+
alive = alive + 1;
|
|
221
|
+
winner = p;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
game.alive_count = alive;
|
|
226
|
+
|
|
227
|
+
// 更新记分板显示
|
|
228
|
+
actionbar(@a, "§c存活: " + alive + " 人");
|
|
229
|
+
|
|
230
|
+
// 检查胜利
|
|
231
|
+
if (alive <= 1) {
|
|
232
|
+
if (alive == 1) {
|
|
233
|
+
declare_winner(winner);
|
|
234
|
+
} else {
|
|
235
|
+
// 无人存活
|
|
236
|
+
end_game_no_winner();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
fn declare_winner(winner: selector) {
|
|
242
|
+
game.phase = 3;
|
|
243
|
+
game.running = 0;
|
|
244
|
+
|
|
245
|
+
let kills: int = scoreboard_get(winner, "hg_kills");
|
|
246
|
+
|
|
247
|
+
title(@a, "§6胜利者!");
|
|
248
|
+
announce("§c[饥饿游戏] §6游戏结束!");
|
|
249
|
+
announce("§a胜利者击杀数:" + kills);
|
|
250
|
+
|
|
251
|
+
// 胜利特效
|
|
252
|
+
totem_effect(winner);
|
|
253
|
+
buff_all(winner, 200);
|
|
254
|
+
|
|
255
|
+
remove_bar("hg_border");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
fn end_game_no_winner() {
|
|
259
|
+
game.phase = 3;
|
|
260
|
+
game.running = 0;
|
|
261
|
+
|
|
262
|
+
title(@a, "§7游戏结束");
|
|
263
|
+
subtitle(@a, "§c无人存活");
|
|
264
|
+
|
|
265
|
+
remove_bar("hg_border");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ===== 玩家死亡处理 =====
|
|
269
|
+
fn on_player_death(player: selector, killer: selector) {
|
|
270
|
+
scoreboard_set(player, "hg_alive", 0);
|
|
271
|
+
|
|
272
|
+
// 更新击杀数
|
|
273
|
+
scoreboard_add(killer, "hg_kills", 1);
|
|
274
|
+
|
|
275
|
+
// 切换观察模式
|
|
276
|
+
// gamemode spectator player
|
|
277
|
+
|
|
278
|
+
let kills: int = scoreboard_get(killer, "hg_kills");
|
|
279
|
+
announce("§c☠ §7玩家被击杀!击杀者已有 " + kills + " 杀");
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ===== 重置游戏 =====
|
|
283
|
+
fn reset_game() {
|
|
284
|
+
game.phase = 0;
|
|
285
|
+
game.running = 0;
|
|
286
|
+
|
|
287
|
+
foreach (p in @a) {
|
|
288
|
+
scoreboard_set(p, "hg_kills", 0);
|
|
289
|
+
scoreboard_set(p, "hg_alive", 0);
|
|
290
|
+
effect_clear(p);
|
|
291
|
+
// gamemode survival p
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// 重置边界
|
|
295
|
+
game.current_border = INITIAL_BORDER;
|
|
296
|
+
// worldborder set 200
|
|
297
|
+
|
|
298
|
+
remove_bar("hg_border");
|
|
299
|
+
|
|
300
|
+
announce("§c[饥饿游戏] §7游戏已重置");
|
|
301
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RedScript New Features Demo
|
|
3
|
+
* 展示 2026-03-12 新增的语言特性
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ============================================
|
|
7
|
+
// 1. 类型推断 (Type Inference)
|
|
8
|
+
// ============================================
|
|
9
|
+
fn type_inference_demo() {
|
|
10
|
+
// 不需要写 `: int`,编译器自动推断
|
|
11
|
+
let health = 100;
|
|
12
|
+
let name = "Steve";
|
|
13
|
+
let alive = true;
|
|
14
|
+
let speed = 1.5;
|
|
15
|
+
|
|
16
|
+
// NBT 后缀也能推断
|
|
17
|
+
let damage = 20b; // byte
|
|
18
|
+
let distance = 1000s; // short
|
|
19
|
+
let bignum = 999999L; // long
|
|
20
|
+
let precise = 3.14d; // double
|
|
21
|
+
|
|
22
|
+
say("Health: ${health}, Name: ${name}");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ============================================
|
|
26
|
+
// 2. for-range 循环 (For-Range Loops)
|
|
27
|
+
// ============================================
|
|
28
|
+
fn for_range_demo() {
|
|
29
|
+
// 从 0 到 9 循环
|
|
30
|
+
for i in 0..10 {
|
|
31
|
+
say("Count: ${i}");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 可以用于倒计时
|
|
35
|
+
for sec in 0..5 {
|
|
36
|
+
title(@a, "Starting in ${sec}...");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ============================================
|
|
41
|
+
// 3. NBT 结构化参数 (NBT Structured Params)
|
|
42
|
+
// ============================================
|
|
43
|
+
fn nbt_params_demo() {
|
|
44
|
+
// 给予带 NBT 的物品
|
|
45
|
+
give(@s, "minecraft:diamond_sword", 1, {
|
|
46
|
+
display: { Name: "Excalibur" },
|
|
47
|
+
Enchantments: [
|
|
48
|
+
{ id: "minecraft:sharpness", lvl: 5 },
|
|
49
|
+
{ id: "minecraft:unbreaking", lvl: 3 }
|
|
50
|
+
]
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// 召唤带属性的实体
|
|
54
|
+
summon("minecraft:zombie", @s, {
|
|
55
|
+
CustomName: "Boss Zombie",
|
|
56
|
+
Health: 100.0,
|
|
57
|
+
Attributes: [
|
|
58
|
+
{ Name: "generic.max_health", Base: 100.0 }
|
|
59
|
+
]
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ============================================
|
|
64
|
+
// 4. Set 数据结构 (Runtime Set)
|
|
65
|
+
// ============================================
|
|
66
|
+
fn set_demo() {
|
|
67
|
+
// 创建集合
|
|
68
|
+
let visited = set_new();
|
|
69
|
+
|
|
70
|
+
// 使用方法语法添加元素
|
|
71
|
+
visited.add("spawn");
|
|
72
|
+
visited.add("castle");
|
|
73
|
+
visited.add("dungeon");
|
|
74
|
+
|
|
75
|
+
// 检查是否存在
|
|
76
|
+
if (visited.contains("castle")) {
|
|
77
|
+
say("You've been to the castle!");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 删除元素
|
|
81
|
+
visited.remove("spawn");
|
|
82
|
+
|
|
83
|
+
// 清空集合
|
|
84
|
+
visited.clear();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ============================================
|
|
88
|
+
// 5. 方法语法糖 (Method Syntax Sugar)
|
|
89
|
+
// ============================================
|
|
90
|
+
fn method_syntax_demo() {
|
|
91
|
+
let items: string[] = [];
|
|
92
|
+
|
|
93
|
+
// obj.method(args) → method(obj, args)
|
|
94
|
+
items.push("sword");
|
|
95
|
+
items.push("shield");
|
|
96
|
+
items.push("potion");
|
|
97
|
+
|
|
98
|
+
let count = items.len();
|
|
99
|
+
say("You have ${count} items");
|
|
100
|
+
|
|
101
|
+
// Set 也可以用方法语法
|
|
102
|
+
let tags = set_new();
|
|
103
|
+
tags.add("vip");
|
|
104
|
+
tags.add("admin");
|
|
105
|
+
|
|
106
|
+
if (tags.contains("admin")) {
|
|
107
|
+
say("Welcome, admin!");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ============================================
|
|
112
|
+
// 6. #mc_name 语法 (MC Identifier Syntax)
|
|
113
|
+
// ============================================
|
|
114
|
+
fn mc_name_demo() {
|
|
115
|
+
// #name 编译为裸 MC 名称(不带引号)
|
|
116
|
+
scoreboard_set(@s, #kills, 0);
|
|
117
|
+
scoreboard_add(@s, #deaths, 1);
|
|
118
|
+
|
|
119
|
+
let score = scoreboard_get(@s, #points);
|
|
120
|
+
|
|
121
|
+
// 用于 tag
|
|
122
|
+
tag_add(@s, #vip);
|
|
123
|
+
|
|
124
|
+
// 用于 team
|
|
125
|
+
team_join(@s, #red);
|
|
126
|
+
|
|
127
|
+
// 对比:字符串仍需引号
|
|
128
|
+
give(@s, "minecraft:diamond", 1);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ============================================
|
|
132
|
+
// 7. 块注释 (Block Comments)
|
|
133
|
+
// ============================================
|
|
134
|
+
|
|
135
|
+
/*
|
|
136
|
+
* 这是块注释
|
|
137
|
+
* 可以跨越多行
|
|
138
|
+
*/
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 这是文档注释
|
|
142
|
+
* @param player 目标玩家
|
|
143
|
+
*/
|
|
144
|
+
fn documented_function() {
|
|
145
|
+
say("Hello!");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ============================================
|
|
149
|
+
// 综合示例:简单游戏
|
|
150
|
+
// ============================================
|
|
151
|
+
|
|
152
|
+
struct Player {
|
|
153
|
+
score: int,
|
|
154
|
+
level: int,
|
|
155
|
+
visited: string // set ID
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
fn init_player() {
|
|
159
|
+
let visited_set = set_new();
|
|
160
|
+
let p: Player = {
|
|
161
|
+
score: 0,
|
|
162
|
+
level: 1,
|
|
163
|
+
visited: visited_set
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// 记录出生点(使用 set_add,因为 visited 是 string ID)
|
|
167
|
+
set_add(p.visited, "spawn");
|
|
168
|
+
|
|
169
|
+
scoreboard_set(@s, #score, p.score);
|
|
170
|
+
scoreboard_set(@s, #level, p.level);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
@tick(rate=20)
|
|
174
|
+
fn game_tick() {
|
|
175
|
+
// 每秒检查一次
|
|
176
|
+
for i in 0..1 {
|
|
177
|
+
let score = scoreboard_get(@s, #score);
|
|
178
|
+
if (score >= 100) {
|
|
179
|
+
title(@s, "Level Up!");
|
|
180
|
+
scoreboard_set(@s, #level, 2);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
fn reward_player(amount: int) {
|
|
186
|
+
// 类型推断 + NBT 参数
|
|
187
|
+
let bonus = amount * 2;
|
|
188
|
+
scoreboard_add(@s, #score, bonus);
|
|
189
|
+
|
|
190
|
+
give(@s, "minecraft:gold_ingot", amount, {
|
|
191
|
+
display: { Name: "Reward Gold" }
|
|
192
|
+
});
|
|
193
|
+
}
|