redscript-mc 1.2.21 → 1.2.25

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.
Files changed (126) hide show
  1. package/.github/workflows/publish-extension-on-ci.yml +100 -0
  2. package/dist/__tests__/entity-types.test.d.ts +1 -0
  3. package/dist/__tests__/entity-types.test.js +203 -0
  4. package/dist/__tests__/var-allocator.test.d.ts +1 -0
  5. package/dist/__tests__/var-allocator.test.js +69 -0
  6. package/dist/ast/types.d.ts +2 -1
  7. package/dist/cli.js +17 -6
  8. package/dist/codegen/mcfunction/index.d.ts +2 -0
  9. package/dist/codegen/mcfunction/index.js +52 -43
  10. package/dist/codegen/structure/index.d.ts +4 -1
  11. package/dist/codegen/structure/index.js +8 -12
  12. package/dist/codegen/var-allocator.d.ts +28 -0
  13. package/dist/codegen/var-allocator.js +78 -0
  14. package/dist/index.d.ts +2 -0
  15. package/dist/index.js +8 -6
  16. package/dist/lowering/index.d.ts +2 -0
  17. package/dist/lowering/index.js +62 -3
  18. package/dist/parser/index.js +22 -1
  19. package/dist/typechecker/index.js +30 -0
  20. package/dist/types/entity-hierarchy.d.ts +29 -0
  21. package/dist/types/entity-hierarchy.js +107 -0
  22. package/editors/vscode/package-lock.json +6 -4
  23. package/editors/vscode/package.json +3 -3
  24. package/package.json +1 -1
  25. package/src/__tests__/entity-types.test.ts +236 -0
  26. package/src/__tests__/var-allocator.test.ts +75 -0
  27. package/src/ast/types.ts +8 -4
  28. package/src/cli.ts +20 -6
  29. package/src/codegen/mcfunction/index.ts +60 -48
  30. package/src/codegen/structure/index.ts +9 -14
  31. package/src/codegen/var-allocator.ts +75 -0
  32. package/src/examples/capture_the_flag.mcrs +34 -34
  33. package/src/examples/hunger_games.mcrs +59 -59
  34. package/src/examples/new_features_demo.mcrs +32 -32
  35. package/src/examples/parkour_race.mcrs +58 -58
  36. package/src/index.ts +10 -6
  37. package/src/lowering/index.ts +73 -8
  38. package/src/parser/index.ts +20 -1
  39. package/src/typechecker/index.ts +30 -0
  40. package/src/types/entity-hierarchy.ts +120 -0
  41. package/dist/data/arena/function/__load.mcfunction +0 -6
  42. package/dist/data/arena/function/__tick.mcfunction +0 -2
  43. package/dist/data/arena/function/announce_leaders/else_1.mcfunction +0 -3
  44. package/dist/data/arena/function/announce_leaders/foreach_0/merge_2.mcfunction +0 -1
  45. package/dist/data/arena/function/announce_leaders/foreach_0/then_0.mcfunction +0 -3
  46. package/dist/data/arena/function/announce_leaders/foreach_0.mcfunction +0 -7
  47. package/dist/data/arena/function/announce_leaders/foreach_1/merge_2.mcfunction +0 -1
  48. package/dist/data/arena/function/announce_leaders/foreach_1/then_0.mcfunction +0 -4
  49. package/dist/data/arena/function/announce_leaders/foreach_1.mcfunction +0 -6
  50. package/dist/data/arena/function/announce_leaders/merge_2.mcfunction +0 -1
  51. package/dist/data/arena/function/announce_leaders/then_0.mcfunction +0 -4
  52. package/dist/data/arena/function/announce_leaders.mcfunction +0 -6
  53. package/dist/data/arena/function/arena_tick/merge_2.mcfunction +0 -1
  54. package/dist/data/arena/function/arena_tick/then_0.mcfunction +0 -4
  55. package/dist/data/arena/function/arena_tick.mcfunction +0 -11
  56. package/dist/data/counter/function/__load.mcfunction +0 -5
  57. package/dist/data/counter/function/__tick.mcfunction +0 -2
  58. package/dist/data/counter/function/counter_tick/merge_2.mcfunction +0 -1
  59. package/dist/data/counter/function/counter_tick/then_0.mcfunction +0 -3
  60. package/dist/data/counter/function/counter_tick.mcfunction +0 -11
  61. package/dist/data/minecraft/tags/function/load.json +0 -5
  62. package/dist/data/minecraft/tags/function/tick.json +0 -5
  63. package/dist/data/quiz/function/__load.mcfunction +0 -16
  64. package/dist/data/quiz/function/__tick.mcfunction +0 -6
  65. package/dist/data/quiz/function/__trigger_quiz_a_dispatch.mcfunction +0 -4
  66. package/dist/data/quiz/function/__trigger_quiz_b_dispatch.mcfunction +0 -4
  67. package/dist/data/quiz/function/__trigger_quiz_c_dispatch.mcfunction +0 -4
  68. package/dist/data/quiz/function/__trigger_quiz_start_dispatch.mcfunction +0 -4
  69. package/dist/data/quiz/function/answer_a.mcfunction +0 -4
  70. package/dist/data/quiz/function/answer_b.mcfunction +0 -4
  71. package/dist/data/quiz/function/answer_c.mcfunction +0 -4
  72. package/dist/data/quiz/function/ask_question/else_1.mcfunction +0 -5
  73. package/dist/data/quiz/function/ask_question/else_4.mcfunction +0 -5
  74. package/dist/data/quiz/function/ask_question/else_7.mcfunction +0 -4
  75. package/dist/data/quiz/function/ask_question/merge_2.mcfunction +0 -1
  76. package/dist/data/quiz/function/ask_question/merge_5.mcfunction +0 -2
  77. package/dist/data/quiz/function/ask_question/merge_8.mcfunction +0 -2
  78. package/dist/data/quiz/function/ask_question/then_0.mcfunction +0 -4
  79. package/dist/data/quiz/function/ask_question/then_3.mcfunction +0 -4
  80. package/dist/data/quiz/function/ask_question/then_6.mcfunction +0 -4
  81. package/dist/data/quiz/function/ask_question.mcfunction +0 -7
  82. package/dist/data/quiz/function/finish_quiz.mcfunction +0 -6
  83. package/dist/data/quiz/function/handle_answer/else_1.mcfunction +0 -5
  84. package/dist/data/quiz/function/handle_answer/else_10.mcfunction +0 -3
  85. package/dist/data/quiz/function/handle_answer/else_16.mcfunction +0 -3
  86. package/dist/data/quiz/function/handle_answer/else_4.mcfunction +0 -3
  87. package/dist/data/quiz/function/handle_answer/else_7.mcfunction +0 -5
  88. package/dist/data/quiz/function/handle_answer/merge_11.mcfunction +0 -2
  89. package/dist/data/quiz/function/handle_answer/merge_14.mcfunction +0 -2
  90. package/dist/data/quiz/function/handle_answer/merge_17.mcfunction +0 -2
  91. package/dist/data/quiz/function/handle_answer/merge_2.mcfunction +0 -8
  92. package/dist/data/quiz/function/handle_answer/merge_5.mcfunction +0 -2
  93. package/dist/data/quiz/function/handle_answer/merge_8.mcfunction +0 -2
  94. package/dist/data/quiz/function/handle_answer/then_0.mcfunction +0 -5
  95. package/dist/data/quiz/function/handle_answer/then_12.mcfunction +0 -5
  96. package/dist/data/quiz/function/handle_answer/then_15.mcfunction +0 -6
  97. package/dist/data/quiz/function/handle_answer/then_3.mcfunction +0 -6
  98. package/dist/data/quiz/function/handle_answer/then_6.mcfunction +0 -5
  99. package/dist/data/quiz/function/handle_answer/then_9.mcfunction +0 -6
  100. package/dist/data/quiz/function/handle_answer.mcfunction +0 -11
  101. package/dist/data/quiz/function/start_quiz.mcfunction +0 -5
  102. package/dist/data/shop/function/__load.mcfunction +0 -7
  103. package/dist/data/shop/function/__tick.mcfunction +0 -3
  104. package/dist/data/shop/function/__trigger_shop_buy_dispatch.mcfunction +0 -4
  105. package/dist/data/shop/function/complete_purchase/else_1.mcfunction +0 -5
  106. package/dist/data/shop/function/complete_purchase/else_4.mcfunction +0 -5
  107. package/dist/data/shop/function/complete_purchase/else_7.mcfunction +0 -3
  108. package/dist/data/shop/function/complete_purchase/merge_2.mcfunction +0 -2
  109. package/dist/data/shop/function/complete_purchase/merge_5.mcfunction +0 -2
  110. package/dist/data/shop/function/complete_purchase/merge_8.mcfunction +0 -2
  111. package/dist/data/shop/function/complete_purchase/then_0.mcfunction +0 -4
  112. package/dist/data/shop/function/complete_purchase/then_3.mcfunction +0 -4
  113. package/dist/data/shop/function/complete_purchase/then_6.mcfunction +0 -4
  114. package/dist/data/shop/function/complete_purchase.mcfunction +0 -7
  115. package/dist/data/shop/function/handle_shop_trigger.mcfunction +0 -3
  116. package/dist/data/turret/function/__load.mcfunction +0 -5
  117. package/dist/data/turret/function/__tick.mcfunction +0 -4
  118. package/dist/data/turret/function/__trigger_deploy_turret_dispatch.mcfunction +0 -4
  119. package/dist/data/turret/function/deploy_turret.mcfunction +0 -8
  120. package/dist/data/turret/function/turret_tick/at_1.mcfunction +0 -2
  121. package/dist/data/turret/function/turret_tick/foreach_0.mcfunction +0 -2
  122. package/dist/data/turret/function/turret_tick/foreach_2.mcfunction +0 -2
  123. package/dist/data/turret/function/turret_tick/tick_body.mcfunction +0 -3
  124. package/dist/data/turret/function/turret_tick/tick_skip.mcfunction +0 -1
  125. package/dist/data/turret/function/turret_tick.mcfunction +0 -5
  126. package/dist/pack.mcmeta +0 -6
