-
Notifications
You must be signed in to change notification settings - Fork 4
/
brief.aca
439 lines (409 loc) · 16.7 KB
/
brief.aca
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
#*
* Generic test file for Acacia
* This file gives you a brief picture of Acacia,
* while itself is a legal Acacia program.
*
* Acacia is written in Python and version 3.6.1+ is required.
* No third-party Python package is needed.
* Compile this (i.e. turn it into MC behavior pack functions) with:
* cd path/to/acacia
* python acacia.py -d test/brief.aca
* "-d" is to show the debugging comments in output files.
*#
#* Variables *#
# Define a variable `x` of integer type and assign 10 to it
x: int = 10
# Initial value is optional.
# You can then assign to them using "="
x = 20
# The following defines a boolean varaible `y` (its value is either
# `True` or `False`) with no initial value.
y: bool
# Short varaible declaration can be used when there is an initial value
# for a variable. Type of varaible is inferred by compiler.
number := 20 # same as `number: int = 20`
# Following `+` operator should be calculated during compile time
# `0x` and `0b` can be used to refer to hexadecimal and binary number
x = 0xF2E + 0b11 # `int` type
y = True # `bool` type
# Augmented assignments can be used to modify a value
x += 20 # same as `x = x + 20`
# There are also -=, *=, /= and %=
#* Fancy operators *#
# Integer operations: + - * / %
number = (x % -2) * (x + 3)
# Boolean operations: and or not
boolean := y and (x > 10) or (x < 0)
#* Constants *#
# Some values in Acacia are not representable in Minecraft and are only
# for your convenience (e.g. strings). "v: t" and ":=" variable
# declaration really creates a value in Minecraft, and "=" assignment is
# also done in Minecraft at runtime. Thus, those values cannot be
# handled using those syntax.
const scoreboard = "scb"
# `const` keyword in Acacia defines constants that are known at compile
# time, and values of constants are held by compiler instead of
# Minecraft. It is similar to "constexpr" in C++ and "comptime" in Zig.
const player_name = "CBerJun"
# Integers and many other types can also be constants:
const CONST10 = 10
# Arithmetic operations are guaranteed to happen at compile time if both
# operands are constants.
const CONST20 = 2 * CONST10
#* Control statements *#
## If statement
if x > 5:
x *= 2
# `x` will be doubled only when it's larger than 5
## While loop statement
sum := 0
while x > 0:
sum += x
x -= 1
# Sum up numbers from 1 to `x`
# Note that: Loops are implemented through recursion, which runs a lot
# of commands. Minecraft limits the amount of commands that one
# function could trigger to 10000. Be careful when using loops.
## For-in structure & compile time list
# A for-in structure is a "compile time loop" that instructs the
# compiler to *generate repetitive commands* for each iteration. This is
# very different from a while loop (that's a "runtime loop" and is done
# in Minecraft).
# {1, 2, 3} creates a compile time list consisting of 1, 2 and 3.
for i in {1, 2, 3}:
x += i
# The above is equivalent to:
x += 1
x += 2
x += 3
# Since lists are compile time only, we have to use "const".
const my_list = {33, 44, 55}
# Assign value of element 0 in `my_list` (33) to `n`.
n := my_list[0]
# Common bisection algorithm implemented with this feature:
y_length := 23
const bisection_arr = {128, 64, 32, 16, 8, 4, 2, 1}
for i in bisection_arr:
if y_length >= i:
y_length -= i
# Lines starting with "/" runs a raw command. "${i}" is a
# substitution. (Acacia provides interface to implement this
# without writing a raw command yourself, but so far let's just
# use this command.)
/execute as CBerJun at @s run tp @s ~ ~${i} ~
# `slice` method works the same as Python's slice (start, stop, step)
# and list.range(10) creates {0, 1, 2, ..., 8, 9}.
for i in list.range(10).slice(2, None, 2):
# `i` is bound to 2, 4, 6 and 8 in turn.
x += i
# This will increase `x` by 5 for 10 times.
for i in list.repeat(x, 10):
i += 5
#* Compile time map *#
# Map is similar to list since it is also compile time only.
# Its keys can only be several literal types (e.g. int literal and
# string).
const mp = {
0: "zero", 1: "one",
2: "two", 3: "three"
}
# Subscripting a map accesses its value according to given key.
i := 0 # calculate `i`...
# Using for-in on maps will iterate through its keys.
for num in mp:
if i == num:
/say ${mp[num]}
#* Great builtin modules! *#
#*
* The builtin modules are actually separated from the compiler itself,
* which means: everyone can create their own Acacia module!
* These builtin modules are written in Python at acaciamc/modules
*#
# To indicate that you are using `print` module
import print
# Tell all players "Hello world!"
print.tell("Hello world!")
# Show title "Chapter I" on a random player's screen, for 10 seconds
# (We'll talk about Engroup later.)
group := Engroup[Entity]()
group.select(Enfilter().random("player", limit=1))
print.title("Chapter I", group, stay_time=200)
# Show everyone the value of `x` in the form "Value of X: ..." above actionbar
print.title(print.format("Value of X: %0", x), mode=print.ACTIONBAR)
# Introduction to other builtin modules can be found in test/module.aca
# Not only these modules written in Python are supported, Acacia code
# can be imported by other Acacia programs. See test/module.aca
#* Super support to interact with the Minecraft world!!! *#
## `world` module
# The builtin `world` module contains interfaces to interact with the
# Minecraft world. We'll use it later.
import world
# Please see acaciamc/modules/world.py for detailed information.
## `entity` and entity template
# `entity` is an Acacia type that represents *exactly one* entity.
# A Minecraft `entity` can have a lot of meanings -- a dummy to store
# position, a character that interacts with player or even a piece of
# custom data. Therefore, Acacia has an "entity template" system. Each
# entity has its template, just like each variable has its type.
# Entities of the same template has same meaning.
# Create an entity template with "entity" keyword:
entity Test:
# Functions inside an entity template are called "methods". They
# are the actions that can be done on this kind of entities.
# You can "call the method of a specific entity". Usually we define
# a method using the `def` keyword (just like how we define
# functions). The `new` keyword is used to define a special type of
# method called "constructor", which is called when the entity is
# summoned.
new():
# Inside the `new` constructor, we should tell the compiler how
# to summon the entity and initialize the entity. This `new`
# method summons an invisible Armor Stand.
# Tell the compiler that when spawning an entity with this
# template (`Test`), spawn an Armor Stand at world position
# 10, -50, 10 (we'll explain `Pos` later).
new(type="armor_stand", pos=Pos(10, -50, 10))
# `self` inside a method refers to "the specific entity"
# mentioned above.
# This statement gives `self` invisibility buff for 600 ticks.
world.effect_give(self, "invisibility", duration=600)
# Now we have created a template of which entities are of armor stand
# type and will become invisible when summoned.
# Summon an entity of Test template:
test_entity := Test()
# You can define a template that "extends" another template:
entity Subtemplate extends Test:
new():
# Use the `new` defined in Test:
Test.new()
# Then do extra things added by this template:
print.tell("It's Subtemplate here!")
# This creates a template that prints a message in chat when any entity
# of this template is summoned. Plus, it has all the behaviors of Test
# template (become invisible when summoned). We say that Test is
# Subtemplate's base.
# You can assign entity variables of Subtemplate template to those of
# Test template, but not the other way around.
test_entity = Subtemplate()
# A function can take arguments of entity type by specifying template:
def kill(test: Test):
world.msg_say(test, "I'm killed") # Let `test` shout "I'm killed"
world.kill(test) # Kill the entity
kill(test_entity) # Remember Subtemplate is compatible with Test?
# `Entity` is the base of all templates and using it in type specifier
# will accept all entities.
# To advance users: entity methods can be virtual and support overriding
# (like classes in C++).
# Though this might not be that useful in Minecraft...
## Entity group and entity filter
# Suppose we want to track players in a mini game, all players have
# their own score and we want to track all of them.
# First define a template:
entity Player:
# `score` is "attribute" of entities of this template.
# All Player entities have their own score separated.
score: int
def show_score():
#* Show the player their score. *#
print.title(
# `self.score` gets the value of score attribute from this
# entity.
print.format("Your score: %0", self.score),
self, mode=print.ACTIONBAR
)
# As mentioned above, entity only represents one single entity, while
# entity group represents a group of entity and there can be zero or
# hundreds of entities inside this group. Entity groups also have their
# template, which is the template that entities inside this group use.
# Create a group of Player:
player_group := Engroup[Player]()
# Engroup[Template] is a type (like `int`) that means a group of
# entities of Template template. Calling it will give you a new empty
# entity group.
# You can select some existing entities using entity filter:
player_group.select(Enfilter()
.is_type("player")
.distance_from(Pos(0, 0, 0), max=5)
)
# Now `player_group` contains all players whose distance from 0, 0, 0 is
# no more than 5 blocks. Everything in Minecraft's selector can be found
# in `Enfilter`.
# Use for-in to iterate over all entities inside this group:
for player in player_group:
player.score += 1
# Now all players inside the `player_group` got their score plus 1.
# Note that this for-in works *completely* different from the for-in we
# previously used to iterate over list or map, but they looked the
# same. This structure is designed like this for readability.
# This removes players whose score is lower than 10 out of group:
for player in player_group:
if player.score < 10:
player_group.remove(player)
# Most of functions in `world` module accepts both single entity and an
# entity group. This moves entities in `player_group` 2 blocks lower:
world.move(player_group, y=-2)
## Position and rotation
# Position and rotation are pretty simple.
Pos(10, 10.5, 10) # absolute position 10, 10.5, 10
const pos = (
Pos(test_entity) # position of test entity
.offset(y=-5) # 5 blocks under test entity
# Move it by 3 blocks in the (xrot=90, yrot=0) direction.
.local(Rot(90, 0), front=3)
# Move it towards test entity by 2 blocks
.local(Rot.face_entity(test_entity), front=2)
.dim("the_end") # convert it to a position in The End
)
# Now let's place a yellow concrete at `pos`:
world.setblock(pos, world.Block("concrete", {"color": "yellow"}))
# See it? In command you will have to dabble with /execute context and
# in Acacia everything about position is in the `Pos` type.
## `Offset`
# `Pos` is suitable most of the time, but due to the design of command,
# we can't use Acacia's position everywhere. For example:
# world.fill(pos1, pos2, some_block)
# This gets compiled to /fill command. If pos1 and pos2 are all Pos,
# their /execute environment might conflict. For example:
# world.fill(Pos(entity1), Pos(entity2), some_block)
# This is hard to accomplish with command.
# Therefore, the real `world.fill` accepts an `Offset` as pos2, it is
# an offset related to pos1.
# This fills 10, 10, 10 to 20, 20, 20 with air:
world.fill(Pos(10, 10, 10), Offset().abs(20, 20, 20), "air")
# This does the same thing:
world.fill(Pos(10, 10, 10), Offset(+10, +10, +10), "air")
# Absolute position and relative offset can mix in `Offset`:
world.fill(Pos(10, 10, 10), Offset(x=+10, y_abs=20, z=+10), "air")
## `AbsPos`
# An absolute position can be either a `Pos` or an `Offset`. `AbsPos`
# is compatible with both of them and represents an absolute position.
const pos1 = AbsPos(10, 10, 10)
const pos2 = AbsPos(20, 20, 20)
world.fill(pos1, pos2, "diamond_block")
world.fill(pos2, pos1, "air", replacement="destroy")
#* Functions *#
# Type of argument or its default value must be specified
def max(a: int, b = int.MAX) -> int:
# int.MAX refers to 2147483647
#* Return the larger one between a and b *#
# Result of a function is specified by assigning to `result`.
if a >= b:
result a
# Unlike "return" in modern languages like Python,
# "result" does not stop functions!
else:
result b
def is_larger(a: int, b: int) -> bool:
#* Return whether a is larger than b *#
result a > b
if not is_larger(x, 4):
x = 2
# Comments and empty lines does not affect an indented block
x = 1 + max(x, 10)
# This elif statement is optimized (1 < 10 is always True)
elif 1 < 10:
x = 100
else:
pass # Empty block
#* Inline functions *#
#*
* Inline functions have great differences from normal ones.
* Inline functions are:
* 1. Expanded when called. Every time they get called, their body is
* parsed once. Therefore, type of argument can be omitted.
* 2. Arguments and result can be delivered in a different way rather
* than assigning (which normal functions do). See below.
*#
# In an inline function, arguments can be annotated with `const` to pass
# a compile time argument, and therefore type hint for these arguments
# are optional.
inline def my_print(const text = "Hello!"):
#* Print a message from Acacia. *#
print.tell("Acacia: " + text)
my_print() # Acacia: Hello!
my_print("Inline functions!") # Acacia: Inline functions!
# Arguments annotated with `&` accepts a "reference" of a given
# variable. This means that they can be assigned to!
inline def set_var(&x: int):
x = 10
set_var(x) # `x` is 10 now
# Result can be reference or const, too. The type must be specified, and
# we use `Any` for a type that we cannot represent.
inline def prefix_str(const s) -> const Any:
#* Prefix given string `s` with "I like ". *#
result "I like " + s
my_print(prefix_str("CB!")) # Acacia: I like CB!
# `const` can also annotate a function. In this case, the function body
# will be processed at compile time, and no command will be generated.
# Inside `const def`, many compile time values are mutable (e.g.
# strings), so we handle them using regular variable declaration and
# assignment -- without `const` keyword! The only way it communicate
# with the outside "runtime world" is to use result value.
const def fact(x: int) -> int:
res := 1
while x > 1:
res *= x
x -= 1
result res
# The above function calculates factorial *at compile time*.
x = fact(6) # completely same as x = 720
# Since everything happening inside `const def` are compile time, the
# arguments are implicitly compile time constants -- `fact(m)` would
# compile only if `m` is a constant defined with `const`.
#* Structs *#
# Structs are combination of data. It allows you to create custom
# data types.
# Create a struct:
struct Struct:
# Custom data type `Struct`
i: int
b: bool
# Create a variable of this custom data type:
test_struct: Struct
# Setting the members inside this struct:
test_struct.i = 10
test_struct.b = test_struct.i < 5
# This is useful when a function's result has multiple values:
def result_struct(i: int) -> Struct:
# You can specify initial values of struct members:
res := Struct(i=i)
res.b = i < 5
result res
test_struct = result_struct(10)
#* Exported interfaces *#
# These interfaces are exported for later use in Minecraft
interface a:
# Local variables are unreachable outside scope
local := 20 < x <= 100 # Chained comparison operators!
# Do something...
# Now we can use the `a` interface in Minecraft through /function
# command (when using default compile options, it is stored at just
# a.mcfunction)
# The following is generated at b/c.mcfunction
interface b/c:
/say b/c called
#* Compatible with the original command system *#
## Accessing scoreboards
# `scb(selector, objective)` refers to selector's score on objective,
# where selector is a string or entity and objective is a string.
x = scb("spam", "score") + 10
scb("foo", scoreboard) = 3
x = scb(player_name, "data")
## Accessing Raw Command
/say Hello, world!
# Using binding with commands
/tp ${player_name} 11 45 14
/scoreboard players add ${player_name} ${scoreboard} 10
# Multi-line command:
/*
tellraw @a {"rawtext":[
{"text": "Hello "},
{"text": "world!"}
]}
*/
#* Line continuation using backslash *#
x = \
10 + int(y)
# `int` converts boolean to integer.
# It also refers to the builtin integer type.
# ENJOY!!!