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,233 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// Parkour Race - 跑酷竞速
|
|
3
|
+
// ============================================
|
|
4
|
+
// 场景:玩家从起点跑到终点,记录最快时间
|
|
5
|
+
// 设有多个检查点,掉落会传送回最近检查点
|
|
6
|
+
// Scenario: Race from start to finish with checkpoints
|
|
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 START_X: int = 0;
|
|
16
|
+
const START_Y: int = 64;
|
|
17
|
+
const START_Z: int = 0;
|
|
18
|
+
const FALL_Y: int = 50; // 低于此高度视为掉落
|
|
19
|
+
|
|
20
|
+
const CHECKPOINT_COUNT: int = 5;
|
|
21
|
+
|
|
22
|
+
// ===== 游戏状态 =====
|
|
23
|
+
// 记分板:
|
|
24
|
+
// - pk_time: 当前用时 (ticks)
|
|
25
|
+
// - pk_best: 最佳记录
|
|
26
|
+
// - pk_checkpoint: 当前检查点
|
|
27
|
+
// - pk_running: 是否在比赛中
|
|
28
|
+
|
|
29
|
+
// 检查点坐标 (简化为直线跑酷)
|
|
30
|
+
// CP0: 0,64,0 (起点)
|
|
31
|
+
// CP1: 0,64,50
|
|
32
|
+
// CP2: 0,70,100
|
|
33
|
+
// CP3: 0,75,150
|
|
34
|
+
// CP4: 0,80,200
|
|
35
|
+
// CP5: 0,64,250 (终点)
|
|
36
|
+
|
|
37
|
+
@load
|
|
38
|
+
fn init() {
|
|
39
|
+
scoreboard_add_objective("pk_time", "dummy");
|
|
40
|
+
scoreboard_add_objective("pk_best", "dummy");
|
|
41
|
+
scoreboard_add_objective("pk_checkpoint", "dummy");
|
|
42
|
+
scoreboard_add_objective("pk_running", "dummy");
|
|
43
|
+
|
|
44
|
+
// 显示最佳时间
|
|
45
|
+
scoreboard_display("sidebar", "pk_best");
|
|
46
|
+
|
|
47
|
+
set_day();
|
|
48
|
+
weather_clear();
|
|
49
|
+
|
|
50
|
+
announce("§b[跑酷] §f已加载!踩压力板开始");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ===== 开始比赛 =====
|
|
54
|
+
fn start_race(player: selector) {
|
|
55
|
+
// 检查是否已在比赛中
|
|
56
|
+
let running: int = scoreboard_get(player, "pk_running");
|
|
57
|
+
if (running == 1) {
|
|
58
|
+
tell(player, "§c你已经在比赛中了!");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
scoreboard_set(player, "pk_running", 1);
|
|
63
|
+
scoreboard_set(player, "pk_time", 0);
|
|
64
|
+
scoreboard_set(player, "pk_checkpoint", 0);
|
|
65
|
+
|
|
66
|
+
// 传送到起点
|
|
67
|
+
tp(player, START_X, START_Y, START_Z);
|
|
68
|
+
|
|
69
|
+
// 清除效果,给速度
|
|
70
|
+
effect_clear(player);
|
|
71
|
+
|
|
72
|
+
title(player, "§b开始!");
|
|
73
|
+
subtitle(player, "§7跑向终点!");
|
|
74
|
+
|
|
75
|
+
// 创建计时 bossbar
|
|
76
|
+
tell(player, "§b[跑酷] §a比赛开始!到达终点完成比赛");
|
|
77
|
+
|
|
78
|
+
sparkles(player);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ===== 每 tick =====
|
|
82
|
+
@tick
|
|
83
|
+
fn race_tick() {
|
|
84
|
+
// 对每个比赛中的玩家
|
|
85
|
+
foreach (p in @a) {
|
|
86
|
+
let running: int = scoreboard_get(p, "pk_running");
|
|
87
|
+
if (running == 1) {
|
|
88
|
+
// 增加时间
|
|
89
|
+
scoreboard_add(p, "pk_time", 1);
|
|
90
|
+
|
|
91
|
+
// 显示当前时间
|
|
92
|
+
let time: int = scoreboard_get(p, "pk_time");
|
|
93
|
+
let seconds: int = time / 20;
|
|
94
|
+
let ms: int = (time % 20) * 5;
|
|
95
|
+
actionbar(p, "§e⏱ " + seconds + "." + ms + " 秒");
|
|
96
|
+
|
|
97
|
+
// 检查掉落
|
|
98
|
+
check_fall(p);
|
|
99
|
+
|
|
100
|
+
// 检查检查点
|
|
101
|
+
check_checkpoints(p);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fn check_fall(player: selector) {
|
|
107
|
+
// 如果玩家低于 FALL_Y,传送回检查点
|
|
108
|
+
// 使用 execute positioned 检查
|
|
109
|
+
execute if entity player[y=..50] run {
|
|
110
|
+
let cp: int = scoreboard_get(player, "pk_checkpoint");
|
|
111
|
+
respawn_at_checkpoint(player, cp);
|
|
112
|
+
tell(player, "§c掉落!返回检查点 " + cp);
|
|
113
|
+
angry(player);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
fn check_checkpoints(player: selector) {
|
|
118
|
+
let current_cp: int = scoreboard_get(player, "pk_checkpoint");
|
|
119
|
+
|
|
120
|
+
// 检查点 1 (0, 64, 50)
|
|
121
|
+
if (current_cp == 0) {
|
|
122
|
+
execute if entity player[x=-2..2, y=62..68, z=48..52] run {
|
|
123
|
+
reach_checkpoint(player, 1);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 检查点 2 (0, 70, 100)
|
|
128
|
+
if (current_cp == 1) {
|
|
129
|
+
execute if entity player[x=-2..2, y=68..74, z=98..102] run {
|
|
130
|
+
reach_checkpoint(player, 2);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 检查点 3 (0, 75, 150)
|
|
135
|
+
if (current_cp == 2) {
|
|
136
|
+
execute if entity player[x=-2..2, y=73..79, z=148..152] run {
|
|
137
|
+
reach_checkpoint(player, 3);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 检查点 4 (0, 80, 200)
|
|
142
|
+
if (current_cp == 3) {
|
|
143
|
+
execute if entity player[x=-2..2, y=78..84, z=198..202] run {
|
|
144
|
+
reach_checkpoint(player, 4);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 终点 (0, 64, 250)
|
|
149
|
+
if (current_cp == 4) {
|
|
150
|
+
execute if entity player[x=-2..2, y=62..68, z=248..252] run {
|
|
151
|
+
finish_race(player);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
fn reach_checkpoint(player: selector, cp: int) {
|
|
157
|
+
scoreboard_set(player, "pk_checkpoint", cp);
|
|
158
|
+
|
|
159
|
+
title(player, "");
|
|
160
|
+
subtitle(player, "§a检查点 " + cp + " / " + CHECKPOINT_COUNT);
|
|
161
|
+
|
|
162
|
+
happy(player);
|
|
163
|
+
playsound("minecraft:entity.experience_orb.pickup", "player", player);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
fn respawn_at_checkpoint(player: selector, cp: int) {
|
|
167
|
+
if (cp == 0) {
|
|
168
|
+
tp(player, 0, 64, 0);
|
|
169
|
+
}
|
|
170
|
+
if (cp == 1) {
|
|
171
|
+
tp(player, 0, 64, 50);
|
|
172
|
+
}
|
|
173
|
+
if (cp == 2) {
|
|
174
|
+
tp(player, 0, 70, 100);
|
|
175
|
+
}
|
|
176
|
+
if (cp == 3) {
|
|
177
|
+
tp(player, 0, 75, 150);
|
|
178
|
+
}
|
|
179
|
+
if (cp == 4) {
|
|
180
|
+
tp(player, 0, 80, 200);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
fn finish_race(player: selector) {
|
|
185
|
+
scoreboard_set(player, "pk_running", 0);
|
|
186
|
+
|
|
187
|
+
let time: int = scoreboard_get(player, "pk_time");
|
|
188
|
+
let best: int = scoreboard_get(player, "pk_best");
|
|
189
|
+
let seconds: int = time / 20;
|
|
190
|
+
let ms: int = (time % 20) * 5;
|
|
191
|
+
|
|
192
|
+
// 检查是否是新纪录
|
|
193
|
+
if (best == 0) {
|
|
194
|
+
// 第一次完成
|
|
195
|
+
scoreboard_set(player, "pk_best", time);
|
|
196
|
+
title(player, "§6完成!");
|
|
197
|
+
subtitle(player, "§e" + seconds + "." + ms + " 秒 §7(首次记录)");
|
|
198
|
+
announce("§b[跑酷] §f玩家完成比赛!用时 §e" + seconds + "." + ms + " 秒");
|
|
199
|
+
} else {
|
|
200
|
+
if (time < best) {
|
|
201
|
+
// 新纪录!
|
|
202
|
+
scoreboard_set(player, "pk_best", time);
|
|
203
|
+
title(player, "§6新纪录!");
|
|
204
|
+
subtitle(player, "§e" + seconds + "." + ms + " 秒");
|
|
205
|
+
announce("§b[跑酷] §6新纪录!§f用时 §e" + seconds + "." + ms + " 秒");
|
|
206
|
+
totem_effect(player);
|
|
207
|
+
} else {
|
|
208
|
+
title(player, "§a完成!");
|
|
209
|
+
subtitle(player, "§e" + seconds + "." + ms + " 秒");
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// 特效
|
|
214
|
+
sparkles(player);
|
|
215
|
+
playsound("minecraft:ui.toast.challenge_complete", "player", player);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ===== 放弃比赛 =====
|
|
219
|
+
fn quit_race(player: selector) {
|
|
220
|
+
let running: int = scoreboard_get(player, "pk_running");
|
|
221
|
+
if (running == 1) {
|
|
222
|
+
scoreboard_set(player, "pk_running", 0);
|
|
223
|
+
tell(player, "§c已放弃比赛");
|
|
224
|
+
tp(player, START_X, START_Y, START_Z);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ===== 查看排行榜 =====
|
|
229
|
+
fn show_leaderboard() {
|
|
230
|
+
announce("§b===== 跑酷排行榜 =====");
|
|
231
|
+
// 显示前 5 名 (需要记分板排序)
|
|
232
|
+
scoreboard_display("sidebar", "pk_best");
|
|
233
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import "../stdlib/math.mcrs"
|
|
2
|
+
import "../stdlib/combat.mcrs"
|
|
3
|
+
|
|
4
|
+
fn attack(enemy: string, base: int, bonus: int) {
|
|
5
|
+
let raw_damage = weapon_damage(base, bonus);
|
|
6
|
+
let damage = clamp(raw_damage, 1, 20);
|
|
7
|
+
apply_damage(enemy, damage);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@tick
|
|
11
|
+
fn battle_tick() {
|
|
12
|
+
attack("goblin", 4, 2);
|
|
13
|
+
}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// Zombie Survival - 僵尸生存模式
|
|
3
|
+
// ============================================
|
|
4
|
+
// 场景:玩家在竞技场中心抵御一波波僵尸
|
|
5
|
+
// 每波结束后可以购买装备升级
|
|
6
|
+
// Scenario: Survive zombie waves, buy upgrades between rounds
|
|
7
|
+
// ============================================
|
|
8
|
+
|
|
9
|
+
import "../stdlib/effects.mcrs"
|
|
10
|
+
import "../stdlib/world.mcrs"
|
|
11
|
+
import "../stdlib/inventory.mcrs"
|
|
12
|
+
import "../stdlib/bossbar.mcrs"
|
|
13
|
+
import "../stdlib/particles.mcrs"
|
|
14
|
+
|
|
15
|
+
// ===== 配置 =====
|
|
16
|
+
const ARENA_X: int = 0;
|
|
17
|
+
const ARENA_Y: int = 64;
|
|
18
|
+
const ARENA_Z: int = 0;
|
|
19
|
+
const ARENA_RADIUS: int = 30;
|
|
20
|
+
const WAVE_DELAY: int = 200; // 10秒准备时间
|
|
21
|
+
|
|
22
|
+
// ===== 游戏状态 =====
|
|
23
|
+
struct SurvivalState {
|
|
24
|
+
running: int,
|
|
25
|
+
wave: int,
|
|
26
|
+
zombies_left: int,
|
|
27
|
+
phase: int, // 0=准备, 1=战斗
|
|
28
|
+
prep_timer: int,
|
|
29
|
+
total_kills: int
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let state: SurvivalState = SurvivalState {
|
|
33
|
+
running: 0,
|
|
34
|
+
wave: 0,
|
|
35
|
+
zombies_left: 0,
|
|
36
|
+
phase: 0,
|
|
37
|
+
prep_timer: 0,
|
|
38
|
+
total_kills: 0
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// ===== 玩家数据 =====
|
|
42
|
+
// 金币记分板: zs_coins
|
|
43
|
+
// 击杀数: zs_kills
|
|
44
|
+
|
|
45
|
+
// ===== 初始化 =====
|
|
46
|
+
@load
|
|
47
|
+
fn init() {
|
|
48
|
+
scoreboard_add_objective("zs_coins", "dummy");
|
|
49
|
+
scoreboard_add_objective("zs_kills", "dummy");
|
|
50
|
+
scoreboard_add_objective("zs_display", "dummy");
|
|
51
|
+
|
|
52
|
+
// 显示记分板
|
|
53
|
+
scoreboard_display("sidebar", "zs_display");
|
|
54
|
+
|
|
55
|
+
set_night();
|
|
56
|
+
disable_mob_griefing();
|
|
57
|
+
|
|
58
|
+
announce("§4[僵尸生存] §f已加载!输入 /trigger start 开始");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ===== 开始游戏 =====
|
|
62
|
+
fn start_game() {
|
|
63
|
+
state.running = 1;
|
|
64
|
+
state.wave = 0;
|
|
65
|
+
state.total_kills = 0;
|
|
66
|
+
|
|
67
|
+
// 初始化玩家
|
|
68
|
+
foreach (p in @a) {
|
|
69
|
+
scoreboard_set(p, "zs_coins", 0);
|
|
70
|
+
scoreboard_set(p, "zs_kills", 0);
|
|
71
|
+
clear_inventory(p);
|
|
72
|
+
give(p, "minecraft:wooden_sword", 1);
|
|
73
|
+
give(p, "minecraft:leather_chestplate", 1);
|
|
74
|
+
tp(p, ARENA_X, ARENA_Y, ARENA_Z);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 创建 bossbar
|
|
78
|
+
create_progress_bar("zs_wave", "§c僵尸剩余", 10);
|
|
79
|
+
|
|
80
|
+
title(@a, "§4僵尸生存");
|
|
81
|
+
subtitle(@a, "§7准备战斗...");
|
|
82
|
+
|
|
83
|
+
// 开始第一波
|
|
84
|
+
start_prep_phase();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ===== 阶段控制 =====
|
|
88
|
+
fn start_prep_phase() {
|
|
89
|
+
state.phase = 0;
|
|
90
|
+
state.prep_timer = WAVE_DELAY;
|
|
91
|
+
state.wave = state.wave + 1;
|
|
92
|
+
|
|
93
|
+
announce("§4[僵尸生存] §e第 " + state.wave + " 波即将来袭!");
|
|
94
|
+
announce("§7准备时间 10 秒...");
|
|
95
|
+
|
|
96
|
+
// 商店提示
|
|
97
|
+
if (state.wave > 1) {
|
|
98
|
+
announce("§a[商店] §f输入 /trigger buy 购买装备");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
fn start_combat_phase() {
|
|
103
|
+
state.phase = 1;
|
|
104
|
+
|
|
105
|
+
// 计算僵尸数量 (每波增加)
|
|
106
|
+
let zombie_count: int = 3 + (state.wave * 2);
|
|
107
|
+
state.zombies_left = zombie_count;
|
|
108
|
+
|
|
109
|
+
// 更新 bossbar
|
|
110
|
+
bossbar_set_max("zs_wave", zombie_count);
|
|
111
|
+
bossbar_set_value("zs_wave", zombie_count);
|
|
112
|
+
|
|
113
|
+
title(@a, "§c第 " + state.wave + " 波");
|
|
114
|
+
|
|
115
|
+
// 生成僵尸
|
|
116
|
+
spawn_zombies(zombie_count);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fn spawn_zombies(count: int) {
|
|
120
|
+
for i in 0..count {
|
|
121
|
+
// 在竞技场边缘随机生成
|
|
122
|
+
let angle: int = i * 30; // 分散生成
|
|
123
|
+
let spawn_x: int = ARENA_X + (ARENA_RADIUS - 5);
|
|
124
|
+
let spawn_z: int = ARENA_Z;
|
|
125
|
+
|
|
126
|
+
// 根据波数增加僵尸强度
|
|
127
|
+
if (state.wave < 3) {
|
|
128
|
+
summon("minecraft:zombie", spawn_x, ARENA_Y, spawn_z);
|
|
129
|
+
} else {
|
|
130
|
+
if (state.wave < 5) {
|
|
131
|
+
// 穿盔甲的僵尸
|
|
132
|
+
summon("minecraft:zombie", spawn_x, ARENA_Y, spawn_z,
|
|
133
|
+
{ArmorItems: [{}, {}, {id: "iron_chestplate", Count: 1}, {}]});
|
|
134
|
+
} else {
|
|
135
|
+
// 快速僵尸
|
|
136
|
+
summon("minecraft:husk", spawn_x, ARENA_Y, spawn_z);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
announce("§c" + count + " 只僵尸出现了!");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ===== 每 tick =====
|
|
145
|
+
@tick
|
|
146
|
+
fn game_tick() {
|
|
147
|
+
if (state.running == 0) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (state.phase == 0) {
|
|
152
|
+
// 准备阶段
|
|
153
|
+
prep_tick();
|
|
154
|
+
} else {
|
|
155
|
+
// 战斗阶段
|
|
156
|
+
combat_tick();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
update_scoreboard();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
fn prep_tick() {
|
|
163
|
+
state.prep_timer = state.prep_timer - 1;
|
|
164
|
+
|
|
165
|
+
// 倒计时提示
|
|
166
|
+
if (state.prep_timer == 100) {
|
|
167
|
+
actionbar(@a, "§e5 秒...");
|
|
168
|
+
}
|
|
169
|
+
if (state.prep_timer == 60) {
|
|
170
|
+
actionbar(@a, "§e3 秒...");
|
|
171
|
+
}
|
|
172
|
+
if (state.prep_timer == 20) {
|
|
173
|
+
actionbar(@a, "§c1 秒...");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (state.prep_timer <= 0) {
|
|
177
|
+
start_combat_phase();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
fn combat_tick() {
|
|
182
|
+
// 检查僵尸数量
|
|
183
|
+
let zombies: int = count_entities(@e[type=zombie, distance=..50]);
|
|
184
|
+
let husks: int = count_entities(@e[type=husk, distance=..50]);
|
|
185
|
+
state.zombies_left = zombies + husks;
|
|
186
|
+
|
|
187
|
+
// 更新 bossbar
|
|
188
|
+
update_bar("zs_wave", state.zombies_left);
|
|
189
|
+
|
|
190
|
+
// 检查波次完成
|
|
191
|
+
if (state.zombies_left <= 0) {
|
|
192
|
+
wave_complete();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 检查玩家存活
|
|
196
|
+
let alive: int = count_entities(@a[gamemode=survival]);
|
|
197
|
+
if (alive <= 0) {
|
|
198
|
+
game_over();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
fn wave_complete() {
|
|
203
|
+
announce("§4[僵尸生存] §a第 " + state.wave + " 波完成!");
|
|
204
|
+
|
|
205
|
+
// 奖励金币
|
|
206
|
+
let reward: int = 50 + (state.wave * 25);
|
|
207
|
+
foreach (p in @a) {
|
|
208
|
+
scoreboard_add(p, "zs_coins", reward);
|
|
209
|
+
happy(p);
|
|
210
|
+
}
|
|
211
|
+
announce("§6+" + reward + " 金币");
|
|
212
|
+
|
|
213
|
+
// 检查特殊波
|
|
214
|
+
if (state.wave == 10) {
|
|
215
|
+
victory();
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
start_prep_phase();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ===== 商店系统 =====
|
|
223
|
+
fn buy_item(player: selector, item_id: int) {
|
|
224
|
+
let coins: int = scoreboard_get(player, "zs_coins");
|
|
225
|
+
|
|
226
|
+
if (item_id == 1) {
|
|
227
|
+
// 铁剑 - 100 金币
|
|
228
|
+
if (coins >= 100) {
|
|
229
|
+
scoreboard_add(player, "zs_coins", -100);
|
|
230
|
+
give(player, "minecraft:iron_sword", 1);
|
|
231
|
+
tell(player, "§a购买成功:铁剑");
|
|
232
|
+
} else {
|
|
233
|
+
tell(player, "§c金币不足!需要 100");
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (item_id == 2) {
|
|
238
|
+
// 铁甲 - 200 金币
|
|
239
|
+
if (coins >= 200) {
|
|
240
|
+
scoreboard_add(player, "zs_coins", -200);
|
|
241
|
+
give(player, "minecraft:iron_chestplate", 1);
|
|
242
|
+
give(player, "minecraft:iron_leggings", 1);
|
|
243
|
+
give(player, "minecraft:iron_boots", 1);
|
|
244
|
+
tell(player, "§a购买成功:铁甲套装");
|
|
245
|
+
} else {
|
|
246
|
+
tell(player, "§c金币不足!需要 200");
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (item_id == 3) {
|
|
251
|
+
// 弓箭 - 150 金币
|
|
252
|
+
if (coins >= 150) {
|
|
253
|
+
scoreboard_add(player, "zs_coins", -150);
|
|
254
|
+
give(player, "minecraft:bow", 1);
|
|
255
|
+
give(player, "minecraft:arrow", 32);
|
|
256
|
+
tell(player, "§a购买成功:弓 + 32 箭");
|
|
257
|
+
} else {
|
|
258
|
+
tell(player, "§c金币不足!需要 150");
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (item_id == 4) {
|
|
263
|
+
// 金苹果 - 75 金币
|
|
264
|
+
if (coins >= 75) {
|
|
265
|
+
scoreboard_add(player, "zs_coins", -75);
|
|
266
|
+
give(player, "minecraft:golden_apple", 2);
|
|
267
|
+
tell(player, "§a购买成功:金苹果 x2");
|
|
268
|
+
} else {
|
|
269
|
+
tell(player, "§c金币不足!需要 75");
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ===== 结束 =====
|
|
275
|
+
fn victory() {
|
|
276
|
+
state.running = 0;
|
|
277
|
+
|
|
278
|
+
title(@a, "§6胜利!");
|
|
279
|
+
subtitle(@a, "§a你们生存了 10 波!");
|
|
280
|
+
announce("§4[僵尸生存] §6恭喜!完成全部 10 波!");
|
|
281
|
+
|
|
282
|
+
foreach (p in @a) {
|
|
283
|
+
totem_effect(p);
|
|
284
|
+
buff_all(p, 200);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
remove_bar("zs_wave");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
fn game_over() {
|
|
291
|
+
state.running = 0;
|
|
292
|
+
|
|
293
|
+
title(@a, "§c游戏结束");
|
|
294
|
+
subtitle(@a, "§7生存了 " + state.wave + " 波");
|
|
295
|
+
announce("§4[僵尸生存] §c全员阵亡!最高波数:" + state.wave);
|
|
296
|
+
|
|
297
|
+
// 清理僵尸
|
|
298
|
+
kill(@e[type=zombie]);
|
|
299
|
+
kill(@e[type=husk]);
|
|
300
|
+
|
|
301
|
+
remove_bar("zs_wave");
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
fn update_scoreboard() {
|
|
305
|
+
scoreboard_set("$wave", "zs_display", state.wave);
|
|
306
|
+
scoreboard_set("$zombies", "zs_display", state.zombies_left);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
fn count_entities(sel: selector) -> int {
|
|
310
|
+
// 使用 execute store 计算实体数量
|
|
311
|
+
let count: int = 0;
|
|
312
|
+
// 实际实现需要 execute store
|
|
313
|
+
return count;
|
|
314
|
+
}
|
package/src/ir/builder.ts
CHANGED
|
@@ -109,6 +109,8 @@ export class IRBuilder {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
import type { GlobalVar } from './types'
|
|
113
|
+
|
|
114
|
+
export function buildModule(namespace: string, fns: IRFunction[], globals: GlobalVar[] = []): IRModule {
|
|
113
115
|
return { namespace, functions: fns, globals }
|
|
114
116
|
}
|
package/src/ir/types.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - Integer vars → scoreboard fake player ($name on objective "rs_vars")
|
|
9
9
|
* - Complex data → NBT storage (redscript:stack / redscript:heap)
|
|
10
10
|
* - Return value → fake player $ret
|
|
11
|
-
* - Temporaries → $
|
|
11
|
+
* - Temporaries → $_0, $_1, ...
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
@@ -100,6 +100,7 @@ export interface IRFunction {
|
|
|
100
100
|
blocks: IRBlock[] // blocks[0] = entry block
|
|
101
101
|
commands?: IRCommand[] // structure target command stream
|
|
102
102
|
isTickLoop?: boolean // true → Repeat command block (runs every tick)
|
|
103
|
+
isLoadInit?: boolean // true → called from __load.mcfunction
|
|
103
104
|
isTriggerHandler?: boolean // true → handles a trigger event
|
|
104
105
|
triggerName?: string // the trigger objective name
|
|
105
106
|
eventTrigger?: {
|
|
@@ -112,8 +113,13 @@ export interface IRFunction {
|
|
|
112
113
|
// Module — top-level compilation unit
|
|
113
114
|
// ---------------------------------------------------------------------------
|
|
114
115
|
|
|
116
|
+
export interface GlobalVar {
|
|
117
|
+
name: string
|
|
118
|
+
init: number
|
|
119
|
+
}
|
|
120
|
+
|
|
115
121
|
export interface IRModule {
|
|
116
122
|
namespace: string // datapack namespace (e.g. "mypack")
|
|
117
123
|
functions: IRFunction[]
|
|
118
|
-
globals:
|
|
124
|
+
globals: GlobalVar[] // global variable declarations with init values
|
|
119
125
|
}
|