@@ -1,8 +1,8 @@
1
1
  // ============================================
2
- // Parkour Race - 跑酷竞速
2
+ // Parkour Race - Parkour Race
3
3
  // ============================================
4
- // 场景:玩家从起点跑到终点,记录最快时间
5
- // 设有多个检查点,掉落会传送回最近检查点
4
+ // Scenario: Players race from start to finish, recording the fastest time
5
+ // Multiple checkpoints set; falling respawns the player at the nearest checkpoint
6
6
  // Scenario: Race from start to finish with checkpoints
7
7
  // ============================================
8
8
 
@@ -11,28 +11,28 @@ import "../stdlib/world.mcrs"
11
11
  import "../stdlib/bossbar.mcrs"
12
12
  import "../stdlib/particles.mcrs"
13
13
 
14
- // ===== 配置 =====
14
+ // ===== Configuration =====
15
15
  const START_X: int = 0;
16
16
  const START_Y: int = 64;
17
17
  const START_Z: int = 0;
18
- const FALL_Y: int = 50; // 低于此高度视为掉落
18
+ const FALL_Y: int = 50; // Below this height is considered a fall
19
19
 
20
20
  const CHECKPOINT_COUNT: int = 5;
21
21
 
22
- // ===== 游戏状态 =====
23
- // 记分板:
24
- // - pk_time: 当前用时 (ticks)
25
- // - pk_best: 最佳记录
26
- // - pk_checkpoint: 当前检查点
27
- // - pk_running: 是否在比赛中
22
+ // ===== Game State =====
23
+ // Scoreboards:
24
+ // - pk_time: Current elapsed time (ticks)
25
+ // - pk_best: Best record
26
+ // - pk_checkpoint: Current checkpoint
27
+ // - pk_running: Whether in a race
28
28
 
