The only way to print text in Mindustry Logic is to use the print
instruction (made available to Mindcode through
various kinds of print()
function) one or more times to construct the resulting string in a text buffer.
Each print
instruction adds its argument, converted to string if necessary, to the end of the text buffer.
Every printflush
instruction, even unsuccessful, clears the text buffer.
When a text is being printed into a message by using the printflush
instruction, the entire string from the text
buffer is displayed in the message block. At this moment, the string is inspected for special character sequences
that can alter the appearance of the text in the message block. Two kinds of formatting are supported:
- Line breaks
- Setting text color
When the \n
sequence of characters is found in the string being printed, the \n
sequence is replaced by a line
break. For example:
print("One\nTwo")
printflush(message1)
produces the following output:
One
Two
Note that the backslash character is only recognized as part of the \n
sequence, it is not otherwise specially
handled. Specifically, it is not possible to encode it as \\
, unlike many other programming languages. Therefore,
the following code snippet
print("One\\Two\\nThree")
printflush(message1)
produces the following output:
One\\Two\
Three
If you really want to output \n
in the message block for whatever reason, you can use this trick:
print("One\[red][]nTwo")
printflush(message1)
which finally produces
One\nTwo
This is because the square brackets are used co encode color (see the next paragraph). The []
cancels [red]
,
and together they split apart \
and n
in such a way the message block doesn't recognize them anymore.
The color of the text can be set by entering the color in square brackets. Colors can be specified using
predefined color names (in uppercase or lowercase, but not mixed-case), or by entering the color in #RRGGBB
or #RRGGBBAA
form. The []
sequence then restores the previous color. It is therefore possible to use
println("[red]This is red")
println("[green]This is green")
println("[]This is red again")
printflush(message1)
to print colored texts in the colors indicated in the example.
Show full list of predefined color names.
clear
black
white
light_gray
gray
dark_gray
light_grey
grey
dark_grey
blue
navy
royal
slate
sky
cyan
teal
green
acid
lime
forest
olive
yellow
gold
goldenrod
orange
brown
tan
brick
red
scarlet
crimson
coral
salmon
pink
magenta
purple
violet
maroon
When a color name or color code is not recognized in square brackets, the text including the brackets is left as is.
A sequence of two left square brackets, i.e. [[
, is always printed as [
e.g.:
println("The color is [[red]")
println("The state is [[alarm]")
printflush(message1)
produces
The color is [red]
The state is [alarm]
In the second case, the doubling of square bracket is not strictly necessary, because alarm
isn't recognized as a
color name.
It is also possible to use built-in Mindustry icons in the print
instruction, for example
println(ITEM-COAL, ": ", vault1.coal)
println(ITEM-LEAD, ": ", vault1.lead)
println(ITEM-SAND, ": ", vault1.sand)
printf("Using $UNIT-MEGA to transport items...\n")
Supported Mindustry icons are available through built-in String constants containing them. For a complete list of available icons, see Built-in icons, for a possibility of embedding icons into string constant see String expressions.
When printing numbers, Mindustry prints the full representation of a number. It might be sometimes cumbersome,
as fractions can produce a lot of digits. To avoid this, use the floor
or ceil
function:
start_time = @time
do_some_stuff()
duration = @time - start_time
println("Elapsed: ", floor(duration), " ms")
When a number you're printing is smaller than 0.00001
(in absolute value), Mindustry will print zero (0
) instead.
The same formatting is used to display value of a variable in the Vars dialog in Mindustry UI. There isn't a way
to output such a small value directly. It is necessary to be aware that a number which was printed as 0
doesn't
necessarily have a zero value.
Mindustry allows your processors to control existing units. Among other things, you can use units to mine, attack, build, heal or move things around. Using units isn't that complicated, but it isn't always immediately apparent what needs to be done or what went wrong with your code, if it doesn't work as expected.
I'm laying out just the basic pointers here, you'll need to combine the techniques mentioned in your own way to create a truly robust solution.
Unit needs to be bound to the processor to be controlled through it, using the ubind
instruction (and a
corresponding ubind()
function). Only one unit can be bound at a time. All commands controlling the
unit are then sent to the bound unit.
You need to specify a unit type when binding a unit (e.g. ubind(@poly)
). If there is at least one unit of the
requested type, it is bound to your processor, and it is stored in a built-in variable named @unit
. (The ubind()
function also returns this value for convenience.) If no unit of the requested type exists, no unit is bound and
@unit
contains null
. When you call ubind()
again with the same unit type, a next available unit is returned.
Once you've gone through all existing units, the first one is bound again.
The @unit
variable is special, as it always contain the unit currently bound to the processor. You can store this
value in another variable. One possible reason to do that is that you can use such variable to determine you've
already encountered all existing units:
first_unit = ubind(@poly)
if first_unit != null
count = 1
while ubind(@poly) != first_unit
count += 1
end
else
count = 0
end
printf("There are $count active poly(s).")
printflush(message1)
This code counts the current number of active polys by binding all of them; as soon as the first one is encountered
again, we know we've seen them all. There is a slight problem with this code: if the unit stored in first_unit
is
destroyed, it will never be bound again, and we'll end up in an infinite loop. More on detecting destroyed units
later.
There's one additional, very important thing you can do with a unit stored in a variable: it can be used to bind that unit again. For example:
poly = ubind(@poly) // We just assume the units exist
mega = ubind(@mega)
ANGLE = 0
while true
controlUnit(poly)
controlUnit(mega)
end
def controlUnit(my_unit)
ubind(my_unit)
move(@thisx + 10 * sin(ANGLE), @thisy + 10 * cos(ANGLE)) // Do *something* with the unit
printf("Currently bound unit is ${@unit}.")
printflush(message1)
wait(0.8)
ANGLE += 45
end
Each unit has a flag, which can hold a numeric value (integer or decimal values). Initially, each unit has a zero flag. A lot of code that can be seen on the internet uses flags to mark units that are in use, so that other processors know to avoid them. Typical code might look like this:
def findFreeUnit(unit_type, mark_flag)
do
ubind(unit_type)
loop while @unit.flag !== 0
// If no unit was found, @unit would be null and so would be @unit.flag.
// The strict comparison (!==) ensures the loop will only end when a live unit with zero flag is found
flag(mark_flag)
return @unit
end
flag = rand(10**10)
my_unit = findFreeUnit(@mono, flag)
Later on, you might loop through all units and use the particular value of the flag to recognize those you acquired. Flags are typically generated randomly so that two processors running the same code do not steal each other's units.
There are two downsides to this arrangement:
- If a processor for some reason stops controlling the unit without clearing its flag, all other processors will consider that unit used and won't reuse it, potentially leading to shortage of available units.
- The flag actually allows you to store various information about unit state, for example which particular task was it assigned to. It is possible to encode the flag and the state into one numerical value, although it requires more computations and makes the code at least a bit slower.
The alternative to using flags is querying the unit to see whether it is free or actively controlled. When a unit is
free, the @unit.controlled
property returns 0
. When the value is nonzero, the unit is controlled, either by
a processor, or directly by a player, or by being part of the units commanded indirectly by player (different values
are assigned to each of these possibilities).
A wee bit enhanced findFreeUnit()
function using the controlled
property might look like this:
def findFreeUnit(unit_type, initial_flag)
// Keep looking for unit until one is found
while true
ubind(unit_type)
if @unit == null
printf("No unit of type $unit_type found.")
elsif @unit.controlled != 0
printf("Looking for a free $unit_type...")
else
flag(initial_flag) // Mark unit as active
return @unit
end
printflush(message1)
end
end
We're still flagging the unit. First of all, it assigns the initial state to it right off the bat, and secondly, it will signal to other processors that might use flags to recognize free units that this one is busy.
The other property is @unit.controller
. This returns the processor that is actively controlling the unit, or
null
if no processor controls that unit. Use this property to detect that your unit was lost:
if @unit.controller != @this
// We lost our unit. Immediatelly get a new one.
findFreeUnit(@mega, STATE_INIT)
end
Unit can become lost if a player or another rogue processor takes over it, so it is definitely useful to guard yourself against this possibility.
Unit becomes controlled by the processor when it is issued a command. Most ucontrol instructions will do so. Notably, setting a flag marks the unit as controlled while querying the flag or other properties of the unit won't.
If a unit is not issued commands from a processor for some time, it becomes free again and both controlled
and
controller
properties are cleared. My tests show it takes about 10 seconds:
loops = 0
ubind(@poly)
start = @time
flag(10)
while @unit.controller == @this
loops += 1 // while loop doesn't allow empty body as of now
end
printf("Unit was controlled for $ ms", floor(@time - start))
printflush(message1)
When a unit is destroyed, the variable that pointed to it keeps its original value. It is possible to detect a
destroyed unit (as well as a destroyed building, actually) by querying the dead
property. A value of 0
(or
false
) means the unit is alive, a value of 1
(or true
) means it is, well, dead as a parrot.
When querying the @unit.dead
property, you can possibly obtain three values
null
when no unit is actually bound,0
when a unit is bound and alive,1
when a unit is bound, but dead.
A single strict comparison (===
or !==
) can be used to recognize live unit from the other two alternatives.
An improved way to detect units no longer available to processor therefore is:
if @unit.controller != @this or @unit.dead !== 0
// We lost our unit. Immediatelly get a new one.
findFreeUnit(@mega, STATE_INIT)
end
Units can carry only one type of items at a time. It might therefore be sometimes necessary to discard items that are no longer needed. The simple, but not-so-obvious way of doing so, is to drop the item into the air:
itemDrop(@air, @unit.totalItems)
In case of dropping things into the air, all items are always dropped, regardless of the specified amount. I'd still suggest specifying the correct amount, just in case something changes in the future.
Mindustry allows your processors to control and receive information from any allied building only if they are linked to your processor, which can be done on a limited processor's range. Otherwise, you can only use units to get building and block information. Getting this data is not just limited to your buildings. You can also obtain enemy building information, which is not possible with linking to your processor since you can't link to an enemy building.
One of the first thing you'll probably want to do is to locate your core, as it is the most important building in the game. Doing so without a processor placed next to the core requires a unit. As soon as you bind a unit, just issue this command:
found = ulocate(building, core, false, core_x, core_y, core)
Let's look at each argument here:
found
is a variable that will receive the result of the operation,true
if the code was found,false
if it wasn't. If you don't need it, use justulocate(building, core, false, core_x, core_y, core)
.ulocate
is the name of the function we're calling.building
andcore
are constant values that specify what are we looking for, They must be specified exactly like this, you cannot, for example, store them in variable (e.g.type = core; ulocate(building, type, false, core_x, core_y, core)
won't work).false
specifies we're looking for our own core. Puttrue
if you want to locate enemy one.core_x
andcore_y
are variables that will receive the position of the core on the map. Use them to send your units there.core
is the building itself, and it can be used to query its state:
findFreeUnit(@poly, 1)
ulocate(building, core, false, core_x, core_y, core)
println("Silicon status: ", core.silicon)
printflush(message1)
will tell you how bad your silicone situation is.
You can obtain building and block information with the getBlock
command. Do not confuse with the getblock
command for world processors with only lowercase letters! getBlock
retrieves the building, floor type or block type at the given coordinates if the unit is within the radius of the position (unit range).
getBlock(x, y, type, building, floor)
Let's look at each argument here:
getBlock
it’s a function that itself returns nothing, but the arguments are returned with a valuex
andy
coordinatestype
variable name that is returned with the block type. If it’s a building returnsbuilding
. If there is free space, returnsair
. If it’s a solid environmental block, returnssolid
building
variable name that is returned with thebuilding
. If there is no building, returns null.floor
variable name that is returned with the floor type.
If you don't need some arguments, just leave them with 0: getBlock(x, y, 0, 0, myVariable)
. If a unit is out of range, or if the block doesn’t exist, all arguments return null
.
In the example code below Flare finds the nearest enemy turret and approach to it. If the unit is within of the turret, checks the building variable. If it's null, turret has been destroyed, if not, it's still exists. If ulocate return 0, no enemy turret was found. Another example here.
findFreeUnit(@flare, 1)
unitRange = @unit.range
enTurr = ulocate(building, turret, true, outx, outy) // Locating nearest enemy turret. If found, enTurr == 1 and (outx, ouy)- it's coordinates, else enTurr == 0.
while @unit.controlled == 1 // If the unit still controlled by this proc
if enTurr == 0
println("There are no more turrets")
printflush(message1)
wait(3)
end()
end
approach(outx, outy, unitRange)
inNear = within(outx, outy, unitRange)
if inNear == 1
getBlock(outx, outy, 0, block, 0) // We don't need a block type or floor type, only a building
if block === null
printf("Enemy turrer at ${outx}, ${outy} has been destroyed!\n")
printflush(message1)
wait(3)
enTurr = ulocate(building, turret, true, outx, outy) // Finding the next enemy turret
else
printf("Enemy turrer at ${outx}, ${outy} still exists\n")
end
else
printf("Searching for enemy turrer at ${outx}, ${outy}...\n")
end
printflush(message1)
end