This document explains how to use the event commands added by this mod.
- General Notes
- Stream Control
- Stream-Safe Command Replacements
- Actor Control
- Viewport Control
- Ambient Light Control
- World Control
- Conditional Execution
- Event Variables
- Vanilla Command Notes
Here are a few terms and things to know to help you read this document.
If you are here for the beta, please check out the example text files in the beta zip! There are some (fairly simple) scripts in there that may help you understand a particular command more clearly than my prose here.
Each one is formatted correctly to be used as a test event, so simply copy
the .txt file to your game directory, rename it test_event.txt, and
run debug rte in your SMAPI console to run it directly. There is no need
to patch reload or anything else; it will be read live.
When a command says that it "blocks" (verb) (or talks about another command
"blocking"), that means that it will pause at that point in the command
list and prevent the stream from continuing until some condition is met.
For example, move <npc> 3 0 2 blocks, because the optional true
argument was not given, so the stream will wait until the move completes
before running the next command.
Unlike vanilla, Codex commands that have a behavior toggle argument (e.g.
ViewportMove) are set up to expect a particular literal string instead of
the boolean strings false/true. I made this choice for ideological
reasons: I prefer when a boolean argument makes its purpose more clear at the
call site, since for me it reduces the burden of remembering what "true"
stands for in a particular command.
For example, ViewportMove has an optional argument wait. It expects to
find the string "wait" there, not the string "true".
"Streams" are the headline feature of the Codex. They allow you to implement parallel execution of multiple command lists, giving you a lot more control over timing than a single command list with only limited support for simultaneous commands. At its most basic, this allows you to run almost any commands simultaneously, although there are some perhaps-unintuitive restrictions (see Vanilla Command Notes for details).
The quick overview is that you declare a stream and give it a list of commands. At that time, the stream begins executing, but does not block the main command list, which continues from the command following the stream. Since a stream is independent of any other command list, you can use blocking and non-blocking commands as you desire within it, as if it were its own main event script.
When a stream exhausts its command list, it becomes idle. You can restart it if desired, or overwrite it with new commands, or issue a command from any other stream that will wait for it to reach this state.
The power this gives you might not be obvious, so let's consider an example:
(example to follow when fully baked)
ichortower.ECC_StreamStart <id>
ichortower.ECC_StreamBegin <id> (alias)
This command declares a stream. When it is encountered, it immediately scans
ahead through the command list looking for a matching StreamEnd command, and
gives the intervening commands over to the new stream to begin executing (they
will not actually execute until the stream's event loop reaches them).
The id value can be any string, but it must not already be in use by another
active stream within this event: attempting to reuse an active id will error
out, and the new stream will be discarded. Once a stream has completed or
halted, it becomes inactive, and you may reuse its id with a new StreamStart
command to replace it; just make sure you don't have any future commands that
need to control the old stream!
After starting a stream, you use the same id you gave it in order to issue
commands from any other stream (StreamAwait, StreamHalt, etc.).
ichortower.ECC_StreamEnd
This command does not do anything on its own (in fact, executing it is an error, since it means your Starts and Ends are not balanced). It is merely a signal that the list of commands for a new stream is over.
There is no id argument: each stream takes the first balanced StreamEnd it
encounters, in order to allow nesting streams and prevent them from crossing
command list boundaries.
ichortower.ECC_StreamAwait <id> [id...]
This command takes one or more stream ids and blocks until they are all
complete (they have run out of commands and are sitting idle, or they have
been halted by the StreamHalt command).
Use this to guarantee that a particular stream (or streams) has finished before proceeding. For example, if a stream is making an actor move to a particular spot, you can await it in order to be sure that actor has arrived. Either they will already be there, or you'll pause until they are.
A stream cannot await itself (this will cause an error); but there is not yet any protection against circular awaiting, so be careful not to do that.
Defunct. See Pause.
ichortower.ECC_StreamHalt <id> [id...]
This command terminates the streams specified in the arguments. A halted stream has its command index set to beyond the end of its command list, and has its state forcibly set to idle. As a result, it will immediately satisfy any other stream that is awaiting it, and its id becomes available again for reuse.
A stream can halt itself, although I don't know what use case that has.
ichortower.ECC_StreamRestart <id> [id...]
This command restarts the specified streams. A restarted stream has its command index set to 0 and its idle state forcibly unset.
This is intended for use on completed or halted streams, but it should work on actives ones as well.
A stream can restart itself (but see StreamLoop).
ichortower.ECC_StreamLoop
A shortcut version of StreamRestart which works only on the current stream
and restarts it by setting its command index to 0.
You can also use this in the main command list, but I don't see much use for
it until goto arrives in 1.6.16 (and when it arrives, you should just use
it instead of this).
Some vanilla commands do not work as expected when used in streams (see Vanilla Command Notes for more details). For this reason, the Codex includes replacements for some of them.
In general, you do not have to use these commands yourself: the Codex will automatically replace the vanilla commands with the stream-safe versions when a new stream contains them. However, they do sometimes have additional features, so maybe you will find them useful.
ichortower.ECC_Emote <actor> <number> [wait]
A replacement for emote, which can misbehave when used in a stream. It uses
the same arguments as the vanilla command, except for the last one: by default,
this command does not wait for the emote to finish before proceeding. To
obtain that behavior, give the optional argument wait.
ichortower.ECC_FaceDirection <actor> <direction> [delay|duration]
A replacement for faceDirection, which can misbehave when used in a stream.
The actor and direction arguments are the same as vanilla, but the optional
extra argument is different: it can be either the string delay, which causes
the default vanilla delay of 500 milliseconds, or an integer, which will cause
a delay of that many milliseconds. Like emote, if the delay argument is
omitted, the default behavior is not to block after facing the actor.
ichortower.ECC_Pause <int> [int...]
A replacement for pause, which does not work in streams. Unlike pause,
this one accepts any number of integer arguments as pause durations, and will
choose one of them at random.
These commands give you more flexibility when controlling actors (characters).
ichortower.ECC_ActorPathfind <actor> <x> <y> <facing> [wait]
This command tells any actor (farmer or NPC) to move to the given map coordinates, by using the pathfinder to figure out how to get there instead of relying on you giving them directions.
The <x> and <y> arguments are expressed in map tiles, just like with move
and advancedMove, and will accept a few different formats:
- a plain positive integer (e.g.
12,38): absolute coordinate - plain
0, or+or-with an integer (e.g.0,+2,-8): relative coordinate awith an integer (e.g.a42,a0,a-1000): absolute coordinate
So, for example, ichortower.ECC_ActorPathfind Emily +2 +6 2 tells Emily to
find her way to the spot 2 tiles right and 6 tiles down from where she is
currently, then face down. Likewise, ichortower.ECC_ActorPathfind farmer 22 15 1
tells the farmer to go to the absolute coordinates (22, 15) and face right.
The a prefix is unlikely to be a common need, but it allows you to specify an
absolute zero or negative coordinate, since those would otherwise be interpreted
as relative.
This is very useful if you have been using streams and an actor has been
halted at an unknowable point along a looping advancedMove, or any similar
situation where you don't know where they are exactly but want them to reach
a fixed point. Or if you just don't want to do the math yourself!
Note that while the farmer will consider NPCs to be obstacles and will navigate around them (using the positions they were in when the route was calculated), NPCs will walk through each other. I may address this in the future, if I can figure out how.
ichortower.ECC_ActorAwaitMovement <actor> [actor...]
This command blocks until all named event actors have completed their current
movements. This works a lot like vanilla's waitForAllStationary (all actors)
and proceedPosition (one actor only), but it allows any number of actors,
and it checks for ongoing movement a bit differently.
In particular, this command does not consider a character in a pause step
during an advancedMove to have stopped (waitForAllStationary and
proceedPosition both do this). This means that using this command to wait for
a looping advancedMove will block forever, so do not do this without a plan to
call ActorHalt from some other stream.
ichortower.ECC_ActorAwaitAnimation <actor> [actor...]
This command is just like ActorAwaitMovement, except instead of blocking to
wait for movement to finish, it waits for animations. This accounts for sprite
animations using the animate command, as well as emote.
Note that just like ActorAwaitMovement, this will block forever if it is
awaiting a looping animation, so be careful not to do that without a way to
use ActorHalt.
ichortower.ECC_ActorHalt [next|waitnext] <actor> [actor...]
This command stops the movement and animation of all named actors, and removes any NPCControllers that may have been puppeting them.
The first argument is optional and can be one of two special strings in order to change the behavior (both case-insensitive):
next: actors will be allowed to finish the current leg of their movement before halting.waitnext: asnext, but this command will also block until the movements complete. this is done by inserting anActorAwaitMovementcommand.
In general, it is best to use the optional argument, in order to have your characters stop squarely on tiles, instead of stopping in between them (being "off the grid" can cause problems with positioning).
Likewise, in general I advise using waitnext over next, since some moves
may not halt correctly without ActorAwaitMovement to help unstick them.
But if you are already awaiting the movement in another stream, next will
suffice.
These commands are intended to replace viewport move with a version that I
find more sensible: it uses tile units instead of pixels per frame, and you
can queue movements as well as wait for them to complete.
The other forms of vanilla's viewport command should still serve; they work
well.
ichortower.ECC_ViewportMove <x> <y> <duration> [override] [wait]
This command sets up a viewport movement.
x and y are given in map tiles, and can accept a few different formats,
exactly the same as ActorPathfind.
- a plain positive integer (e.g.
12,38): absolute coordinate - plain
0, or+or-with an integer (e.g.0,+2,-8): relative coordinate awith an integer (e.g.a42,a0,a-1000): absolute coordinate
So you could move the viewport to (14, 20) by giving 14 20, or you could move
it 3 tiles down from its current position by giving 0 +3.
duration is in milliseconds and determines how long the move will take to
complete.
By default, a viewport move will be queued behind any ongoing moves, and the
command will not block. The optional arguments override and wait can be
given to change this behavior: override will cause the existing queue to be
emptied before starting this move, and wait will cause the command to block
until the queue has been finished.
Note: the movements set up by this command are totally separate from how
vanilla's viewport move moves the viewport. Do not mix and match them.
ichortower.ECC_ViewportAwait
This command blocks until all queued viewport moves have completed.
ichortower.ECC_ViewportHalt
This command immediately halts any ongoing viewport moves and empties the viewport move queue.
Although vanilla has the ambientLight command which lets you set the ambient
light color and intensity at any time, it is merely immediate and there is no
way to transition smoothly. These commands give you the ability to fade it
gradually and queue the operations, just like with the viewport control
commands; you can use this to help simulate things like sunsets.
ichortower.ECC_AmbientLightShift <red> <green> <blue> <duration> [override] [wait]
This command sets up a gradual ambient light shift.
red, green, and blue should be integers from 0 to 255, representing the
RGB values of the color that will be subtracted from white to generate the
game tint, just as it is with vanilla's ambientLight command.
Note: The game's draw code has a special case for when all three values of
the ambient light color are 255 (which would normally mean full darkness): this
is treated as full brightness instead, the same as 0 0 0. I recommend
avoiding this value, and using 254 254 254 instead if you need pitch black;
it's close enough and won't cause flashing in and out of darkness.
duration is in milliseconds and determines how long the shift will take to
complete.
Like with ViewportMove, by default, the shift will be queued behind any
ongoing shifts, and the command will not block. Just like that command, you can
give the optional argument override to first empty the queue before starting,
and you can give the optional argument wait to block until the queue empties.
ichortower.ECC_AmbientLightAwait
This command blocks until all queued ambient light shifts have completed.
ichortower.ECC_AmbientLightHalt
This command immediately halts all ongoing ambient light shifts and empties the light shift queue.
ichortower.ECC_WorldAdvanceTime <hhmm>
This command causes world time to pass when the event finishes. In addition to advancing the game clock, machines are given processing time and all NPCs are automatically advanced along their daily schedules to be ready for their next move (spouses with no schedule will be warped to bed if the target time is after 2200).
Give the time as an integer between 600 and 2600, but it must be after the current game time or you will get an error.
In multiplayer, this command has no effect: events don't freeze time in multiplayer, so time will pass just by watching it.
Note: the machine processing and NPC advancing will happen immediately when this command executes, but the time change will be delayed until the event actually ends.
Note: If possible, you should find a way to warn players ahead of time that you plan to use this command, and ideally give them a chance to avoid it. They are likely accustomed to events taking no time, and may have plans for their day which you may ruin if you surprise them with this.
This command is also available as a trigger action, using the same name, and should work anywhere actions are accepted (except in multiplayer, as above). When triggered outside of an event, the time change will be immediate.
ichortower.ECC_TemporaryMapTiles (<layer> <x> <y> <sheet> <index>)+
This command temporarily replaces map tiles on the current map, reverting them to their previous state (by reloading the map) when the event ends.
Use layer, x, and y to specify what tile to change. Sheet and index are to
tell what to change that tile to. To remove a tile, use -1 for index; in this
case, sheet will be ignored, so you can pass something meaningless like -.
ichortower.ECC_TemporaryMapOverride (<asset> <x> <y>)+
This command temporarily applies one or more map overrides to the current location. The overrides will be removed (and the map reloaded) when the event ends.
The asset name is expected to be under Maps/, so to apply the map asset at
Maps/foo/bar, you should specify just foo/bar. The x and y values are map
tile coordinates of where to overlay it (the top-left corner, just like you
would specify in e.g. a Content Patcher pack).
ichortower.ECC_If <Game state query>
ichortower.ECC_ElseIf <Game state query>
ichortower.ECC_Else
ichortower.ECC_EndIf
These commands define a set of conditional command blocks. Just like a conventional programming language, the conditions are checked in order, and as soon as one is satisfied, that block is executed and the others are discarded without even being evaluated.
You can put any number of event commands between the control commands, e.g.:
ichortower.ECC_If PLAYER_NPC_RELATIONSHIP Current Abigail married
emote Abigail 16
speak Abigail "That's so rude! You're talking about my ${husband^wife^spouse}$!$a"
emote Pierre 40
ichortower.ECC_EndIf
In this example, the three commands (emote/speak/emote) are only executed if the player is married to Abigail. But you can add more blocks, too:
ichortower.ECC_If PLAYER_NPC_RELATIONSHIP Current Abigail married
emote Abigail 16
speak Abigail "That's so rude! You're talking about my ${husband^wife^spouse}$!$a"
emote Pierre 40
ichortower.ECC_ElseIf PLAYER_NPC_RELATIONSHIP Current Abigail dating
emote Abigail 12
speak Abigail "Hey! I happen to like that person quite a lot!$a"
ichortower.ECC_Else
speak Abigail "Who? Farmer @?$u"
ichortower.ECC_EndIf
In this case, you'll get the first block (If..ElseIf) if married, the
second block (ElseIf..Else) if dating, and the third block
(Else..EndIf) for any other relationship.
You can have as many ElseIf blocks as you like, but only one Else, and
the Else must come last. Violations of this order will parse, but you will
get a warning about the blocks not being reachable.
You should be able to nest these commands, but I haven't tested that yet.
Note: the game state queries that drive this are evaluated when the blocks are parsed for execution, so they are "real time" and may reflect changes to game state that have occurred earlier in the event.
This command and game state query are intended for use with the Conditional Execution blocks (see above). You can use them to store and manipulate integer or string values, then change your event's behavior accordingly.
ichortower.ECC_VarSet <name> <value|expression>
This command sets a user-defined variable to the value or expression that follows it. You can use it to save a value for later (say, the player's choice in some situation), or to keep track of a running total, or things like that.
When picking a variable name, there are a few restrictions:
- it must be alphanumeric only ([a-zA-Z0-9])
- it must contain at least one letter ([a-zA-Z])
- it must not start with 'ECC' (case-insensitive; this is to prevent collisions with planned future features)
Other than those, you can choose whatever you like. Variable names are case-sensitive.
The variables are purely local to the event, and are purged when the event ends, so you need not worry about collisions (except with yourself). However, they are freely accessible from any stream, to both read and write.
The expression parsing and evaluation proceeds as you might expect, including
operator precedence, but note that it supports only integers and strings (no
floating-point numbers!): the . symbol is a low-priority concatenate operator
and may cause confusing results if used by mistake.
Note: every resulting value that this command generates is serialized to a string, including during the parsing and evaluation steps. You might not need to know this, but if something weird happens, maybe that will help you figure it out.
See the following table for what symbols are supported. They are listed in precedence order, although the variable and string notes are not ranked (so, excluding those, parentheses are highest, then exponent, and so on).
| Symbol | Examples | Explanation |
|---|---|---|
|
plain name |
|
To reference a variable in an expression, just use its name as-is (but remember that it is case-sensitive). When evaluated, it will be replaced with the value you last stored in it. A variable must be set before referencing it, or you will get an evaluation error. |
|
|
|
Use |
|
|
|
Use parentheses to enclose operations and increase their precedence. In the
example, |
|
|
|
The exponent operator will work only on integers and will not accept strings. Be careful not to exceed the positive integer limit of 2^31-1, or this will return the negative integer limit. |
|
|
|
Multiply Note: Backslash Reminder: floating-point is not supported, so |
|
|
|
Add In addition to binary operation, |
|
|
|
Compare two values for equality ( Note for programmers: You can use double-equals |
|
|
|
Like |
|
|
|
The Concatenate ( Note: this is the lowest-precedence operator, so deploy parentheses if needed. Note: if you try to use a decimal number (e.g. |
For example, you could use:
ichortower.ECC_VarSet myvalue 4
... which would set a variable called "myvalue" to the value 4. Or:
ichortower.ECC_VarSet myvalue myvalue + 1
... to increment its current value. You can combine a lot of stuff:
ichortower.ECC_VarSet myvalue (1+4) * (12-5) . ' points'
... which does the math as you would expect (5 * 7), then concatenates it with the text to generate the string "35 points".
ichortower.ECC_VAR_QUERY <value|expression>
This game state query uses the same expression parsing as VarSet, above, but
instead of storing the result into a named variable, it evaluates the
expression and returns true or false: false if it yields the string "false"
(case-insensitive) or the value 0, and true otherwise. Note that this means
that an empty string "" and maybe some other unintuitive results will
evaluate to true.
This is intended for use with If/ElseIf/Else/EndIf. You can use it in
other contexts if you like, but I doubt that it is useful to do so: outside of
an event, you won't be able to access any variables.
[ichortower.ECC_VarEval <value|expression>]
This is a token resolver, for use in
tokenizable strings.
This lets you evaluate your event variables inside parsed text: at the moment,
the only use I am aware of is in the speak command (but remember to use that
only on the main command list).
I suppose you could use it to do basic math in dialogue elsewhere, but accessing the event vars is the real purpose.
There are some vanilla commands which cause problems when used in streams outside of the main command list. Whenever possible, the Codex will automatically replace them with its own alternatives when they appear in a new stream's command list, so in most cases you should not need to know about these behaviors and can keep using vanilla commands the way you are used to; but note that any errors you get in your log will reference the replacement command string.
The most common problem is that something started by a particular command directly advances the main command list index from somewhere else in the codebase. This both causes the main list to jump forward at inappropriate times and also leaves the stream softlocked, since its index remains where it was.
The commands and their behavior are documented here. The ones without adequate substitutes are so noted.
When not passing the optional true to avoid blocking, this is hardcoded to
advance the main command list when the emote expires. In a stream, your
emote command will be replaced as follows:
'emote <actor> <num>' -> 'ichortower.ECC_Emote <actor> <num> wait'
'emote <actor> <num> true' -> 'ichortower.ECC_Emote <actor> <num>'
When not passing the optional true to avoid blocking, this command uses the
global pause timer (see pause, below) to implement its delay. In a stream,
your faceDirection command will be replaced as follows:
'faceDirection <actor> <dir>' -> 'ichortower.ECC_FaceDirection <actor> <dir> delay'
'faceDirection <actor> <dir> true' -> 'ichortower.ECC_FaceDirection <actor> <dir>'
To proceed after the dialogue box closes, this command relies on DialogueBox
being hardcoded to advance the main command list. To make matters worse, it
also uses the global pause timer to insert a short delay, so it is not suitable
for use in streams.
There is no substitute available at this time. If used in a stream, it will be replaced as follows:
'message <text>' -> 'ichortower.ECC_Message <text>'
... but executing that command will cause an error (this is deliberate, in order to warn you that your script is malformed).
This command uses a global field (Game1.pauseTime) which is hardcoded to
advance the main command list when it expires. In a stream, your pause
command will be replaced as follows:
'pause <ms>' -> 'ichortower.ECC_Pause <ms>'
In order to execute the embedded scripts, this command injects them hardcodedly into the main command list. There is no substitute available at this time.
Just like message, this uses DialogueBox and there is no substitute for its
hardcoding at this time. If used in a stream, it will be replaced as follows:
'speak <actor> <dialogue>' -> 'ichortower.ECC_Speak <actor> <dialogue>'
... but executing that command will cause an error, just like with message.
When used with NPC actors, this command behaves as expected in any stream. When used with a farmer, the speed change is local to the stream, and movements in other streams will not see the value. There is no substitute, so you must be aware of the behavior when using this command.