29
- // 检查点坐标 (简化为直线跑酷)
30
- // CP0: 0,64,0 (起点)
29
+ // Checkpoint coordinates (simplified to linear parkour)
30
+ // CP0: 0,64,0 (start)
31
31
  // CP1: 0,64,50
32
32
  // CP2: 0,70,100
33
33
  // CP3: 0,75,150
34
34
  // CP4: 0,80,200
35
- // CP5: 0,64,250 (终点)
35
+ // CP5: 0,64,250 (finish)
36
36
 
37
37
  @load
38
38
  fn init() {
@@ -41,21 +41,21 @@ fn init() {
41
41
  scoreboard_add_objective("pk_checkpoint", "dummy");
42
42
  scoreboard_add_objective("pk_running", "dummy");
43
43
 
44
- // 显示最佳时间
44
+ // Display best time
45
45
  scoreboard_display("sidebar", "pk_best");
46
46
 
47
47
  set_day();
48
48
  weather_clear();
49
49
 
50
- announce("§b[跑酷] §f已加载!踩压力板开始");
50
+ announce("§b[Parkour] §fLoaded! Step on pressure plate to start");
51
51
  }
52
52
 
53
- // ===== 开始比赛 =====
53
+ // ===== Start Race =====
54
54
  fn start_race(player: selector) {
55
- // 检查是否已在比赛中
55
+ // Check if already in a race
56
56
  let running: int = scoreboard_get(player, "pk_running");
57
57
  if (running == 1) {
58
- tell(player, "§c你已经在比赛中了!");
58
+ tell(player, "§cYou are already in a race!");
59
59
  return;
60
60
  }
61
61
 
@@ -63,53 +63,53 @@ fn start_race(player: selector) {
63
63
  scoreboard_set(player, "pk_time", 0);
64
64
  scoreboard_set(player, "pk_checkpoint", 0);
65
65
 
66
- // 传送到起点
66
+ // Teleport to start
67
67
  tp(player, START_X, START_Y, START_Z);
68
68
 
69
- // 清除效果,给速度
69
+ // Clear effects, give speed
70
70
  effect_clear(player);
71
71
 
72
- title(player, "§b开始!");
73
- subtitle(player, "§7跑向终点!");
72
+ title(player, "§bGo!");
73
+ subtitle(player, "§7Race to the finish!");
74
74
 
75
- // 创建计时 bossbar
76
- tell(player, "§b[跑酷] §a比赛开始!到达终点完成比赛");
75
+ // Create timer bossbar
76
+ tell(player, "§b[Parkour] §aRace started! Reach the finish to complete");
77
77
 
78
78
  sparkles(player);
79
79
  }
80
80
 
81
- // ===== tick =====
81
+ // ===== Every Tick =====
82
82
  @tick
83
83
  fn race_tick() {
84
- // 对每个比赛中的玩家
84
+ // For each player in a race
85
85
  foreach (p in @a) {
86
86
  let running: int = scoreboard_get(p, "pk_running");
87
87
  if (running == 1) {
88
- // 增加时间
88
+ // Increment time
89
89
  scoreboard_add(p, "pk_time", 1);
90
90
 
91
- // 显示当前时间
91
+ // Display current time
92
92
  let time: int = scoreboard_get(p, "pk_time");
93
93
  let seconds: int = time / 20;
94
94
  let ms: int = (time % 20) * 5;
95
- actionbar(p, "§e⏱ " + seconds + "." + ms + "");
95
+ actionbar(p, "§e⏱ " + seconds + "." + ms + "s");
96
96
 
97
- // 检查掉落
97
+ // Check fall
98
98
  check_fall(p);
99
99
 
100
- // 检查检查点
100
+ // Check checkpoints
101
101
  check_checkpoints(p);
102
102
  }
103
103
  }
104
104
  }
105
105
 
106
106
  fn check_fall(player: selector) {
107
- // 如果玩家低于 FALL_Y,传送回检查点
108
- // 使用 execute positioned 检查
107
+ // If player is below FALL_Y, teleport back to checkpoint
108
+ // Using execute positioned to check
109
109
  execute if entity player[y=..50] run {
110
110
  let cp: int = scoreboard_get(player, "pk_checkpoint");
111
111
  respawn_at_checkpoint(player, cp);
112
- tell(player, "§c掉落!返回检查点 " + cp);
112
+ tell(player, "§cFell! Returning to checkpoint " + cp);
113
113
  angry(player);
114
114
  }
115
115
  }
@@ -117,35 +117,35 @@ fn check_fall(player: selector) {
117
117
  fn check_checkpoints(player: selector) {
118
118
  let current_cp: int = scoreboard_get(player, "pk_checkpoint");
119
119
 
120
- // 检查点 1 (0, 64, 50)
120
+ // Checkpoint 1 (0, 64, 50)
121
121
  if (current_cp == 0) {
122
122
  execute if entity player[x=-2..2, y=62..68, z=48..52] run {
123
123
  reach_checkpoint(player, 1);
124
124
  }
125
125
  }
126
126
 
127
- // 检查点 2 (0, 70, 100)
127
+ // Checkpoint 2 (0, 70, 100)
128
128
  if (current_cp == 1) {
129
129
  execute if entity player[x=-2..2, y=68..74, z=98..102] run {
130
130
  reach_checkpoint(player, 2);
131
131
  }
132
132
  }
133
133
 
134
- // 检查点 3 (0, 75, 150)
134
+ // Checkpoint 3 (0, 75, 150)
135
135
  if (current_cp == 2) {
136
136
  execute if entity player[x=-2..2, y=73..79, z=148..152] run {
137
137
  reach_checkpoint(player, 3);
138
138
  }
139
139
  }
140
140
 
141
- // 检查点 4 (0, 80, 200)
141
+ // Checkpoint 4 (0, 80, 200)
142
142
  if (current_cp == 3) {
143
143
  execute if entity player[x=-2..2, y=78..84, z=198..202] run {
144
144
  reach_checkpoint(player, 4);
145
145
  }
146
146
  }
147
147
 
148
- // 终点 (0, 64, 250)
148
+ // Finish (0, 64, 250)
149
149
  if (current_cp == 4) {
150
150
  execute if entity player[x=-2..2, y=62..68, z=248..252] run {
151
151
  finish_race(player);
@@ -157,7 +157,7 @@ fn reach_checkpoint(player: selector, cp: int) {
157
157
  scoreboard_set(player, "pk_checkpoint", cp);
158
158
 
159
159
  title(player, "");
160
- subtitle(player, "§a检查点 " + cp + " / " + CHECKPOINT_COUNT);
160
+ subtitle(player, "§aCheckpoint " + cp + " / " + CHECKPOINT_COUNT);
161
161
 
162
162
  happy(player);
163
163
  playsound("minecraft:entity.experience_orb.pickup", "player", player);
@@ -189,45 +189,45 @@ fn finish_race(player: selector) {
189
189
  let seconds: int = time / 20;
190
190
  let ms: int = (time % 20) * 5;
191
191
 
192
- // 检查是否是新纪录
192
+ // Check if it's a new record
193
193
  if (best == 0) {
194
- // 第一次完成
194
+ // First completion
195
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 + "");
196
+ title(player, "§6Finished!");
197
+ subtitle(player, "§e" + seconds + "." + ms + "s §7(first record)");
198
+ announce("§b[Parkour] §fA player finished! Time: §e" + seconds + "." + ms + "s");
199
199
  } else {
200
200
  if (time < best) {
201
- // 新纪录!
201
+ // New record!
202
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 + "");
203
+ title(player, "§6New Record!");
204
+ subtitle(player, "§e" + seconds + "." + ms + "s");
205
+ announce("§b[Parkour] §6New Record! §fTime: §e" + seconds + "." + ms + "s");
206
206
  totem_effect(player);
207
207
  } else {
208
- title(player, "§a完成!");
209
- subtitle(player, "§e" + seconds + "." + ms + "");
208
+ title(player, "§aFinished!");
209
+ subtitle(player, "§e" + seconds + "." + ms + "s");
210
210
  }
211
211
  }
212
212
 
213
- // 特效
213
+ // Effects
214
214
  sparkles(player);
215
215
  playsound("minecraft:ui.toast.challenge_complete", "player", player);
216
216
  }
217
217
 
218
- // ===== 放弃比赛 =====
218
+ // ===== Quit Race =====
219
219
  fn quit_race(player: selector) {
220
220
  let running: int = scoreboard_get(player, "pk_running");
221
221
  if (running == 1) {
222
222
  scoreboard_set(player, "pk_running", 0);
223
- tell(player, "§c已放弃比赛");
223
+ tell(player, "§cRace abandoned");
224
224
  tp(player, START_X, START_Y, START_Z);
225
225
  }
226
226
  }
227
227
 
228
- // ===== 查看排行榜 =====
228
+ // ===== View Leaderboard =====
229
229
  fn show_leaderboard() {
230
- announce("§b===== 跑酷排行榜 =====");
231
- // 显示前 5 (需要记分板排序)
230
+ announce("§b===== Parkour Leaderboard =====");
231
+ // Display top 5 (requires scoreboard sorting)
232
232
  scoreboard_display("sidebar", "pk_best");
233
233
  }
package/src/index.ts CHANGED
@@ -35,6 +35,7 @@ export interface CompileOptions {
35
35
  typeCheck?: boolean
36
36
  filePath?: string
37
37
  dce?: boolean
38
+ mangle?: boolean
38
39
  }
39
40
 
40
41
  export interface CompileResult {
@@ -45,6 +46,7 @@ export interface CompileResult {
45
46
  typeErrors?: DiagnosticError[]
46
47
  warnings?: Warning[]
47
48
  stats?: OptimizationStats
49
+ sourceMap?: Record<string, string>
48
50
  }
49
51
 
50
52
  /**
@@ -59,6 +61,7 @@ export function compile(source: string, options: CompileOptions = {}): CompileRe
59
61
  const shouldOptimize = options.optimize ?? true
60
62
  const shouldTypeCheck = options.typeCheck ?? true
61
63
  const shouldRunDce = options.dce ?? shouldOptimize
64
+ const mangle = options.mangle ?? false
62
65
  const filePath = options.filePath
63
66
  const preprocessed = preprocessSourceWithMetadata(source, { filePath })
64
67
  const preprocessedSource = preprocessed.source
@@ -83,7 +86,7 @@ export function compile(source: string, options: CompileOptions = {}): CompileRe
83
86
  const ir = lowering.lower(ast)
84
87
 
85
88
  let optimizedIR: IRModule = ir
86
- let generated = generateDatapackWithStats(ir, { optimizeCommands: shouldOptimize })
89
+ let generated = generateDatapackWithStats(ir, { optimizeCommands: shouldOptimize, mangle })
87
90
  let optimizationStats: OptimizationStats | undefined
88
91
 
89
92
  if (shouldOptimize) {
@@ -105,10 +108,10 @@ export function compile(source: string, options: CompileOptions = {}): CompileRe
105
108
  const copyPropagatedIR: IRModule = { ...ir, functions: copyPropagatedFunctions }
106
109
  optimizedIR = { ...ir, functions: deadCodeEliminatedFunctions }
107
110
 
108
- const baselineGenerated = generateDatapackWithStats(ir, { optimizeCommands: false })
109
- const beforeDceGenerated = generateDatapackWithStats(copyPropagatedIR, { optimizeCommands: false })
110
- const afterDceGenerated = generateDatapackWithStats(optimizedIR, { optimizeCommands: false })
111
- generated = generateDatapackWithStats(optimizedIR, { optimizeCommands: true })
111
+ const baselineGenerated = generateDatapackWithStats(ir, { optimizeCommands: false, mangle })
112
+ const beforeDceGenerated = generateDatapackWithStats(copyPropagatedIR, { optimizeCommands: false, mangle })
113
+ const afterDceGenerated = generateDatapackWithStats(optimizedIR, { optimizeCommands: false, mangle })
114
+ generated = generateDatapackWithStats(optimizedIR, { optimizeCommands: true, mangle })
112
115
 
113
116
  stats.deadCodeRemoved =
114
117
  countMcfunctionCommands(beforeDceGenerated.files) - countMcfunctionCommands(afterDceGenerated.files)
@@ -124,7 +127,7 @@ export function compile(source: string, options: CompileOptions = {}): CompileRe
124
127
  optimizationStats = stats
125
128
  } else {
126
129
  optimizedIR = ir
127
- generated = generateDatapackWithStats(ir, { optimizeCommands: false })
130
+ generated = generateDatapackWithStats(ir, { optimizeCommands: false, mangle })
128
131
  }
129
132
 
130
133
  return {
@@ -135,6 +138,7 @@ export function compile(source: string, options: CompileOptions = {}): CompileRe
135
138
  typeErrors,
136
139
  warnings: [...dceResult.warnings, ...lowering.warnings],
137
140
  stats: optimizationStats,
141
+ sourceMap: generated.sourceMap,
138
142
  }
139
143
  }
140
144
 
@@ -17,6 +17,7 @@ import type {
17
17
  import type { GlobalVar } from '../ir/types'
18
18
  import * as path from 'path'
19
19
  import { EVENT_TYPES, getEventParamSpecs, isEventTypeName } from '../events/types'
20
+ import { getBaseSelectorType, areCompatibleTypes, getConcreteSubtypes } from '../types/entity-hierarchy'
20
21
 
21
22
  // ---------------------------------------------------------------------------
22
23
  // Macro-aware builtins (MC 1.20.2+)
@@ -134,11 +135,21 @@ const ENTITY_TO_MC_TYPE: Partial<Record<EntityTypeName, string>> = {
134
135
  Creeper: 'minecraft:creeper',
135
136
  Spider: 'minecraft:spider',
136
137
  Enderman: 'minecraft:enderman',
138
+ Blaze: 'minecraft:blaze',
139
+ Witch: 'minecraft:witch',
140
+ Slime: 'minecraft:slime',
141
+ ZombieVillager: 'minecraft:zombie_villager',
142
+ Husk: 'minecraft:husk',
143
+ Drowned: 'minecraft:drowned',
144
+ Stray: 'minecraft:stray',
145
+ WitherSkeleton: 'minecraft:wither_skeleton',
146
+ CaveSpider: 'minecraft:cave_spider',
137
147
  Pig: 'minecraft:pig',
138
148
  Cow: 'minecraft:cow',
139
149
  Sheep: 'minecraft:sheep',
140
150
  Chicken: 'minecraft:chicken',
141
151
  Villager: 'minecraft:villager',
152
+ WanderingTrader: 'minecraft:wandering_trader',
142
153
  ArmorStand: 'minecraft:armor_stand',
143
154
  Item: 'minecraft:item',
144
155
  Arrow: 'minecraft:arrow',
@@ -211,6 +222,15 @@ export class Lowering {
211
222
  private intervalCounter: number = 0
212
223
  readonly warnings: Warning[] = []
213
224
 
225
+ // Entity type context stack for W_IMPOSSIBLE_AS warnings
226
+ private entityContextStack: string[] = []
227
+
228
+ private currentEntityContext(): string {
229
+ return this.entityContextStack.length > 0
230
+ ? this.entityContextStack[this.entityContextStack.length - 1]
231
+ : 'Entity'
232
+ }
233
+
214
234
  // Builder state for current function
215
235
  private builder!: LoweringBuilder
216
236
  private varMap: Map<string, string> = new Map()
@@ -1056,17 +1076,31 @@ export class Lowering {
1056
1076
  }
1057
1077
 
1058
1078
  const mcType = ENTITY_TO_MC_TYPE[cond.entityType]
1079
+ const thenFnName = `${this.currentFn}/then_${this.foreachCounter++}`
1080
+
1059
1081
  if (!mcType) {
1060
- throw new DiagnosticError(
1061
- 'LoweringError',
1062
- `Cannot lower entity type check for '${cond.entityType}'`,
1063
- cond.span ?? stmt.span ?? { line: 0, col: 0 }
1064
- )
1082
+ // Abstract type — check all concrete subtypes
1083
+ const subtypes = getConcreteSubtypes(cond.entityType)
1084
+ if (subtypes.length === 0) {
1085
+ throw new DiagnosticError(
1086
+ 'LoweringError',
1087
+ `Cannot lower entity type check for '${cond.entityType}'`,
1088
+ cond.span ?? stmt.span ?? { line: 0, col: 0 }
1089
+ )
1090
+ }
1091
+ // Use a temp scoreboard variable to OR multiple type checks
1092
+ this.builder.emitRaw(`scoreboard players set __is_result rs:temp 0`)
1093
+ for (const subtype of subtypes) {
1094
+ if (subtype.mcId) {
1095
+ this.builder.emitRaw(`execute if entity ${this.appendTypeFilter(selector, subtype.mcId)} run scoreboard players set __is_result rs:temp 1`)
1096
+ }
1097
+ }
1098
+ this.builder.emitRaw(`execute if score __is_result rs:temp matches 1 run function ${this.namespace}:${thenFnName}`)
1099
+ } else {
1100
+ // Concrete type — single check
1101
+ this.builder.emitRaw(`execute if entity ${this.appendTypeFilter(selector, mcType)} run function ${this.namespace}:${thenFnName}`)
1065
1102
  }
1066
1103
 
1067
- const thenFnName = `${this.currentFn}/then_${this.foreachCounter++}`
1068
- this.builder.emitRaw(`execute if entity ${this.appendTypeFilter(selector, mcType)} run function ${this.namespace}:${thenFnName}`)
1069
-
1070
1104
  const savedBuilder = this.builder
1071
1105
  const savedVarMap = new Map(this.varMap)
1072
1106
  const savedBlockPosVars = new Map(this.blockPosVars)
@@ -1243,12 +1277,22 @@ export class Lowering {
1243
1277
  // In foreach body, the binding maps to @s
1244
1278
  this.varMap.set(stmt.binding, '@s')
1245
1279
 
1280
+ // Track entity context for type narrowing
1281
+ const selectorEntityType = getBaseSelectorType(selector)
1282
+ if (selectorEntityType) {
1283
+ this.entityContextStack.push(selectorEntityType)
1284
+ }
1285
+
1246
1286
  this.builder.startBlock('entry')
1247
1287
  this.lowerBlock(stmt.body)
1248
1288
  if (!this.builder.isBlockSealed()) {
1249
1289
  this.builder.emitReturn()
1250
1290
  }
1251
1291
 
1292
+ if (selectorEntityType) {
1293
+ this.entityContextStack.pop()
1294
+ }
1295
+
1252
1296
  const subFn = this.builder.build(subFnName, [], false)
1253
1297
  this.functions.push(subFn)
1254
1298
 
@@ -1397,6 +1441,18 @@ export class Lowering {
1397
1441
  const selector = this.selectorToString(stmt.selector)
1398
1442
  const subFnName = `${this.currentFn}/as_${this.foreachCounter++}`
1399
1443
 
1444
+ // Check for impossible type assertions (W_IMPOSSIBLE_AS)
1445
+ const innerType = getBaseSelectorType(selector)
1446
+ const outerType = this.currentEntityContext()
1447
+ if (innerType && outerType !== 'Entity' && innerType !== 'Entity' && !areCompatibleTypes(outerType, innerType)) {
1448
+ this.warnings.push({
1449
+ message: `Impossible type assertion: @s is ${outerType} but as-block targets ${innerType}`,
1450
+ code: 'W_IMPOSSIBLE_AS',
1451
+ line: stmt.span?.line,
1452
+ col: stmt.span?.col,
1453
+ })
1454
+ }
1455
+
1400
1456
  this.builder.emitRaw(`execute as ${selector} run function ${this.namespace}:${subFnName}`)
1401
1457
 
1402
1458
  // Create sub-function
@@ -1408,12 +1464,21 @@ export class Lowering {
1408
1464
  this.varMap = new Map(savedVarMap)
1409
1465
  this.blockPosVars = new Map(savedBlockPosVars)
1410
1466
 
1467
+ // Track entity context inside as-block
1468
+ if (innerType) {
1469
+ this.entityContextStack.push(innerType)
1470
+ }
1471
+
1411
1472
  this.builder.startBlock('entry')
1412
1473
  this.lowerBlock(stmt.body)
1413
1474
  if (!this.builder.isBlockSealed()) {
1414
1475
  this.builder.emitReturn()
1415
1476
  }
1416
1477
 
1478
+ if (innerType) {
1479
+ this.entityContextStack.pop()
1480
+ }
1481
+
1417
1482
  const subFn = this.builder.build(subFnName, [], false)
1418
1483
  this.functions.push(subFn)
1419
1484
 
@@ -41,11 +41,21 @@ const ENTITY_TYPE_NAMES = new Set<EntityTypeName>([
41
41
  'Creeper',
42
42
  'Spider',
43
43
  'Enderman',
44
+ 'Blaze',
45
+ 'Witch',
46
+ 'Slime',
47
+ 'ZombieVillager',
48
+ 'Husk',
49
+ 'Drowned',
50
+ 'Stray',
51
+ 'WitherSkeleton',
52
+ 'CaveSpider',
44
53
  'Pig',
45
54
  'Cow',
46
55
  'Sheep',
47
56
  'Chicken',
48
57
  'Villager',
58
+ 'WanderingTrader',
49
59
  'ArmorStand',
50
60
  'Item',
51
61
  'Arrow',
@@ -446,7 +456,16 @@ export class Parser {
446
456
  type = { kind: 'named', name: token.kind }
447
457
  } else if (token.kind === 'ident') {
448
458
  this.advance()
449
- type = { kind: 'struct', name: token.value }
459
+ if (token.value === 'selector' && this.check('<')) {
460
+ this.advance() // consume <
461
+ const entityType = this.expect('ident').value
462
+ this.expect('>')
463
+ type = { kind: 'selector', entityType }
464
+ } else if (token.value === 'selector') {
465
+ type = { kind: 'selector' }
466
+ } else {
467
+ type = { kind: 'struct', name: token.value }
468
+ }
450
469
  } else {
451
470
  this.error(`Expected type, got '${token.kind}'`)
452
471
  }
@@ -31,11 +31,21 @@ const ENTITY_HIERARCHY: Record<EntityTypeName, EntityTypeName | null> = {
31
31
  'Creeper': 'HostileMob',
32
32
  'Spider': 'HostileMob',
33
33
  'Enderman': 'HostileMob',
34
+ 'Blaze': 'HostileMob',
35
+ 'Witch': 'HostileMob',
36
+ 'Slime': 'HostileMob',
37
+ 'ZombieVillager': 'HostileMob',
38
+ 'Husk': 'HostileMob',
39
+ 'Drowned': 'HostileMob',
40
+ 'Stray': 'HostileMob',
41
+ 'WitherSkeleton': 'HostileMob',
42
+ 'CaveSpider': 'HostileMob',
34
43
  'Pig': 'PassiveMob',
35
44
  'Cow': 'PassiveMob',
36
45
  'Sheep': 'PassiveMob',
37
46
  'Chicken': 'PassiveMob',
38
47
  'Villager': 'PassiveMob',
48
+ 'WanderingTrader': 'PassiveMob',
39
49
  'ArmorStand': 'entity',
40
50
  'Item': 'entity',
41
51
  'Arrow': 'entity',
@@ -53,6 +63,24 @@ const MC_TYPE_TO_ENTITY: Record<string, EntityTypeName> = {
53
63
  'minecraft:spider': 'Spider',
54
64
  'enderman': 'Enderman',
55
65
  'minecraft:enderman': 'Enderman',
66
+ 'blaze': 'Blaze',
67
+ 'minecraft:blaze': 'Blaze',
68
+ 'witch': 'Witch',
69
+ 'minecraft:witch': 'Witch',
70
+ 'slime': 'Slime',
71
+ 'minecraft:slime': 'Slime',
72
+ 'zombie_villager': 'ZombieVillager',
73
+ 'minecraft:zombie_villager': 'ZombieVillager',
74
+ 'husk': 'Husk',
75
+ 'minecraft:husk': 'Husk',
76
+ 'drowned': 'Drowned',
77
+ 'minecraft:drowned': 'Drowned',
78
+ 'stray': 'Stray',
79
+ 'minecraft:stray': 'Stray',
80
+ 'wither_skeleton': 'WitherSkeleton',
81
+ 'minecraft:wither_skeleton': 'WitherSkeleton',
82
+ 'cave_spider': 'CaveSpider',
83
+ 'minecraft:cave_spider': 'CaveSpider',
56
84
  'pig': 'Pig',
57
85
  'minecraft:pig': 'Pig',
58
86
  'cow': 'Cow',
@@ -63,6 +91,8 @@ const MC_TYPE_TO_ENTITY: Record<string, EntityTypeName> = {
63
91
  'minecraft:chicken': 'Chicken',
64
92
  'villager': 'Villager',
65
93
  'minecraft:villager': 'Villager',
94
+ 'wandering_trader': 'WanderingTrader',
95
+ 'minecraft:wandering_trader': 'WanderingTrader',
66
96
  'armor_stand': 'ArmorStand',
67
97
  'minecraft:armor_stand': 'ArmorStand',
68
98
  'item': 'Item',