diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..b57fd4b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.sc linguist-language=Python +*.scl linguist-language=Python diff --git a/.github/workflows/build-on-pr.yml b/.github/workflows/build-on-pr.yml new file mode 100644 index 0000000..ad8e998 --- /dev/null +++ b/.github/workflows/build-on-pr.yml @@ -0,0 +1,28 @@ +# This workflow will build a Java project with Gradle +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle + +name: Pull Request Builds + +on: [pull_request] + +jobs: + Build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 21 + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: 21 + cache: 'gradle' + - name: Grant execute permission to gradlew + run: chmod +x gradlew + - name: Build with Gradle + run: ./gradlew build + - uses: actions/upload-artifact@v3 + with: + name: "Compiled artifacts for Pull Request #${{github.event.number}}" + path: build/libs diff --git a/.github/workflows/devbuild.yml b/.github/workflows/devbuild.yml new file mode 100644 index 0000000..15dbeaf --- /dev/null +++ b/.github/workflows/devbuild.yml @@ -0,0 +1,28 @@ +# This workflow will build a Java project with Gradle +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle + +name: Development Builds + +on: [push] + +jobs: + Build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 21 + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: 21 + cache: 'gradle' + - name: Grant execute permission to gradlew + run: chmod +x gradlew + - name: Build with Gradle + run: ./gradlew build + - uses: actions/upload-artifact@v3 + with: + name: Compiled artifacts for ${{ github.sha }} + path: build/libs diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 0000000..6872048 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,192 @@ +name: Publish Release + +on: + release: + types: [published] +jobs: + Get-Properties: + runs-on: ubuntu-latest + outputs: + release-type: ${{ steps.type.outputs.release-type }} + carpet-version: ${{ steps.properties.outputs.mod_version }} + minecraft-version: ${{ steps.properties.outputs.minecraft_version }} + curse-versions: ${{ steps.properties.outputs.release-curse-versions }} + matrix-exclude-branch: ${{ steps.processmatrix.outputs.matrix-to-exclude }} + extra-branch-name: ${{ steps.properties.outputs.release-extra-branch-name }} + extra-branch-curse-version: ${{ steps.properties.outputs.release-extra-curse-version }} + steps: + - name: Checkout the sources + uses: actions/checkout@v3 + - name: Determine release type + id: type + run: | + if ${{ github.event.release.prerelease }}; then + echo "release-type=beta" >> $GITHUB_OUTPUT + else + echo "release-type=release" >> $GITHUB_OUTPUT + fi + - name: Read relevant fields from gradle.properties + id: properties + run: | # From christian-draeger/read-properties, using the action makes it extremely messy until christian-draeger/read-properties#2 + path='./gradle.properties' + for property in mod_version minecraft_version release-curse-versions release-extra-branch release-extra-branch-name release-extra-curse-version + do + result=$(sed -n "/^[[:space:]]*$property[[:space:]]*=[[:space:]]*/s/^[[:space:]]*$property[[:space:]]*=[[:space:]]*//p" "$path") + echo "$property: $result" + echo "$property=$result" >> $GITHUB_OUTPUT + done + - name: Process property for matrix + id: processmatrix + run: | + if ! ${{ steps.properties.outputs.release-extra-branch }}; then + echo "matrix-to-exclude=Snapshots" >> $GITHUB_OUTPUT + fi + - uses: actions/github-script@v6 + env: + READ_VERSION: ${{ steps.properties.outputs.mod_version }} + with: + script: | + const { READ_VERSION } = process.env; + console.log('Read version is: ' + READ_VERSION); + let releases = (await github.rest.repos.listReleases({ + owner: context.repo.owner, + repo: context.repo.repo + })).data; + console.log('Previous release was: ' + releases[1].name); + for (let release of releases.slice(1)) { + if (release.name.includes(READ_VERSION)) + core.setFailed('Version number is the same as a previous release!') + } + Build-And-Publish: + runs-on: ubuntu-latest + needs: [Get-Properties] + strategy: + matrix: + branch: [Release, Snapshots] + exclude: + - branch: ${{ needs.Get-Properties.outputs.matrix-exclude-branch }} + steps: + - name: Get info from branch to run + id: getbranchinfo + run: | + if ${{ matrix.branch == 'Snapshots'}}; then + echo "branchname=${{ needs.Get-Properties.outputs.extra-branch-name }}" >> $GITHUB_OUTPUT + echo "version=${{ needs.Get-Properties.outputs.extra-branch-curse-version }}" >> $GITHUB_OUTPUT + echo "curse-versions=${{ needs.Get-Properties.outputs.extra-branch-curse-version }}" >> $GITHUB_OUTPUT + else + echo "version=${{ needs.Get-Properties.outputs.minecraft-version }}" >> $GITHUB_OUTPUT + echo "curse-versions=${{ needs.Get-Properties.outputs.curse-versions }}" >> $GITHUB_OUTPUT + fi + - name: Checkout the sources + uses: actions/checkout@v3 + with: + ref: ${{ steps.getbranchinfo.outputs.branchname }} + - name: Set up JDK 21 + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: 21 + cache: 'gradle' + - name: Grant execute permission to gradlew + run: chmod +x gradlew + - name: Build with Gradle + run: ./gradlew build + - name: Find correct JAR + id: findjar + run: | + output="$(find build/libs/ ! -name "*-dev.jar" ! -name "*-sources.jar" -type f -printf "%f\n")" + echo "jarname=$output" >> $GITHUB_OUTPUT + - name: Save build artifacts in the action + uses: actions/upload-artifact@v3 + with: + name: Artifacts for ${{ matrix.branch }} + path: build/libs + - name: Upload to the Github release + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: build/libs/${{ steps.findjar.outputs.jarname }} + asset_name: ${{ steps.findjar.outputs.jarname }} + asset_content_type: application/java-archive + - name: Upload to Curseforge + uses: itsmeow/curseforge-upload@v3 + with: + token: ${{ secrets.CF_API_TOKEN }} + project_id: 349239 + game_endpoint: minecraft + file_path: build/libs/${{ steps.findjar.outputs.jarname }} + changelog_type: markdown + changelog: ${{ github.event.release.body }} + display_name: Carpet v${{ needs.Get-Properties.outputs.carpet-version }} for ${{ steps.getbranchinfo.outputs.version }} + game_versions: 7499,4458,${{ steps.getbranchinfo.outputs.curse-versions }} #Fabric,Java 8,[version (s) for the branch] + release_type: ${{ needs.Get-Properties.outputs.release-type }} + - name: Ask Gradle to publish + run: ./gradlew publish + - name: Save publish folder in action's artifacts # Remove when automated + uses: actions/upload-artifact@v3 + with: + name: Maven publishing artifacts for ${{ matrix.branch }} + path: publish/carpet/fabric-carpet/ + Publish-To-Discord: + runs-on: ubuntu-latest + needs: [Build-And-Publish] + steps: + - name: Publish to discord + uses: Crec0/announce-n-crosspost@v1 + with: + bot-token: ${{ secrets.DISCORD_BOT_TOKEN }} + channel: '897934715200339999' + content: | + **${{ github.event.release.name }}** has been released! + + ${{ github.event.release.body }} + + Get it on Github Releases: <${{ github.event.release.html_url }}> + Or on CurseForge + Merge-Scarpet-Docs: + runs-on: ubuntu-latest + steps: + - name: Checkout the sources + uses: actions/checkout@v3 + with: + ref: master + - name: Merge docs + run: | + ./mergedoc.sh + - name: Commit merged docs + continue-on-error: true + run: | + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + git commit -am "Merge docs for '${{ github.event.release.name }}'" || exit 0 + git push + Update-Rules-Wiki: + runs-on: ubuntu-latest + steps: + - name: Checkout Carpet sources + uses: actions/checkout@v3 + - name: Set up JDK 21 + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: 21 + cache: 'gradle' + - name: Checkout wiki + uses: actions/checkout@v3 + with: + repository: ${{github.repository}}.wiki + path: wiki + - name: Run rule printer into the wiki page + run: | + chmod +x gradlew + ./gradlew runServer --args="-- -carpetDumpRules -dumpPath ../wiki/Current-Available-Settings.md" + - name: Commit updated wiki page + continue-on-error: true + run: | + cd wiki + git config --global user.name 'github-actions-bot' # Releases don't have valid commiter info :( + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + git commit -am "Update wiki for '${{ github.event.release.name }}'" || exit 0 + git push diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..db0d4ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +# gradle + +.gradle/ +build/ +out/ +lout/ +classes/ +publish/ + +# git + +.shelf/ + +# idea + +.idea/ +*.iml +*.ipr +*.iws + +# eclipse +*.launch + +# vscode + +.settings/ +.vscode/ +bin/ +.classpath +.project +*.code-workspace + +# fabric + +run/ +logs/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..467d914 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 gnembon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e4a03ab --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ + + +# Fabric Carpet + +[![Development Builds](https://github.com/gnembon/fabric-carpet/actions/workflows/devbuild.yml/badge.svg)](https://github.com/gnembon/fabric-carpet/actions/workflows/devbuild.yml) +[![CurseForge downloads](http://cf.way2muchnoise.eu/full_349239_downloads.svg)](https://www.curseforge.com/minecraft/mc-mods/carpet) +[![Modrinth downloads](https://img.shields.io/modrinth/dt/carpet?label=Modrinth%20downloads&logo=modrinth)](https://modrinth.com/mod/carpet) +[![GitHub downloads](https://img.shields.io/github/downloads/gnembon/fabric-carpet/total?label=Github%20downloads&logo=github)](https://github.com/gnembon/fabric-carpet/releases) +[![GitHub contributors](https://img.shields.io/github/contributors/gnembon/fabric-carpet?label=Contributors&logo=github)](https://github.com/gnembon/fabric-carpet/graphs/contributors) +[![Discord](https://badgen.net/discord/online-members/gn99m4QRY4?icon=discord&label=Discord&list=what)](https://discord.gg/gn99m4QRY4) + +Cause all carpets are made of fabric? +This is the [Fabric](https://fabricmc.net/) version of Carpet Mod, for Minecraft 1.14.X, 1.15.X, 1.16.X and above. + +Carpet Mod is a mod for vanilla Minecraft that allows you to take full control of what matters from a technical perspective of the game. + +* Test your farms over several hours in only a few minutes using [`/tick warp`](https://github.com/gnembon/fabric-carpet/wiki/Commands#usage-tick-warp-ticks-cmd), as fast as your computer can +* ...and then see a detailed breakdown of the items they produce using [`hopperCounters`](https://github.com/gnembon/fabric-carpet/wiki/Current-Available-Settings#hoppercounters) +* See the server mobcap, TPS, etc. update live with [`/log`](https://github.com/gnembon/fabric-carpet/wiki/Commands#log) +* Let pistons push block entities (ie. chests) with [`movableBlockEntities`](https://github.com/gnembon/fabric-carpet/wiki/Current-Available-Settings#movableblockentities) +* [Fix](https://github.com/gnembon/fabric-carpet/wiki/Current-Available-Settings#leadfix) [many](https://github.com/gnembon/fabric-carpet/wiki/Current-Available-Settings#portalsuffocationfix) [things](https://github.com/gnembon/fabric-carpet/wiki/Current-Available-Settings#unloadedentityfix) + +# Carpet mod ecosystem + +For core carpet functionality, this is the right place. Check available downloads on the [release page](https://github.com/gnembon/fabric-carpet/releases) + + + +## carpet-extra + +Check [carpet-extra](https://github.com/gnembon/carpet-extra/) add-on mod for more whacky and crazy features, including autocrafting, block-placing dispensers, and even chicken-shearing! + +## scarpet app store + +If you want to browse or contribute to the scarpet app store check available apps, go [here](https://github.com/gnembon/scarpet), its free! + +## quick-carpet + +Have problems with the recent snapshot or someone is slacking with releasing the base carpet mod, but you need some basic carpet functionality, check the minimal build of [quick-carpet](https://github.com/gnembon/quick-carpet/releases). It hopefully works just fine. You will not be able to add any extensions or use scarpet apps on this one though. + +## Other community-based extensions to carpet + +Here is a [list of other carpet extensions](https://github.com/gnembon/fabric-carpet/wiki/List-of-Carpet-extensions) created by the community. + +Everybody can create their own carpet features or extend scarpet language with some new API, by creating a carpet extension using this [carpet extension mod template](https://github.com/gnembon/fabric-carpet-extension-example-mod). + +# How? Hwat? + +Follow instructions for all other fabric mods in https://fabricmc.net/use/ and dump [carpet...jar](https://github.com/gnembon/fabric-carpet/releases) in `mods` folder along with other compatible mods. + +See the [mod showcase](https://www.youtube.com/watch?v=Lt-ooRGpLz4) on youtube for an explanation of every setting and command, and check the List of all currently available settings on the wiki for an updated list of every setting. + +# Carpet Mod Settings +See [List of all currently available settings][settings] on the wiki + +[settings]: https://github.com/gnembon/fabric-carpet/wiki/Current-Available-Settings + +For previous Minecraft versions: 1.13 check [gnembon/carpetmod](https://github.com/gnembon/carpetmod) and for 1.12 check [gnembon/carpetmod112](https://github.com/gnembon/carpetmod112). diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..678f16a --- /dev/null +++ b/build.gradle @@ -0,0 +1,83 @@ +plugins { + id 'fabric-loom' version '1.6-SNAPSHOT' + id 'io.github.juuxel.loom-quiltflower' version '1.7.3' + id 'maven-publish' +} + +sourceCompatibility = JavaVersion.VERSION_21 +targetCompatibility = JavaVersion.VERSION_21 + +archivesBaseName = project.archives_base_name +version = project.minecraft_version+'-'+project.mod_version+'+v'+new Date().format('yyMMdd') +group = project.maven_group + +loom { + accessWidenerPath = file("src/main/resources/carpet.accesswidener") + runtimeOnlyLog4j = true +} + +dependencies { + // To change the versions see the gradle.properties file + minecraft "com.mojang:minecraft:${project.minecraft_version}" + mappings loom.officialMojangMappings() + modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" + + // Fabric API. This is technically optional, but you probably want it anyway. + //modImplementation "net.fabricmc:fabric:${project.fabric_version}" + //modCompileOnly fabricApi.module("fabric-rendering-v1", project.fabric_version) + + // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. + // You may need to force-disable transitiveness on them. + compileOnly "com.google.code.findbugs:jsr305:${project.jsr305_version}" +} + +processResources { + inputs.property "version", project.version+'+v'+new Date().format('yyMMdd') + + filesMatching("fabric.mod.json") { + expand "version": project.mod_version+'+v'+new Date().format('yyMMdd') + } +} + + +tasks.withType(JavaCompile).configureEach { + // ensure that the encoding is set to UTF-8, no matter what the system default is + // this fixes some edge cases with special characters not displaying correctly + // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html + // If Javadoc is generated, this must be specified in that task too. + it.options.encoding = "UTF-8" + + // Minecraft 1.20.5 upwards uses Java 21. + it.options.release = 21 +} + +java { + // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task + // if it is present. + // If you remove this line, sources will not be generated. + withSourcesJar() +} + +jar { + from("LICENSE") { + rename { "${it}_${project.archivesBaseName}"} + } +} + +// configure the maven publication +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + } + } + + // Select the repositories you want to publish to + // To publish to maven local, no extra repositories are necessary. Just use the task `publishToMavenLocal`. + repositories { + // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. + maven { + url "$projectDir/publish" + } + } +} diff --git a/docs/scarpet/Documentation.md b/docs/scarpet/Documentation.md new file mode 100644 index 0000000..d56ad07 --- /dev/null +++ b/docs/scarpet/Documentation.md @@ -0,0 +1,42 @@ +# Scarpet Language Documentation + +## Language Specification + +[Overview](/docs/scarpet/language/Overview.md) + +[Variables and Constants](/docs/scarpet/language/VariablesAndConstants.md) + +[Operators](/docs/scarpet/language/Operators.md) + +[Mathematical Functions](/docs/scarpet/language/Math.md) + +[System Functions](/docs/scarpet/language/SystemFunctions.md) + +[Loops and Higher Order Functions](/docs/scarpet/language/LoopsAndHigherOrderFunctions.md) + +[User Defined Functions and Control Flow](/docs/scarpet/language/FunctionsAndControlFlow.md) + +[Container Types](/docs/scarpet/language/Containers.md) + +## Minecraft API + +[Overview](/docs/scarpet/api/Overview.md) + +[Blocks and World Access](/docs/scarpet/api/BlocksAndWorldAccess.md) + +[Block Iterations](/docs/scarpet/api/BlockIterations.md) + +[Entities](/docs/scarpet/api/Entities.md) + +[Inventories](/docs/scarpet/api/Inventories.md) + +[Events](/docs/scarpet/api/Events.md) + +[Scoreboard manipulation](/docs/scarpet/api/Scoreboard.md) + +[Auxiliary Functions](/docs/scarpet/api/Auxiliary.md) + +[`/script` Command](/docs/scarpet/api/ScriptCommand.md) + +## [One Page](/docs/scarpet/Full.md) + diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md new file mode 100644 index 0000000..3c70c64 --- /dev/null +++ b/docs/scarpet/Full.md @@ -0,0 +1,6330 @@ +# Fundamental components of `scarpet` programming language. + +Scarpet (a.k.a. Carpet Script, or Script for Carpet) is a programming language +designed to provide the ability to write custom programs to run within Minecraft +and interact with the world. + +This specification is divided into two sections: this one is agnostic to any +Minecraft related features and could function on its own, and CarpetExpression +for Minecraft specific routines and world manipulation functions. + +# Synopsis + +
+script run print('Hello World!') ++ +or an OVERLY complex example: + +
+/script run + block_check(x1, y1, z1, x2, y2, z2, block_to_check) -> + ( + [minx, maxx] = sort([x1, x2]); + [miny, maxy] = sort([y1, y2]); + [minz, maxz] = sort([z1, z2]); + 'Need to compute the size of the area of course'; + 'Cause this language doesn\'t support comments in the command mode'; + xsize = maxx - minx + 1; + ysize = maxy - miny + 1; + zsize = maxz - minz + 1; + total_count = 0; + loop(xsize, + xx = minx + _ ; + loop(ysize, + yy = miny + _ ; + loop(zsize, + zz = minz + _ ; + if ( block(xx,yy,zz) == block_to_check, + total_count += ceil(rand(1)) + ) + ) + ) + ); + total_count + ); + check_area_around_closest_player_for_block(block_to_check) -> + ( + closest_player = player(); + [posx, posy, posz] = query(closest_player, 'pos'); + total_count = block_check( posx-8,1,posz-8, posx+8,17,posz+8, block_to_check); + print('There is '+total_count+' of '+block_to_check+' around you') + ) +/script invoke check_area_around_closest_player_for_block 'diamond_ore' ++ +or simply + +
+/script run print('There is '+for(rect(x,9,z,8,8,8), _ == 'diamond_ore')+' diamond ore around you') ++ +It definitely pays to check what higher level `scarpet` functions have to offer. + +# Programs + +You can think of an program like a mathematical expression, like `"2.4*sin(45)/(2-4)"` or `"sin(y)>0 & max(z, 3)>3"`. +Writing a program, is like writing a `2+3`, just a bit longer. + +## Basic language components + +Programs consist of constants, like `2`, `3.14`, `pi`, or `'foo'`, operators like `+`, `/`, `->`, variables which you +can define, like `foo` or special ones that will be defined for you, like `_x`, or `_` , which are specific to each +built in function, and functions with name, and arguments in the form of `f(a,b,c)`, where `f` is the function name, +and `a, b, c` are the arguments which can be any other expression. And that's all the parts of the language, so all +in all - sounds quite simple. + +## Code flow + +Like any other proper programming language, `scarpet` needs brackets, basically to identify where stuff begins and +where it ends. In the languages that uses much more complicated constructs, like Java, they tend to use all sort of +them, round ones to indicate function calls, curly to indicate section of code, square to access lists, pointy for +generic types etc... I mean - there is no etc, cause they have exhausted all the bracket options... + +`Scarpet` is different, since it runs everything based on functions (although its not per se a functional +language like lisp) only needs the round brackets for everything, and it is up to the programmer to organize +its code so its readable, as adding more brackets does not have any effect on the performance of the programs +as they are compiled before they are executed. Look at the following example usage of `if()` function: + +
+if(x<y+6,set(x,8+y,z,'air');plop(x,top('surface',x,z),z,'birch'),sin(query(player(),'yaw'))>0.5,plop(0,0,0,'boulder'),particle('fire',x,y,z)) ++ +Would you prefer to read + +
+if( x<y+6, + set(x,8+y,z,'air'); + plop(x,top('surface',x,z),z,'birch'), + sin(query(player(),'yaw'))>0.5, + plop(0,0,0,'boulder'), + particle('fire',x,y,z) +) ++ +Or rather: + +
+if +( x<y+6, + ( + set(x,8+y,z,'air'); + plop(x,top('surface',x,z),z,'birch') + ), + // else if + sin(query(player(),'yaw'))>0.5, + ( + plop(0,0,0,'boulder') + ), + // else + particle('fire',x,y,z) +) ++ +Whichever style you prefer it doesn't matter. It typically depends on the situation and the complexity of the +subcomponents. No matter how many whitespaces and extra brackets you add - the code will evaluate to exactly the +same expression, and will run exactly the same, so make sure your programs are nice and clean so others don't +have problems with them + +## Functions and scoping + +Users can define functions in the form `fun(args....) -> expression` and they are compiled and saved for further +execution in this, but also subsequent calls of /script command, added to events, etc. Functions can also be + assigned to variables, +passed as arguments, called with `call('fun', args...)` function, but in most cases you would want to +call them directly by +name, in the form of `fun(args...)`. This means that once defined functions are saved with the world for +further use. For variables, there are two types of them, global - which are shared anywhere in the code, +and those are all which name starts with 'global_', and local variables which is everything else and those +are only visible inside each function. This also means that all the parameters in functions are +passed 'by value', not 'by reference'. + +## Outer variables + +Functions can still 'borrow' variables from the outer scope, by adding them to the function signature wrapped +around built-in function `outer`. It adds the specified value to the function call stack so they behave exactly +like capturing lambdas in Java, but unlike java captured variables don't need to be final. Scarpet will just +attach their new values at the time of the function definition, even if they change later. Most value will be +copied, but mutable values, like maps or lists, allow to keep the 'state' with the function, allowing them to +have memory and act like objects so to speak. Check `outer(var)` for details. + +## Code delivery, line indicators + +Note that this should only apply to pasting your code to execute with commandblock. Scarpet recommends placing +your code in apps (files with `.sc` extension that can be placed inside `/scripts` folder in the world files +or as a globally available app in singleplayer in the `.minecraft/config/carpet/scripts` folder and loaded +as a Scarpet app with the command `/script load [app_name]`. Scarpet apps loaded from disk should only +contain code, no need to start with `/script run` prefix. + +The following is the code that could be provided in a `foo.sc` app file located in world `/scripts` folder + +
+run_program() -> ( + loop( 10, + // looping 10 times + // comments are allowed in scripts located in world files + // since we can tell where that line ends + foo = floor(rand(10)); + check_not_zero(foo); + print(_+' - foo: '+foo); + print(' reciprocal: '+ _/foo ) + ) +); +check_not_zero(foo) -> ( + if (foo==0, foo = 1) +) ++ +Which we then call in-game with: + +
+/script load foo +/script in foo invoke run_program ++ +However the following code can also be input as a command, or in a command block. + +Since the maximum command that can be input to the chat is limited in length, you will be probably inserting your +programs by pasting them to command blocks or reading from world files, however pasting to command blocks will +remove some whitespaces and squish your newlines making the code not readable. If you are pasting a program that +is perfect and will never cause an error, I salute you, but for the most part it is quite likely that your program +might break, either at compile time, when its initially analyzed, or at execute time, when you suddenly attempt to +divide something by zero. In these cases you would want to get a meaningful error message, but for that you would +need to indicate for the compiler where did you put these new lines, since command block would squish them. For that, +place at the beginning of the line to let the compiler know where are you. This makes so that `$` is the only +character that is illegal in programs, since it will be replaced with new lines. As far as I know, `$` is not +used anywhere inside Minecraft identifiers, so this shouldn't hinder the abilities of your programs. + +Consider the following program executed as command block command: + +
+/script run +run_program() -> ( + loop( 10, + foo = floor(rand(_)); + check_not_zero(foo); + print(_+' - foo: '+foo); + print(' reciprocal: '+ _/foo ) + ) +); +check_not_zero(foo) -> ( + if (foo==0, foo = 1) +) ++ +Lets say that the intention was to check if the bar is zero and prevent division by zero in print, but because +the `foo` is passed as a variable, it never changes the original foo value. Because of the inevitable division +by zero, we get the following message: + +
+Your math is wrong, Incorrect number format for NaN at pos 98 +run_program() -> ( loop( 10, foo = floor(rand(_)); check_not_zero(foo); print(_+' - foo: '+foo); +HERE>> print(' reciprocal: '+ _/foo ) ));check_not_zero(foo) -> ( if (foo==0, foo = 1)) ++ +As we can see, we got our problem where the result of the mathematical operation was not a number +(infinity, so not a number), however by pasting our program into the command made it squish the newlines so +while it is clear where the error happened and we still can track the error down, the position of the error (98) +is not very helpful and wouldn't be useful if the program gets significantly longer. To combat this issue we can +precede every line of the script with dollar signs `$`: + +
+/script run +$run_program() -> ( +$ loop( 10, +$ foo = floor(rand(_)); +$ check_not_zero(foo); +$ print(_+' - foo: '+foo); +$ print(' reciprocal: '+ _/foo ) +$ ) +$); +$check_not_zero(foo) -> ( +$ if (foo==0, foo = 1) +$) ++ +Then we get the following error message + +
+Your math is wrong, Incorrect number format for NaN at line 7, pos 2 + print(_+' - foo: '+foo); + HERE>> print(' reciprocal: '+ _/foo ) + ) ++ +As we can note not only we get much more concise snippet, but also information about the line number and position, +so means its way easier to locate the potential problems problem + +Obviously that's not the way we intended this program to work. To get it `foo` modified via a function call, +we would either return it as a result and assign it to the new variable: + +
+foo = check_not_zero(foo); +... +check_not_zero(foo) -> if(foo == 0, 1, foo) ++ +.. or convert it to a global variable, which in this case passing as an argument is not required + +
+global_foo = floor(rand(10)); +check_foo_not_zero(); +... +check_foo_not_zero() -> if(global_foo == 0, global_foo = 1) ++ +## Scarpet preprocessor + +There are several preprocessing operations applied to the source of your program to clean it up and prepare for +execution. Some of them will affect your code as it is reported via stack traces and function definition, and some +are applied only on the surface. + - stripping `//` comments (in file mode) + - replacing `$` with newlines (in command mode, modifies submitted code) + - removing extra semicolons that don't follow `;` use as a binary operator, allowing for lenient use of semicolons + - translating `{` into `m(`, `[` into `l(`, and `]` and `}` into `)` + +No further optimizations are currently applied to your code. + +## Mentions + +LR1 parser, tokenizer, and several built-in functions are built based on the EvalEx project. +EvalEx is a handy expression evaluator for Java, that allows to evaluate +simple mathematical and boolean expressions. EvalEx is distributed under MIT licence. +For more information, see: [EvalEx GitHub repository](https://github.com/uklimaschewski/EvalEx) +# Variables and Constants + +`scarpet` provides a number of constants that can be used literally in scripts + +* `null`: nothing, zilch, not even false +* `true`: pure true, can act as `1` +* `false`: false truth, or true falsth, equals to `0` +* `pi`: for the fans of perimeters, its a perimeter of an apple pi of diameter 1\. About 3.14 +* `euler`: clever guy. Derivative of its exponent is goto 1\. About 2.72 + +Apart from that, there is a bunch of system variables, that start with `_` that are set by `scarpet` built-ins, +like `_`, typically each consecutive value in loops, `_i` indicating iteration, or `_a` like an accumulator +for `reduce` function. Certain calls to Minecraft specific calls would also set `_x`, `_y`, `_z`, indicating +block positions. All variables starting with `_` are read-only, and cannot be declared and modified in client code. + +## Literals + +`scarpet` accepts numeric and string liters constants. Numbers look like `1, 2.5, -3e-7, 0xff,` and are internally +represented primarily as Java's `double` but `scarpet` will try to trim trailing zeros as much as possible so if you +need to use them as integers or even longs - you can. Long values will also not loose their long precision in addition, +subtraction, negation and multiplication, however any other operation that is not guaranteed to return a long value +(like division) on a number even if it can be properly +represented as long, will make them convert to doubles. + +Strings use single quoting, for multiple reasons, but primarily to allow for +easier use of strings inside doubly quoted command arguments (when passing a script as a parameter of `/script fill` +for example), or when typing in jsons inside scarpet (to feed back into a `/data merge` command for example). +Strings also use backslashes `\` for quoting special characters, in both plain strings and regular expressions + +
+'foo' +print('This doesn\'t work') +nbt ~ '\\.foo' // matching '.' as a '.', not 'any character match' ++# Operators + +There is a number of operators you can use inside the expressions. Those could be considered generic type operators +that apply to most data types. They also follow standard operator precedence, i.e. `2+2*2` is understood +as `2+(2*2)`, not `(2+2)*2`, otherwise they are applied from left to right, i.e. `2+4-3` is interpreted +as `(2+4)-3`, which in case of numbers doesn't matter, but since `scarpet` allows for mixing all value types +the associativity would matter, and may lead to unintended effects: + +Operators can be unary - with one argument prefixed by the operator (like `-`, `!`, `...`), "practically binary" (that +clearly have left and right operands, like assignment `=` operator), and "technically binary" (all binary operators have left and +right hand, but can be frequently chained together, like `1+2+3`). All "technically binary" operators (where chaining makes sense) +have their functional counterparts, e.g. `1+2+3` is equivalent to `sum(1, 2, 3)`. Functional and operatoral forms are directly +equivalent - they actually will result in the same code as scarpet will optimize long operator chains into their optimized functional forms. + +Important operator is function definition `->` operator. It will be covered +in [User Defined Functions and Program Control Flow](docs/scarpet/language/FunctionsAndControlFlow.md) + +
+'123'+4-2 => ('123'+4)-2 => '1234'-2 => '134' +'123'+(4-2) => '123'+2 => '1232' +3*'foo' => 'foofoofoo' +1357-5 => 1352 +1357-'5' => 137 +3*'foo'-'o' => 'fff' +[1,3,5]+7 => [8,10,12] ++ +As you can see, values can behave differently when mixed with other types in the same expression. +In case values are of the same types, the result tends to be obvious, but `Scarpet` tries to make sense of whatever +it has to deal with + +## Operator Precedence + +Here is the complete list of operators in `scarpet` including control flow operators. Note, that commas and brackets +are not technically operators, but part of the language, even if they look like them: + +* Match, Get `~ :` +* Unary `+ - ! ...` +* Exponent `^` +* Multiplication `* / %` +* Addition `+ -` +* Comparison `> >= <= <` +* Equality `== !=` +* Logical And`&&` +* Logical Or `||` +* Assignment `= += <>` +* Definition `->` +* Next statement`;` +* Comma `,` +* Bracket `( )` + +### `Get, Accessor Operator :` + +Operator version of the `get(...)` function to access elements of lists, maps, and potentially other containers +(i.e. NBTs). It is important to distinguish from `~` operator, which is a matching operator, which is expected to +perform some extra computations to retrieve the result, while `:` should be straightforward and immediate, and +the source object should behave like a container and support full container API, +meaning `get(...)`, `put(...)`, `delete(...)`, and `has(...)` functions + +For certain operators and functions (get, put, delete, has, =, +=) objects can use `:` annotated fields as l-values, +meaning construct like `foo:0 = 5`, would act like `put(foo, 0, 5)`, rather than `get(foo, 0) = 5`, +which would result in an error. + +TODO: add more information about l-value behaviour. + +### `Matching Operator ~` + +This operator should be understood as 'matches', 'contains', 'is_in', or 'find me some stuff about something else. +For strings it matches the right operand as a regular expression to the left, returning: + - `null` if there is no match + - matched phrase if no grouping is applied + - matched element if one group is applied + - list of matches if more than one grouping is applied + +This can be used to extract information from unparsed nbt's in a more convoluted way (use `get(...)` for +more appropriate way of doing it). For lists it checks if an element is in the list, and returns the +index of that element, or `null` if no such element was found, especially that the use of `first(...)` +function will not return the index. Currently it doesn't have any special behaviour for numbers - it checks for +existence of characters in string representation of the left operand with respect of the regular expression on +the right hand side. + +In Minecraft API portion `entity ~ feature` is a shortcode for `query(entity,feature)` for queries that do not take +any extra arguments. + +
+[1,2,3] ~ 2 => 1 +[1,2,3] ~ 4 => null + +'foobar' ~ 'baz' => null +'foobar' ~ '.b' => 'ob' +'foobar' ~ '(.)b' => 'o' +'foobar' ~ '((.)b)' => ['ob', 'o'] +'foobar' ~ '((.)(b))' => ['ob', 'o', 'b'] +'foobar' ~ '(?:(.)(?:b))' => 'o' + +player('*') ~ 'gnembon' // null unless player gnembon is logged in (better to use player('gnembon') instead +p ~ 'sneaking' // if p is an entity returns whether p is sneaking ++ +Or a longer example of an ineffective way to searching for a squid + +
+entities = entities_area('all',x,y,z,100,10,100); +sid = entities ~ 'Squid'; +if(sid != null, run('execute as '+query(get(entities,sid),'id')+' run say I am here '+query(get(entities,sid),'pos') ) ) ++ +Or an example to find if a player has specific enchantment on a held axe (either hand) and get its level +(not using proper NBTs query support via `get(...)`): + +
+global_get_enchantment(p, ench) -> ( +$ for(['mainhand','offhand'], +$ holds = query(p, 'holds', _); +$ if( holds, +$ [what, count, nbt] = holds; +$ if( what ~ '_axe' && nbt ~ ench, +$ lvl = max(lvl, number(nbt ~ '(?<=lvl:)\\d') ) +$ ) +$ ) +$ ); +$ lvl +$); +/script run global_get_enchantment(player(), 'sharpness') ++ +### Basic Arithmetic Operators `+`, `sum(...)`, `-`, `difference(...)`, `*`, `product(...)`, `/`, `quotient(...)` + +Allows to add the results of two expressions. If the operands resolve to numbers, the result is arithmetic operation. +In case of strings, adding or subtracting from a string results in string concatenation and removal of substrings +from that string. Multiplication of strings and numbers results in repeating the string N times and division results +in taking the first k'th part of the string, so that `str*n/n ~ str` + +In case first operand is a list, either it +results in a new list with all elements modified one by one with the other operand, or if the operand is a list +with the same number of items - element-wise addition/subtraction. This prioritize treating lists as value containers +to lists treated as vectors. + +Addition with maps (`{}` or `m()`) results in a new map with keys from both maps added, if both operands are maps, +adding elements of the right argument to the keys, of left map, or just adding the right value as a new key +in the output map. + +Functional forms of `-` and `/` have less intuitive multi-nary interpretation, but they might be useful in some situations. +`x-y-z` resolves to `difference(x, y, z)`. + +`/` always produces a properly accurate result, fully reversible with `*` operator. To obtain a integer 'div' result, use +`floor(x/y)`. + +Examples: + +
+2+3 => 5 +'foo'+3+2 => 'foo32' +'foo'+(3+2) => 'foo5' +3+2+'bar' => '5bar' +'foo'*3 => 'foofoofoo' +'foofoofoo' / 3 => 'foo' +'foofoofoo'-'o' => 'fff' +[1,2,3]+1 => [2,3,4] +b = [100,63,100]; b+[10,0,10] => [110,63,110] +{'a' -> 1} + {'b' -> 2} => {'a' -> 1, 'b' -> 2} ++ +### Just Operators `%`, `^` + +The modulo and exponent (power) operators work only if both operands are numbers. `%` is a proper (and useful) 'modulus' operator, +not a useless 'reminder' operator that you would expect from anything that touches Java. While typically modulus is reserved +to integer numbers, scarpet expands them to floats with as much sense as possible. + +
pi^pi%euler => 1.124.... +-9 % 4 => 3 +9 % -4 => -3 +9.1 % -4.2 => -3.5 +9.1 % 4.2 => 0.7 +-3 ^ 2 => 9 +-3 ^ pi => // Error ++ +### Comparison Operators `==`, `equal()`, `!=`, `unique()`, `<`, `increasing()`, `>`, `decreasing()`, `<=`, `nondecreasing()`, `>=`, `nonincreasing()` + +Allows to compare the results of two expressions. For numbers, it considers arithmetic order of numbers, for +strings - lexicographical, nulls are always 'less' than everything else, and lists check their elements - +if the sizes are different, the size matters, otherwise, pairwise comparisons for each element are performed. +The same order rules than with all these operators are used with the default sortographical order as used by `sort` +function. All of these are true: + +
+null == null +null != false +0 == false +1 == true +null < 0 +null < -1000 +1000 < 'a' +'bar' < 'foo' +3 == 3.0 ++ +Functional variants of these operators allow to assert certain paradigms on multiple arguments at once. This means that +due to formal equivalence `x < y < z` is equivalent to `x < y & y < z` because of direct mapping to `increasing(x, y, z)`. This translates through +the parentheses, so `((x < y) < z)` is the same as `increasing(x, y, z)`. To achieve the same effect as you would see in other + languages (not python), you would need to cast the first pair to boolean value, i.e. `bool(x < y) < z`. + +### Logical Operators `&&`, `and(...)`, `||`, `or(...)` + +These operator compute respective boolean operation on the operands. What it important is that if calculating of the +second operand is not necessary, it won't be evaluated, which means one can use them as conditional statements. In +case of success returns first positive operand (`||`) or last one (`&&`). + +
+true || false => true +null || false => false +false || null => null +null != false || run('kill gnembon') // gnembon survives +null != false && run('kill gnembon') // when cheats not allowed +null != false && run('kill gnembon') // gnembon dies, cheats allowed ++ +### `Assignment Operators = <> +=` + +A set of assignment operators. All require bounded variable on the LHS, `<>` requires bounded arguments on the +right hand side as well (bounded, meaning being variables). Additionally they can also handle list constructors +with all bounded variables, and work then as list assignment operators. When `+=` is used on a list, it extends +that list of that element, and returns the list (old == new). `scarpet` doesn't support currently removal of items. +Removal of items can be obtained via `filter` command, and reassigning it fo the same variable. Both operations would +require rewriting of the array anyways. + +
+a = 5 => a == 5 +[a,b,c] = [3,4,5] => a==3, b==4, c==5 +[minx,maxx] = sort(xi,xj); // minx assumes min(xi, xj) and maxx, max(xi, xj) +[a,b,c,d,e,f] = [range(6)]; [a,b,c] <> [d,e,f]; [a,b,c,d,e,f] => [3,4,5,0,1,2] +a = [1,2,3]; a += 4 => [1,2,3,4] +a = [1,2,3,4]; a = filter(a,_!=2) => [1,3,4] ++ +### `Unary Operators - +` + +Require a number, flips the sign. One way to assert it's a number is by crashing the script. gg. + +
+-4 => -4 ++4 => 4 ++'4' // Error message ++ +### `Negation Operator !` + +flips boolean condition of the expression. Equivalent of `bool(expr)==false` + +
+!true => false +!false => true +!null => true +!5 => false +![] => true +![null] => false ++ +### `Unpacking Operator ...` + +Unpacks elements of a list of an iterator into a sequence of arguments in a function making so that `fun(...[1, 2, 3])` is +identical with `fun(1, 2, 3)`. For maps, it unpacks them to a list of key-value pairs. + +In function signatures it identifies a vararg parameter. + +
+fun(a, b, ... rest) -> [a, b, rest]; fun(1, 2, 3, 4) => [1, 2, [3, 4]] ++ +Effects of `...` can be surprisingly lasting. It is kept through the use of variables and function calls. + +
+fun(a, b, ... rest) -> [a, b, ... rest]; fun(1, 2, 3, 4) => [1, 2, 3, 4] +args() -> ... [1, 2, 3]; sum(a, b, c) -> a+b+c; sum(args()) => 6 +a = ... [1, 2, 3]; sum(a, b, c) -> a+b+c; sum(a) => 6 ++ +Unpacking mechanics can be used for list and map construction, not just for function calls. + +
+[...range(5), pi, ...range(5,-1,-1)] => [0, 1, 2, 3, 4, 3.14159265359, 5, 4, 3, 2, 1, 0] +{ ... map(range(5), _ -> _*_ )} => {0: 0, 1: 1, 2: 4, 3: 9, 4: 16} +{...{1 -> 2, 3 -> 4}, ...{5 -> 6, 7 -> 8}} => {1: 2, 3: 4, 5: 6, 7: 8} ++ +Fine print: unpacking of argument lists happens just before functions are evaluated. +This means that in some situations, for instance +when an expression is expected (`map(list, expr)`), or a function should not evaluate some (most!) of its arguments (`if(...)`), +unpacking cannot be used, and will be ignored, leaving `... list` identical to `list`. +Functions that don't honor unpacking mechanics, should have no use for it at the first place + (i.e. have one, or very well-defined, and very specific parameters), +so some caution (prior testing) is advised. Some of these multi-argument built-in functions are + `if`, `try`, `sort_key`, `system_variable_get`, `synchronize`, `sleep`, `in_dimension`, +all container functions (`get`, `has`, `put`, `delete`), +and all loop functions (`while`, `loop`, `map`, `filter`, `first`, `all`, `c_for`, `for` and`reduce`). + +### `Binary (bitwise) operations` + +These are a bunch of operators that work exclusively on numbers, more specifically their binary representations. Some of these +work on multiple numbers, some on only 2, and others on only 1. Note that most of these functions (all but `double_to_long_bits`) +only take integer values, so if the input has a decimal part, it will be discarded. + + - `bitwise_and(...)` -> Does the bitwise AND operation on each number in order. Note that with larger ranges of numbers this will + tend to 0. + - `bitwise_xor(...)` -> Does the bitwise XOR operation on each number in order. + - `bitwise_or(...)` -> Does the bitwise AND operation on each number in order. Note that with larger ranges of numbers this will + tend to -1. + - `bitwise_shift_left(num, amount)` -> Shifts all the bits of the first number `amount` spots to the left. Note that only the 6 + lowest-order bits of the amount are considered. + - `bitwise_shift_right(num, amount)` -> Shifts all the bits of the first number `amount` spots to the right logically. That is, the + `amount` most significant bits will always be set to 0. Like with the above, only the 6 lowest-order bits of the amount are considered. + - `bitwise_arithmetic_shift_right(num, amount)` -> Shifts all the bits of the first number `amount` spots to the right arithmetically. + That is, if the most significant (sign) bit is a 1, it'll propagate the one to the `amount` most significant bits. Like with the above, + only the 6 lowest-order bits of the amount are considered. + - `bitwise_roll_left(num, amount)` -> Rolls the bits of the first number `amount` bits to the left. This is basically where you + shift out the first `amount` bits and then add them on at the back, essentially 'rolling' the number. Note that unlike with + shifting, you can roll more than 63 bits at a time, as it just makes the number roll over more times, which isn't an issue + - `bitwise_roll_right(num, amount)` -> Same as above, just rolling in the other direction + - `bitwise_not(num)` -> Flips all the bits of the number. This is simply done by performing xor operation with -1, which in binary is + all ones. + - `bitwise_popcount(num)` -> Returns the number of ones in the binary representation of the number. For the number of zeroes, just + do 64 minus this number. + - `double_to_long_bits(num)` -> Returns a representation of the specified floating-point value according to the IEEE 754 floating-point + "double format" bit layout. + - `long_to_double_bits(num)` -> Returns the double value corresponding to a given bit representation. +# Arithmetic operations + +## Basic Arithmetic Functions + +There is bunch of them - they require a number and spit out a number, doing what you would expect them to do. + +### `fact(n)` + +Factorial of a number, a.k.a `n!`, just not in `scarpet`. Gets big... quick... Therefore, values larger +than `fact(20)` will not return the exact value, but a value with 'double-float' precision. + +### `sqrt(n)` + +Square root (not 'a squirt') of a number. For other fancy roots, use `^`, math and yo noggin. Imagine square roots on a tree... + +### `abs(n)` + +Absolut value. + +### `round(n)` + +Closest integer value. Did you know the earth is also round? + +### `floor(n)` + +Highest integer that is still no larger then `n`. Insert a floor pun here. + +### `ceil(n)` + +First lucky integer that is not smaller than `n`. As you would expect, ceiling is typically right above the floor. + +### `ln(n)` + +Natural logarithm of `n`. Naturally. + +### `ln1p(n)` + +Natural logarithm of `n+1`. Very optimistic. + +### `log10(n)` + +Decimal logarithm of `n`. Its ceiling is the length of its floor. + +### `log(n)` + +Binary logarithm of `n`. Finally, a proper one, not like the previous 11. + +### `log1p(n)` + +Binary logarithm of `n+1`. Also always positive. + +### `mandelbrot(a, b, limit)` + +Computes the value of the mandelbrot set, for set `a` and `b` spot. Spot the beetle. Why not. + +### `min(arg, ...), min(list), max(arg, ...), max(list)` + +Compute minimum or maximum of supplied arguments assuming default sorthoraphical order. +In case you are missing `argmax`, just use `a ~ max(a)`, little less efficient, but still fun. + +Interesting bit - `min` and `max` don't remove variable associations from arguments, which means can be used as +LHS of assignments (obvious case), or argument spec in function definitions (far less obvious). + +
+a = 1; b = 2; min(a,b) = 3; [a,b] => [3, 2] +a = 1; b = 2; fun(x, min(a,b)) -> [a,b]; fun(3,5) => [5, 0] ++ +Absolutely no idea, how the latter might be useful in practice. But since it compiles, can ship it. + +### `relu(n)` + +Linear rectifier of `n`. 0 below 0, n above. Why not. `max(0,n)` with less moral repercussions. + +## Trigonometric / Geometric Functions + +### `sin(x)` + +### `cos(x)` + +### `tan(x)` + +### `asin(x)` + +### `acos(x)` + +### `atan(x)` + +### `atan2(x,y)` + +### `sinh(x)` + +### `cosh(x)` + +### `tanh(x)` + +### `sec(x)` + +### `csc(x)` + +### `sech(x)` + +### `csch(x)` + +### `cot(x)` + +### `acot(x)` + +### `coth(x)` + +### `asinh(x)` + +### `acosh(x)` + +### `atanh(x)` + +### `rad(deg)` + +### `deg(rad)` + +Use as you wish +# System functions + +## Type conversion functions + +### `copy(expr)` + +Returns the deep copy of the expression. Can be used to copy mutable objects, like maps and lists + +### `type(expr)` + +Returns the string value indicating type of the expression. Possible outcomes +are `null`, `number`, `string`, `list`, `map`, `iterator`, `function`, `task`, +as well as minecraft related concepts like `block`, `entity`, `nbt`, `text`. + +### `bool(expr)` + +Returns a boolean context of the expression. +Bool is also interpreting string values as boolean, which is different from other +places where boolean context can be used. This can be used in places where API functions return string values to +represent binary values. + +
+bool(pi) => true +bool(false) => false +bool('') => false +bool([]) => false +bool(['']) => true +bool('foo') => true +bool('false') => false +bool('nulL') => false +if('false',1,0) => true ++ +### `number(expr)` + +Returns a numeric context of the expression. Can be used to read numbers from strings, or other types + +
+number(null) => 0 +number(false) => 0 +number(true) => 1 +number('') => null +number('3.14') => 3.14 +number([]) => 0 +number(['']) => 1 +number('foo') => null +number('3bar') => null +number('2')+number('2') => 4 ++ +### `str(expr)`,`str(expr, params? ... )`, `str(expr, param_list)` + +If called with one argument, returns string representation of such value. + +Otherwise, returns a formatted string representing the expression. Arguments for formatting can either be provided as + each consecutive parameter, or as a list which then would be the only extra parameter. To format one list argument + , you can use `str(list)`, or `str('foo %s', [list])`. + +Accepts formatting style accepted by `String.format`. +Supported types (with `"%>"` syntax): + +* `d`, `o`, `x`: integers, octal, hex +* `a`, `e`, `f`, `g`: floats +* `b`: booleans +* `s`: strings +* `%%`: '%' character + +
+str(null) => 'null' +str(false) => 'false' +str('') => '' +str('3.14') => '3.14' +str([]) => '[]' +str(['']) => '[]' +str('foo') => 'foo' +str('3bar') => '3bar' +str(2)+str(2) => '22' +str('pi: %.2f',pi) => 'pi: 3.14' +str('player at: %d, %d, %d',pos(player())) => 'player at: 567, -2423, 124' ++ +* * * +## Threading and Parallel Execution + +Scarpet allows to run threads of execution in parallel to the main script execution thread. In Minecraft, apps +are executed on the main server thread. Since Minecraft is inherently NOT thread safe, it is not that +beneficial to parallel execution in order to access world resources faster. Both `getBlockState` and `setBlockState` +are not thread safe and require the execution to park on the server thread, where these requests can be executed in +the off-tick time in between ticks that didn't take all 50ms. There are however benefits of running things in parallel, +like fine time control not relying on the tick clock, or running things independent on each other. You can still run +your actions on tick-by-tick basis, either taking control of the execution using `game_tick()` API function +(nasty solution), or scheduling tick using `schedule()` function (preferred solution), but threading gives much more control +on the timings without impacting the main game and is the only solution to solve problems in parallel +(see [scarpet camera](/src/main/resources/assets/carpet/scripts/camera.sc)). + +Due to limitations with the game, there are some limits to the threading as well. You cannot for +instance `join_task()` at all from the main script and server thread, because any use of Minecraft specific +function that require any world access, will require to park and join on the main thread to get world access, +meaning that calling join on that task would inevitably lead to a typical deadlock. You can still join tasks +from other threads, just because the only possibility of a deadlock in this case would come explicitly from your +bad code, not the internal world access behaviour. Some things tough like players or entities manipulation, can be +effectively parallelized. + +If the app is shutting down, creating new tasks via `task` will not succeed. Instead the new task will do nothing and return +`null`, so most threaded application should handle closing apps naturally. Keep in mind in case you rely on task return values, +that they will return `null` regardless of anything in these situations. When app handles `__on_close()` event, new tasks cannot +be submitted at this point, but current tasks are not terminated. Apps can use that opportunity to gracefully shutdown their tasks. +Regardless if the app handles `__on_close()` event, or does anything with their tasks in it, all tasks will be terminated exceptionally +within the next 1.5 seconds. + +### `task(function, ... args)`, `task_thread(executor, function, ... args)` + +Creates and runs a parallel task, returning the handle to the task object. Task will return the return value of the +function when its completed, or will return `null` immediately if task is still in progress, so grabbing a value of +a task object is non-blocking. Function can be either function value, or function lambda, or a name of an existing +defined function. In case function needs arguments to be called with, they should be supplied after the function +name, or value. `executor` identifier in `task_thread`, places the task in a specific queue identified by this value. +The default thread value is the `null` thread. There are no limits on number of parallel tasks for any executor, +so using different queues is solely for synchronization purposes. + +
+task( _() -> print('Hello Other World') ) => Runs print command on a separate thread +foo(a, b) -> print(a+b); task('foo',2,2) => Uses existing function definition to start a task +task_thread('temp', 'foo',3,5); => runs function foo with a different thread executor, identified as 'temp' +a = 3; task_thread('temp', _(outer(a), b) -> foo(a,b), 5) + => Another example of running the same thing passing arguments using closure over anonymous function as well as passing a parameter. ++ +In case you want to create a task based on a function that is not defined in your module, please read the tips on + "Passing function references to other modules of your application" section in the `call(...)` section. + +### `sleep()` `sleep(timeout)`, `sleep(timeout, close_expr)` + + +Halts the execution of the thread (or the game itself, if run not as a part of a task) for `expr` milliseconds. +It checks for interrupted execution, in that case exits the thread (or the entire program, if not run on a thread) in case the app +is being stopped/removed. If the closing expression is specified, executes the expression when a shutdown signal is triggered. +If run on the main thread (i.e. not as a task) the close expression may only be invoked when the entire game shuts down, so close call only +makes sense for threads. For regular programs, use `__on_close()` handler. + +Since `close_expr` is executed after app shutdown is initiated, you won't be able to create new tasks in that block. Threads +should periodically call `sleep` to ensure all app tasks will finish when the app is closing or right after, but the app engine +will not forcefully remove your running tasks, so the tasks themselves need to properly react to the closing request. + +
+sleep(50) # wait for 50 milliseconds +sleep(1000, print('Interrupted')) # waits for 1 second, outputs a message when thread is shut down. ++ +### `task_count(executor?)` + +If no argument provided, returns total number of tasks being executed in parallel at this moment using scarpet +threading system. If the executor is provided, returns number of active tasks for that provider. Use `task_count(null)` +to get the task count of the default executor only. + +### `task_value(task)` + +Returns the task return value, or `null` if task hasn't finished yet. Its a non-blocking operation. Unlike `join_task`, +can be called on any task at any point + +### `task_join(task)` + +Waits for the task completion and returns its computed value. If the task has already finished returns it immediately. +Unless taking the task value directly, i.e. via `task_value`, this operation is blocking. Since Minecraft has a +limitation that all world access operations have to be performed on the main game thread in the off-tick time, +joining any tasks that use Minecraft API from the main thread would mean automatic lock, so joining from the main +thread is not allowed. Join tasks from other threads, if you really need to, or communicate asynchronously with +the task via globals or function data / arguments to monitor its progress, communicate, get partial results, +or signal termination. + +### `task_completed(task)` + +Returns true if task has completed, or false otherwise. + +### `synchronize(lock, expression)` + +Evaluates `expression` synchronized with respect to the lock `lock`. Returns the value of the expression. + +### `task_dock(expr)` + +In a not-task (running regular code on the main game thread) it is a pass-through command. In tasks - it docks +the current thread on the main server thread and executes expression as one server offline server task. +This is especially helpful in case a task has several docking operations to perform, such as setting a block, and +it would be much more efficient to do them all at once rather then packing each block access in each own call. + +Be mindful, that docking the task means that the tick execution will be delayed until the expression is evaluated. +This will synchronize your task with other tasks using `task_dock`, but if you should be using `synchronize` to +synchronize tasks without locking the main thread. + + +* * * + +## Auxiliary functions + +### `lower(expr), upper(expr), title(expr)` + +Returns lowercase, uppercase or titlecase representation of a string representation of the passed expression + +
+lower('aBc') => 'abc' +upper('aBc') => 'ABC' +title('aBc') => 'Abc' ++ +### `replace(string, regex, repl?); replace_first(string, regex, repl?)` + +Replaces all, or first occurrence of a regular expression in the string with `repl` expression, +or nothing, if not specified. To use escape characters (`\(`,`\+`,...), metacharacters (`\d`,`\w`,...), or position anchors (`\b`,`\z`,...) in your regular expression, use two backslashes. + +
+replace('abbccddebfg','b+','z') // => azccddezfg +replace('abbccddebfg','\\w$','z') // => abbccddebfz +replace_first('abbccddebfg','b+','z') // => azccddebfg ++ +### `length(expr)` + +Returns length of the expression, the length of the string, the length of the integer part of the number, +or length of the list + +
+length(pi) => 1 +length(pi*pi) => 1 +length(pi^pi) => 2 +length([]) => 0 +length([1,2,3]) => 3 +length('') => 0 +length('foo') => 3 ++ +### `rand(expr), rand(expr, seed)` + +returns a random number from `0.0` (inclusive) to `expr` (exclusive). In boolean context (in conditions, +boolean functions, or `bool`), returns false if the randomly selected value is less than 1. This means +that `bool(rand(2))` returns true half of the time and `!rand(5)` returns true for 20% (1/5) of the time. If seed is not +provided, uses a random seed that's shared across all scarpet apps. +If seed is provided, each consecutive call to rand() will act like 'next' call to the +same random object. Scarpet keeps track of up to 65536 custom random number generators (custom seeds, per app), +so if you exceed this number, your random sequences will revert to the beginning and start over. + +
+map(range(10), floor(rand(10))) => [5, 8, 0, 6, 9, 3, 9, 9, 1, 8] +map(range(10), bool(rand(2))) => [false, false, true, false, false, false, true, false, true, false] +map(range(10), str('%.1f',rand(_))) => [0.0, 0.4, 0.6, 1.9, 2.8, 3.8, 5.3, 2.2, 1.6, 5.6] ++ +## `reset_seed(seed)` + +Resets the sequence of the randomizer used by `rand` for this seed to its initial state. Returns a boolean value +indicating if the given seed has been used or not. + +### `perlin(x), perlin(x, y), perlin(x, y, z), perlin(x, y, z, seed)` + +returns a noise value from `0.0` to `1.0` (roughly) for 1, 2 or 3 dimensional coordinate. The default seed it samples +from is `0`, but seed can be specified as a 4th argument as well. In case you need 1D or 2D noise values with custom +seed, use `null` for `y` and `z`, or `z` arguments respectively. + +Perlin noise is based on a square grid and generates rougher maps comparing to Simplex, which is creamier. +Querying for lower-dimensional result, rather than affixing unused dimensions to constants has a speed benefit, + +Thou shall not sample from noise changing seed frequently. Scarpet will keep track of the last 256 perlin seeds +used for sampling providing similar speed comparing to the default seed of `0`. In case the app engine uses more +than 256 seeds at the same time, switching between them can get much more expensive. + +### `simplex(x, y), simplex(x, y, z), simplex(x, y, z, seed)` + +returns a noise value from `0.0` to `1.0` (roughly) for 2 or 3 dimensional coordinate. The default seed it samples +from is `0`, but seed can be specified as a 4th argument as well. In case you need 2D noise values with custom seed, +use `null` for `z` argument. + +Simplex noise is based on a triangular grid and generates smoother maps comparing to Perlin. To sample 1D simplex +noise, affix other coordinate to a constant. + +Thou shall not sample from noise changing seed frequently. Scarpet will keep track of the last 256 simplex seeds +used for sampling providing similar speed comparing to the default seed of `0`. In case the app engine uses more +than 256 seeds at the same time, switching between them can get much more expensive. + +### `print(expr)`, `print(player, expr)` + +prints the value of the expression to chat. Passes the result of the argument to the output unchanged, +so `print`-statements can be weaved in code to debug programming issues. By default it uses the same communication +channels that most vanilla commands are using. + +In case player is directly specified, it only sends the message to that player, like `tell` command. + +
+print('foo') => results in foo, prints: foo +a = 1; print(a = 5) => results in 5, prints: 5 +a = 1; print(a) = 5 => results in 5, prints: 1 +print('pi = '+pi) => prints: pi = 3.141592653589793 +print(str('pi = %.2f',pi)) => prints: pi = 3.14 ++ +### `time()` + +Returns the number of milliseconds since 'some point', like Java's `System.nanoTime()`, which varies from system to +system and from Java to Java. This measure should NOT be used to determine the current (date)time, but to measure +durations of things. +it returns a float with time in milliseconds (ms) for convenience and microsecond (μs) resolution for sanity. + + +
+start_time = time(); +flip_my_world_upside_down(); +print(str('this took %d milliseconds',time()-start_time)) ++ +### `unix_time()` + +Returns standard POSIX time as a number of milliseconds since the start of the epoch +(00:00 am and 0 seconds, 1 Jan 1970). +Unlike the previous function, this can be used to get exact time, but it varies from time zone to time zone. + +### `convert_date(milliseconds)` +### `convert_date(year, month, date, hours?, mins?, secs?)` +### `convert_date([year, month, date, hours?, mins?, secs?])` + +If called with a single argument, converts standard POSIX time to a list in the format: + +`[year, month, date, hours, mins, secs, day_of_week, day_of_year, week_of_year]` + +eg: `convert_date(1592401346960) -> [2020, 6, 17, 10, 42, 26, 3, 169, 25]` + +Where the `6` stands for June, but `17` stands for 17th, `10` stands for 10am, +`42` stands for 42 minutes past the hour, and `26` stands for 26 seconds past the minute, +and `3` stands for Wednesday, `169` is the day of year, and `25` is a week of year. + +Run `convert_date(unix_time())` to get current time as list. + + +When called with a list, or with 3 or 6 arguments, returns standard POSIX time as a number of milliseconds since the + start of the epoch (1 Jan 1970), +using the time inputted into the function as opposed to the system time. + +Example editing: +
+date = convert_date(unix_time()); + +months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; + +days = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun']; + +print( + str('Its %s, %d %s %d, %02d:%02d:%02d', + days:(date:6-1), date:2, months:(date:1-1), date:0, date:3, date:4, date:5 + ) +) ++ +This will give you a date: + +It is currently `hrs`:`mins` and `secs` seconds on the `date`th of `month`, `year` + +### `encode_b64(string)`, `decode_b64(string)` + +Encode or decode a string from b64, throwing a `b64_error` exception if it's invalid + +### `encode_json(value)`, `decode_json(string)` + +Encodes a value as a json string, and decodes a json string as a valid value, throwing a `json_error` exception if it +doesn't parse properly + +### `profile_expr(expression)` + +Returns number of times given expression can be run in 50ms time. Useful to profile and optimize your code. +Note that, even if its only a number, it WILL run these commands, so if they are destructive, you need to be careful. + +* * * + +## Access to variables and stored functions (use with caution) + +### `var(expr)` + +Returns the variable under the name of the string value of the expression. Allows to manipulate variables in more +programmatic manner, which allows to use local variable set with a hash map type key-value access, +can also be used with global variables + +
+a = 1; var('a') = 'foo'; a => a == 'foo' ++ +### `undef(expr)` + +Removes all bindings of a variable with a name of `expr`. Removes also all function definitions with that name. +It can affect global variable pool, and local variable set for a particular function. + +
+inc(i) -> i+1; foo = 5; inc(foo) => 6 +inc(i) -> i+1; foo = 5; undef('foo'); inc(foo) => 1 +inc(i) -> i+1; foo = 5; undef('inc'); undef('foo'); inc(foo) => Error: Function inc is not defined yet at pos 53 ++ +### `vars(prefix)` + +It returns all names of variables from local scope (if prefix does not start with 'global') or global variables +(otherwise). Here is a larger example that uses combination of `vars` and `var` functions to be +used for object counting + +
+/script run +$ count_blocks(ent) -> ( +$ [cx, cy, cz] = query(ent, 'pos'); +$ scan(cx, cy, cz, 16, 16, 16, var('count_'+_) += 1); +$ for ( sort_key( vars('count_'), -var(_)), +$ print(str( '%s: %d', slice(_,6), var(_) )) +$ ) +$ ) +/script run count_blocks(player()) ++ +* * * + +## System key-value storage + +Scarpet runs apps in isolation. The can share code via use of shared libraries, but each library that is imported to +each app is specific to that app. Apps can store and fetch state from disk, but its restricted to specific locations +meaning apps cannot interact via disk either. To facilitate communication for interappperability, scarpet hosts its +own key-value storage that is shared between all apps currently running on the host, providing methods for getting an +associated value with optional setting it if not present, and an operation of modifying a content of a system +global value. + +### `system_variable_get(key, default_value ?)` + +Returns the variable from the system shared key-value storage keyed with a `key` value, optionally if value is +not present, and default expression is provided, sets a new value to be associated with that key + +### `system_variable_set(key, new_value)` + +Returns the variable from the system shared key-value storage keyed with a `key` value, and sets a new +mapping for the key +# Loops, and higher order functions + +Efficient use of these functions can greatly simplify your programs and speed them up, as these functions will +internalize most of the operations that need to be applied on multiple values at the same time. Most of them take +a `list` argument which can be any iterable structure in scarpet, including generators, like `rect`, or `range`, +and maps, where the iterator returns all the map keys + +## Loops + +### `break(), break(expr), continue(), continue(expr)` + +These allow to control execution of a loop either skipping current iteration code, using `continue`, or finishing the +current loop, using `break`. `break` and `continue` can only be used inside `for`, `c_for`, `while`, `loop`, `map`, +`filter`, `reduce` as well as Minecraft API block loops, `scan` and `volume` +functions, while `break` can be used in `first` as well. Outside of the internal expressions of these functions, +calling `break` or `continue` will cause an error. In case of the nested loops, and more complex setups, use +custom `try` and `throw` setup. + +Please check corresponding loop function description what `continue` and `break` do in their contexts, but in +general case, passed values to `break` and `continue` will be used in place of the return value of the internal +iteration expression. + +### `c_for(init, condition, increment, body)` + +`c_for` Mimics c-style tri-arg (plus body) for loops. Return value of `c_for` is number of iterations performed in the + loop. Unlike other loops, the `body` is not provided with pre-initialized `_` style variables - all initialization + and increments has to be handled by the programmers themselves. + `break` and `continue` statements are handled within `body` expression only, and not in `condition` or `increment`. + +
+ c_for(x=0, x<10, x+=1, + c_for(y=0, y<10, y+=1, + print(str('%d * %d = %d', x, y, x*y)) + ) + ) ++ +### `for(list,expr(_,_i))` + +Evaluates expression over list of items from the `list`. Supplies `_`(value) and `_i`(iteration number) to the `expr`. + +Returns the number of times `expr` was successful. Uses `continue` and `break` argument in place of the returned +value from the `expr`(if supplied), to determine if the iteration was successful. + +
+check_prime(n) -> !first( range(2, sqrt(n)+1), !(n % _) ); +for(range(1000000,1100000),check_prime(_)) => 7216 ++ +From which we can learn that there is 7216 primes between 1M and 1.1M + +### `while(cond, expr)`, `while(cond, limit, expr)` + +Evaluates expression `expr` repeatedly until condition `cond` becomes false, but not more than `limit` times (if limit is specified). +Returns the result of the last `expr` evaluation, or `null` if nothing was successful. Both `expr` and `cond` will +received a bound variable `_` indicating current iteration, so its a number. + +
+while(a<100,a=_*_) => 100 // loop stopped at condition +while(a<100,10,a=_*_) => 81 // loop exhausted via limit +while(a<100,20,a=_*_) => 100 // loop stopped at condition, but a has already been assigned +while(_*_<100,20,a=_*_) => 81 // loop stopped at condition, before a was assigned a value ++ +### `loop(num,expr(_),exit(_)?)` + +Evaluates expression `expr`, `num` number of times. code>expr receives `_` system variable indicating the iteration. + +
+loop(5, game_tick()) => repeat tick 5 times +list = []; loop(5, x = _; loop(5, list += [x, _] ) ); list + // double loop, produces: [[0, 0], [0, 1], [0, 2], [0, 3], [0, 4], [1, 0], [1, 1], ... , [4, 2], [4, 3], [4, 4]] ++ +In this small example we will search for first 10 primes, apparently including 0: + +
+check_prime(n) -> !first( range(2, sqrt(n)+1), !(n % _) ); +primes = []; +loop(10000, if(check_prime(_), primes += _ ; if (length(primes) >= 10, break()))); +primes +// outputs: [0, 1, 2, 3, 5, 7, 11, 13, 17, 19] ++ +## Higher Order Functions + +### `map(list,expr(_,_i))` + +Converts a `list` of values, to another list where each value is result of an expression `v = expr(_, _i)` +where `_` is passed as each element of the list, and `_i` is the index of such element. If `break` is called the +map returns whatever collected thus far. If `continue` and `break` are used with supplied argument, it is used in +place of the resulting map element, otherwise current element is skipped. + +
+map(range(10), _*_) => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] +map(player('*'), _+' is stoopid') [gnembon is stoopid, herobrine is stoopid] ++ +### `filter(list,expr(_,_i))` + +filters `list` elements returning only these that return positive result of the `expr`. With `break` and `continue` +statements, the supplied value can be used as a boolean check instead. + +
+filter(range(100), !(_%5), _*_>1000) => [0, 5, 10, 15, 20, 25, 30] +map(filter(entity_list('*'),_=='Witch'), query(_,'pos') ) => [[1082.5, 57, 1243.5]] ++ +### `first(list,expr(_,_i))` + +Finds and returns the first item in the list that satisfies `expr`. It sets `_` for current element value, +and `_i` for index of that element. `break` can be called inside the iteration code, using its argument value +instead of the current item. `continue` has no sense and cannot be called inside `first` call. + +
+first(range(1000,10000), n=_; !first( range(2, sqrt(n)+1), !(n % _) ) ) => 1009 // first prime after 1000 ++ +Notice in the example above, that we needed to rename the outer `_` to be albe to use in in the inner `first` call + +### `all(list,expr(_,_i))` + +Returns `true` if all elements on the list satisfy the condition. Its roughly equivalent +to `all(list,expr) <=> for(list,expr)==length(list)`. `expr` also receives bound `_` and `_i` variables. `break` +and `continue` have no sense and cannot be used inside of `expr` body. + +
+all([1,2,3], check_prime(_)) => true +all(neighbours(x,y,z), _=='stone') => true // if all neighbours of [x, y, z] are stone +map(filter(rect(0,4,0,1000,0,1000), [x,y,z]=pos(_); all(rect(x,y,z,1,0,1),_=='bedrock') ), pos(_) ) + => [[-298, 4, -703], [-287, 4, -156], [-269, 4, 104], [242, 4, 250], [-159, 4, 335], [-208, 4, 416], [-510, 4, 546], [376, 4, 806]] + // find all 3x3 bedrock structures in the top bedrock layer +map( filter( rect(0,4,0,1000,1,1000,1000,0,1000), [x,y,z]=pos(_); + all(rect(x,y,z,1,0,1),_=='bedrock') && for(rect(x,y-1,z,1,1,1,1,0,1),_=='bedrock')<8), + pos(_) ) => [[343, 3, -642], [153, 3, -285], [674, 3, 167], [-710, 3, 398]] + // ditto, but requiring at most 7 bedrock block in the 18 blocks below them ++ +### `reduce(list,expr(_a,_,_i), initial)` + +Applies `expr` for each element of the list and saves the result in `_a` accumulator. Consecutive calls to `expr` +can access that value to apply more values. You also need to specify the initial value to apply for the +accumulator. `break` can be used to terminate reduction prematurely. If a value is provided to `break` or `continue`, +it will be used from now on as a new value for the accumulator. + +
+reduce([1,2,3,4],_a+_,0) => 10 +reduce([1,2,3,4],_a*_,1) => 24 ++ +# User-defined functions and program control flow + +## Writing programs with more than 1 line + +### Operator `;`, `then(...)` + +To effectively write programs that have more than one line, a programmer needs way to specify a sequence of commands +that execute one after another. In `scarpet` this can be achieved with `;`. Its an operator, and by separating +statements with semicolons. And since whitespaces and +`$` (commandline visible newline separator) +sign are all treats as whitespaces, how you layout your code doesn't matter, as long as it is readable to everyone involved. + +
+expr; +expr; +expr; +expr ++ +Notice that the last expression is not followed by a semicolon. Since instruction separation is functional +in `scarpet`, and not barely an instruction delimiter, terminating the code with a dangling operator wouldn't +be valid. Having said that, since many programming languages don't care about the number of op terminators +programmers use, carpet preprocessor will remove all unnecessary semicolons from scripts when compiled. + +In general `expr; expr; expr; expr` is equivalent to `(((expr ; expr) ; expr) ; expr)` or `then(expr, expr, expr, expr)`. + +Result of the evaluated expression is the same as the result of the second expression, but first expression +is also evaluated for side-effects + +
+expr1 ; expr2 => expr2 // with expr1 as a side-effect ++ +## Global variables + +All defined functions are compiled, stored persistently, and available globally within the app. +Functions can only be undefined via call to `undef('fun')`, which would erase global entry for function `fun`. +Since all variables have local scope inside each function, or each command script, + global variables is a way to share the global state. + +Any variable that is used with a name that starts with `'global_'` will be stored and accessible globally, not only +inside the current scope. If used directly in the chat window with the default app, it will persist across calls to `/script` +function. Like functions, which are global, global variables can only be undefined via `undef`. + +For apps running in `'global'` scope - all players will share the same global variables and defined functions, +and with `player` scope, each player hosts its own state for each app, so function and global_variables are distinct. + + +
+/script run a() -> global_list+=1; global_list = [1,2,3]; a(); a(); global_list // => [1, 2, 3, 1, 1] +/script run a(); a(); global_list // => [1, 2, 3, 1, 1, 1, 1] ++ +### `Operator ->` + +`->` operator has two uses - as a function definition operator and key-value initializer for maps. + +To organize code better than a flat sequence of operations, one can define functions. Definition is correct +if has the following form + +
+fun(args, ...) -> expr ++ +Where `fun(args, ...)` is a function signature indicating function name, number of arguments, and their names, +and expr is an expression (can be complex) that is evaluated when `fun` is called. Names in the signature don't +need to be used anywhere else, other occurrences of these names will be masked in this function scope. Function +call creates new scope for variables inside `expr`, so all non-global variables are not visible from the caller +scope. All parameters are passed by value to the new scope, including lists and other containers, however their +copy will be shallow. + +The function returns itself as a first class object, which means it can be used to call it later with the `call` function + +Using `_` as the function name creates anonymous function, so each time `_` function is defined, it will be given +a unique name, which you can pass somewhere else to get this function `call`ed. Anonymous functions can only be called +by their value and `call` method. + +
+a(lst) -> lst+=1; list = [1,2,3]; a(list); a(list); list // => [1,2,3] ++ +In case the inner function wants to operate and modify larger objects, lists from the outer scope, but not global, +it needs to use `outer` function in function signature. + +in map construction context (directly in `m()` or `{}`), the `->` operator has a different function by converting its +arguments to a tuple which is used by map constructor as a key-value pair: + +
+{ 'foo' -> 'bar' } => {l('foo', 'bar')} ++ +This means that it is not possible to define literally a set of inline function, however a set of functions can still +be created by adding elements to an empty set, and building it this way. That's a tradeoff for having a cool map initializer. + +### `outer(arg)` + +`outer` function can only be used in the function signature, and it will cause an error everywhere else. It +saves the value of that variable from the outer scope and allows its use in the inner scope. This is a similar +behaviour to using outer variables in lambda function definitions from Java, except here you have to specify +which variables you want to use, and borrow + +This mechanism can be used to use static mutable objects without the need of using `global_...` variables + +
+list = [1,2,3]; a(outer(list)) -> list+=1; a(); a(); list // => [1,2,3,1,1] ++ +The return value of a function is the value of the last expression. This as the same effect as using outer or +global lists, but is more expensive + +
+a(lst) -> lst+=1; list = [1,2,3]; list=a(list); list=a(list); list // => [1,2,3,1,1] ++ +Ability to combine more statements into one expression, with functions, passing parameters, and global and outer +scoping allow to organize even larger scripts + +### `Operator ...` + +Defines a function argument to represent a variable length argument list of whatever arguments are left +from the argument call list, also known as a varargs. There can be only one defined vararg argument in a function signature. +Technically, it doesn't matter where is it, but it looks best at the butt side of it. + +
+foo(a, b, c) -> ... # fixed argument function, call foo(1, 2, 3) +foo(a, b, ... c) -> ... # c is now representing the variable argument part + foo(1, 2) # a:1, b:2, c:[] + foo(1, 2, 3) # a:1, b:2, c:[3] + foo(1, 2, 3, 4) # a:1, b:2, c:[3, 4] +foo(... x) -> ... # all arguments for foo are included in the list + + ++ +### `import(module_name, ? symbols ...)` + +Imports symbols from other apps and libraries into the current one: global variables or functions, allowing to use +them in the current app. This includes other symbols imported by these modules. Scarpet supports circular dependencies, +but if symbols are used directly in the module body rather than functions, it may not be able to retrieve them. + +Returns full list of available symbols that could be imported from this module, which can be used to debug import +issues, and list contents of libraries. + +You can load and import functions from dependencies in a remote app store's source specified in your config's `libraries` block, but make sure +to place your config _before_ the import in order to allow the remote dependency to be downloaded (currently, app resources are only downloaded +when using the `/carpet download` command). + +### `call(function, ? args ...)` + +calls a user defined function with specified arguments. It is equivalent to calling `function(args...)` directly +except you can use it with function value, or name instead. This means you can pass functions to other user defined +functions as arguments and call them with `call` internally. Since function definitions return the defined +function, they can be defined in place as anonymous functions. + +#### Passing function references to other modules of your application + +In case a function is defined by its name, Scarpet will attempt to resolve its definition in the given module and its imports, +meaning if the call is in a imported library, and not in the main module of your app, and that function is not visible from the +library perspective, but in the app, it won't be call-able. In case you pass a function name to a separate module in your app, +it should import back that method from the main module for visibility. + +Check an example of a problematic code of a library that expects a function value as a passed argument and how it is called in +the parent app: +``` +//app.sc +import('lib', 'callme'); +foo(x) -> x*x; +test() -> callme('foo' , 5); +``` +``` +//lib.scl +callme(fun, arg) -> call(fun, arg); +``` + +In this case `'foo'` will fail to dereference in `lib` as it is not visible by name. In tightly coupled modules, where `lib` is just +a component of your `app` you can use circular import to acknowledge the symbol from the other module (pretty much like +imports in Java classes), and that solves the issue but makes the library dependent on the main app: +``` +//lib.scl +import('app','foo'); +callme(fun, arg) -> call(fun, arg); +``` +You can circumvent that issue by explicitly dereferencing the local function where it is used as a lambda argument created +in the module in which the requested function is visible: +``` +//app.sc +import('lib', 'callme'); +foo(x) -> x*x; +test() -> callme(_(x) -> foo(x), 5); +``` +``` +//lib.scl +callme(fun, arg) -> call(fun, arg); +``` +Or by passing an explicit reference to the function, instead of calling it by name: +``` +//app.sc +import('lib', 'callme'); +global_foohandler = (foo(x) -> x*x); +test() -> callme(global_foohandler, 5); +``` + +Little technical note: the use of `_` in expression passed to built in functions is much more efficient due to not +creating new call stacks for each invoked function, but anonymous functions is the only mechanism available for +programmers with their own lambda arguments + +
+my_map(list, function) -> map(list, call(function, _)); +my_map([1,2,3], _(x) -> x*x); // => [1,4,9] +profile_expr(my_map([1,2,3], _(x) -> x*x)); // => ~32000 +sq(x) -> x*x; profile_expr(my_map([1,2,3], 'sq')); // => ~36000 +sq = (_(x) -> x*x); profile_expr(my_map([1,2,3], sq)); // => ~36000 +profile_expr(map([1,2,3], _*_)); // => ~80000 ++ +## Control flow + +### `return(expr?)` + +Sometimes its convenient to break the organized control flow, or it is not practical to pass the final result value of +a function to the last statement, in this case a return statement can be used + +If no argument is provided - returns null value. + +
+def() -> ( + expr1; + expr2; + return(expr3); // function terminates returning expr3 + expr4; // skipped + expr5 // skipped +) ++ +In general its cheaper to leave the last expression as a return value, rather than calling +returns everywhere, but it would often lead to a messy code. + +### `exit(expr?)` + +It terminates entire program passing `expr` as the result of the program execution, or null if omitted. + +### `try(expr)` `try(expr, user_catch_expr)` `try(expr, type, catch_expr, type?, catch_expr?, ...)` + +`try` evaluates expression, allowing capturing exceptions that would be thrown inside `expr` statement. The exceptions can be +thrown explicitly using `throw()` or internally by scarpet where code is correct but detects illegal state. The 2-argument form +catches only user-thrown exceptions and one argument call `try(expr)` is equivalent to `try(expr, null)`, +or `try(expr, 'user_exception', null)`. If multiple `type-catch` pairs are defined, the execution terminates on the first +applicable type for the exception thrown. Therefore, even if the caught exception matches multiple filters, only +the first matching block will be executed. + +Catch expressions are evaluated with +`_` set to the value associated with the exception and `_trace` set to contain details about point of error (token, and line and +column positions), call stack and local +variables at the time of failure. The `type` will catch any exception of that type and any subtype of this type. + + +You can use `try` mechanism to exit from large portion of a convoluted call stack and continue program execution, although catching +exceptions is typically much more expensive comparing to not throwing them. + +The `try` function allows you to catch some scarpet exceptions for cases covering invalid data, like invalid +blocks, biomes, dimensions and other things, that may have been modified by datapacks, resourcepacks or other mods, +or when an error is outside of the programmers scope, such as problems when reading or decoding files. + +This is the hierarchy of the exceptions that could be thrown/caught in the with the `try` function: +- `exception`: This is the base exception. Catching `'exception'` allows to catch everything that can be caught, +but like everywhere else, doing that sounds like a bad idea. + - `value_exception`: This is the parent for any exception that occurs due to an + incorrect argument value provided to a built-in function + - `unknown_item`, `unknown_block`, `unknown_biome`, `unknown_sound`, `unknown_particle`, + `unknown_poi_type`, `unknown_dimension`, `unknown_structure`, `unknown_criterion`: Specific + errors thrown when a specified internal name does not exist or is invalid. + - `io_exception`: This is the parent for any exception that occurs due to an error handling external data. + - `nbt_error`: Incorrect input/output NBT file. + - `json_error`: Incorrect input/output JSON data. + - `b64_error`: Incorrect input/output b64 (base 64) string + - `user_exception`: Exception thrown by default with `throw` function. + +Synopsis: +
+inner_call() -> +( + aaa = 'booyah'; + try( + for (range(10), item_tags('stick'+_*'k')); + , + print(_trace) // not caught, only catching user_exceptions + ) +); + +outer_call() -> +( + try( + inner_call() + , 'exception', // catching everything + print(_trace) + ) +); ++Producing: +``` +{stack: [[
+get([range(10)], 5) => 5 +get([range(10)], -1) => 9 +get([range(10)], 10) => 0 +[range(10)]:93 => 3 +get(player() ~ 'nbt', 'Health') => 20 // inefficient way to get player health, use player() ~ 'health' instead +get({ 'foo' -> 2, 'bar' -> 3, 'baz' -> 4 }, 'bar') => 3 ++ +### `has(container, address, ...), has(lvalue)` + +Similar to `get`, but returns boolean value indicating if the given index / key / path is in the container. +Can be used to determine if `get(...)==null` means the element doesn't exist, or the stored value for this +address is `null`, and is cheaper to run than `get`. + +Like get, it can accept multiple addresses for chains in nested containers. In this case `has(foo:a:b)` is +equivalent to `has(get(foo,a), b)` or `has(foo, a, b)` + +### `delete(container, address, ...), delete(lvalue)` + +Removes specific entry from the container. For the lists - removes the element and shrinks it. For maps, it +removes the key from the map, and for nbt - removes content from a given path. + +Like with the `get` and `has`, `delete` can accept chained addresses, as well as l-value container access, removing +the value from the leaf of the path provided, so `delete(foo, a, b)` is the +same as `delete(get(foo,a),b)` or `delete(foo:a:b)` + +Returns true, if container was changed, false, if it was left unchanged, and null if operation was invalid. + +### `put(container, address, value), put(container, address, value, mode), put(lvalue, value)` + +**Lists** + +Modifies the container by replacing the value under the address with the supplied `value`. For lists, a valid +index is required, but can be negative as well to indicate positions from the end of the list. If `null` is +supplied as the address, it always means - add to the end of the list. + +There are three modes that lists can have items added to them: + +* `replace`(default): Replaces item under given index(address). Doesn't change the size of the array +unless `null` address is used, which is an exception and then it appends to the end +* `insert`: Inserts given element at a specified index, shifting the rest of the array to make space for the item. +Note that index of -1 points to the last element of the list, thus inserting at that position and moving the previous +last element to the new last element position. To insert at the end, use `+=` operator, or `null` address in put +* `extend`: treats the supplied value as an iterable set of values to insert at a given index, extending the list +by this amount of items. Again use `null` address/index to point to the end of the list + +Due to the extra mode parameter, there is no chaining for `put`, but you can still use l-value container access to +indicate container and address, so `put(foo, key, value)` is the same as `put(foo:key, value)` or `foo:key=value` + +Returns true, if container got modified, false otherwise, and null if operation was invalid. + +**Maps** + +For maps there are no modes available (yet, seems there is no reason to). It replaces the value under the supplied +key (address), or sets it if not currently present. + +**NBT Tags** + +The address for nbt values is a valid nbt path that you would use with `/data` command, and tag is any tag that +would be applicable for a given insert operation. Note that to distinguish between proper types (like integer types, +you need to use command notation, i.e. regular ints is `123`, while byte size int would be `123b` and an explicit +string would be `"5"`, so it helps that scarpet uses single quotes in his strings. Unlike for lists and maps, it +returns the number of affected nodes, or 0 if none were affected. + +There are three modes that NBT tags can have items added to them: + +* `replace`(default): Replaces item under given path(address). Removes them first if possible, and then adds given +element to the supplied position. The target path can indicate compound tag keys, lists, or individual elements +of the lists. +* `
+a = [1, 2, 3]; put(a, 1, 4); a => [1, 4, 3] +a = [1, 2, 3]; put(a, null, 4); a => [1, 2, 3, 4] +a = [1, 2, 3]; put(a, 1, 4, 'insert'); a => [1, 4, 2, 3] +a = [1, 2, 3]; put(a, null, [4, 5, 6], 'extend'); a => [1, 2, 3, 4, 5, 6] +a = [1, 2, 3]; put(a, 1, [4, 5, 6], 'extend'); a => [1, 4, 5, 6, 2, 3] +a = [[0,0,0],[0,0,0],[0,0,0]]; put(a:1, 1, 1); a => [[0, 0, 0], [0, 1, 0], [0, 0, 0]] +a = {1,2,3,4}; put(a, 5, null); a => {1: null, 2: null, 3: null, 4: null, 5: null} +tag = nbt('{}'); put(tag, 'BlockData.Properties', '[1,2,3,4]'); tag => {BlockData:{Properties:[1,2,3,4]}} +tag = nbt('{a:[{lvl:3},{lvl:5},{lvl:2}]}'); put(tag, 'a[].lvl', 1); tag => {a:[{lvl:1},{lvl:1},{lvl:1}]} +tag = nbt('{a:[{lvl:[1,2,3]},{lvl:[3,2,1]},{lvl:[4,5,6]}]}'); put(tag, 'a[].lvl', 1, 2); tag + => {a:[{lvl:[1,2,1,3]},{lvl:[3,2,1,1]},{lvl:[4,5,1,6]}]} +tag = nbt('{a:[{lvl:[1,2,3]},{lvl:[3,2,1]},{lvl:[4,5,6]}]}'); put(tag, 'a[].lvl[1]', 1); tag + => {a:[{lvl:[1,1,3]},{lvl:[3,1,1]},{lvl:[4,1,6]}]} ++ +## List operations + +### `[value, ...?]`,`[iterator]`,`l(value, ...?)`, `l(iterator)` + +Creates a list of values of the expressions passed as parameters. It can be used as an L-value and if all +elements are variables, you coujld use it to return multiple results from one function call, if that +function returns a list of results with the same size as the `[]` call uses. In case there is only one +argument and it is an iterator (vanilla expression specification has `range`, but Minecraft API implements +a bunch of them, like `diamond`), it will convert it to a proper list. Iterators can only be used in high order +functions, and are treated as empty lists, unless unrolled with `[]`. + +Internally, `[elem, ...]`(list syntax) and `l(elem, ...)`(function syntax) are equivalent. `[]` is simply translated to +`l()` in the scarpet preprocessing stage. This means that internally the code has always expression syntax despite `[]` +not using different kinds of brackets and not being proper operators. This means that `l(]` and `[)` are also valid +although not recommended as they will make your code far less readable. + +
+l(1,2,'foo') <=> [1, 2, 'foo'] +l() <=> [] (empty list) +[range(10)] => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +[1, 2] = [3, 4] => Error: l is not a variable +[foo, bar] = [3, 4]; foo==3 && bar==4 => 1 +[foo, bar, baz] = [2, 4, 6]; [min(foo, bar), baz] = [3, 5]; [foo, bar, baz] => [3, 4, 5] ++ +In the last example `[min(foo, bar), baz]` creates a valid L-value, as `min(foo, bar)` finds the lower of the +variables (in this case `foo`) creating a valid assignable L-list of `[foo, baz]`, and these values +will be assigned new values + +### `join(delim, list), join(delim, values ...)` + +Returns a string that contains joined elements of the list, iterator, or all values, +concatenated with `delim` delimiter + +
+join('-',range(10)) => 0-1-2-3-4-5-6-7-8-9 +join('-','foo') => foo +join('-', 'foo', 'bar') => foo-bar ++ +### `split(delim?, expr)` + +Splits a string under `expr` by `delim` which can be a regular expression. If no delimiter is specified, it splits +by characters. + +If `expr` is a list, it will split the list into multiple sublists by the element (s) which equal `delim`, or which equal the empty string +in case no delimiter is specified. + +Splitting a `null` value will return an empty list. + +
+split('foo') => [f, o, o] +split('','foo') => [f, o, o] +split('.','foo.bar') => [] +split('\\.','foo.bar') => [foo, bar] +split(1,[2,5,1,2,3,1,5,6]) => [[2,5],[2,3],[5,6]] +split(1,[1,2,3,1,4,5,1] => [[], [2,3], [4,5], []] +split(null) => [] ++ +### `slice(expr, from, to?)` + +extracts a substring, or sublist (based on the type of the result of the expression under expr with +starting index of `from`, and ending at `to` if provided, or the end, if omitted. Can use negative indices to +indicate counting form the back of the list, so `-1 <=> length(_)`. + +Special case is made for iterators (`range`, `rect` etc), which does require non-negative indices (negative `from` is treated as +`0`, and negative `to` as `inf`), but allows retrieving parts of the sequence and ignore +other parts. In that case consecutive calls to `slice` will refer to index `0` the current iteration position since iterators +cannot go back nor track where they are in the sequence (see examples). + +
+slice([0,1,2,3,4,5], 1, 3) => [1, 2] +slice('foobar', 0, 1) => 'f' +slice('foobar', 3) => 'bar' +slice(range(10), 3, 5) => [3, 4] +slice(range(10), 5) => [5, 6, 7, 8, 9] +r = range(100); [slice(r, 5, 7), slice(r, 1, 3)] => [[5, 6], [8, 9]] ++ +### `sort(list), sort(values ...)` + +Sorts in the default sortographical order either all arguments, or a list if its the only argument. +It returns a new sorted list, not affecting the list passed to the argument + +
sort(3,2,1) => [1, 2, 3] +sort('a',3,11,1) => [1, 3, 11, 'a'] +list = [4,3,2,1]; sort(list) => [1, 2, 3, 4] ++ +### `sort_key(list, key_expr)` + +Sorts a copy of the list in the order or keys as defined by the `key_expr` for each element + +
+sort_key([1,3,2],_) => [1, 2, 3] +sort_key([1,3,2],-_) => [3, 2, 1] +sort_key([range(10)],rand(1)) => [1, 0, 9, 6, 8, 2, 4, 5, 7, 3] +sort_key([range(20)],str(_)) => [0, 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9] ++ +### `range(to), range(from, to), range(from, to, step)` + +Creates a range of numbers from `from`, no greater/larger than `to`. The `step` parameter dictates not only the +increment size, but also direction (can be negative). The returned value is not a proper list, just the iterator +but if for whatever reason you need a proper list with all items evaluated, use `[range(to)]`. +Primarily to be used in higher order functions + +
+range(10) => [...] +[range(10)] => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +map(range(10),_*_) => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] +reduce(range(10),_a+_, 0) => 45 +range(5,10) => [5, 6, 7, 8, 9] +range(20, 10, -2) => [20, 18, 16, 14, 12] +range(-0.3, 0.3, 0.1) => [-0.3, -0.2, -0.1, 0, 0.1, 0.2] +range(0.3, -0.3, -0.1) => [0.3, 0.2, 0.1, -0, -0.1, -0.2] ++ +## Map operations + +Scarpet supports map structures, aka hashmaps, dicts etc. Map structure can also be used, with `null` values as sets. +Apart from container access functions, (`. , get, put, has, delete`), the following functions: + +### `{values, ...}`,`{iterator}`,`{key -> value, ...}`,`m(values, ...)`, `m(iterator)`, `m(l(key, value), ...))` + +creates and initializes a map with supplied keys, and values. If the arguments contains a flat list, these are all +treated as keys with no value, same goes with the iterator - creates a map that behaves like a set. If the +arguments is a list of lists, they have to have two elements each, and then first is a key, and second, a value + +In map creation context (directly inside `{}` or `m{}` call), `->` operator acts like a pair constructor for simpler +syntax providing key value pairs, so the invocation to `{foo -> bar, baz -> quux}` is equivalent to +`{[foo, bar], [baz, quux]}`, which is equivalent to somewhat older, but more traditional functional form of +`m(l(foo, bar),l(baz, quuz))`. + +Internally, `{?}`(list syntax) and `m(?)`(function syntax) are equivalent. `{}` is simply translated to +`m()` in the scarpet preprocessing stage. This means that internally the code has always expression syntax despite `{}` +not using different kinds of brackets and not being proper operators. This means that `m(}` and `{)` are also valid +although not recommended as they will make your code far less readable. + +When converting map value to string, `':'` is used as a key-value separator due to tentative compatibility with NBT +notation, meaning in simpler cases maps can be converted to NBT parsable string by calling `str()`. This however +does not guarantee a parsable output. To properly convert to NBT value, use `encode_nbt()`. + +
+{1, 2, 'foo'} => {1: null, 2: null, foo: null} +m() <=> {} (empty map) +{range(10)} => {0: null, 1: null, 2: null, 3: null, 4: null, 5: null, 6: null, 7: null, 8: null, 9: null} +m(l(1, 2), l(3, 4)) <=> {1 -> 2, 3 -> 4} => {1: 2, 3: 4} +reduce(range(10), put(_a, _, _*_); _a, {}) + => {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81} ++ +### `keys(map), values(map), pairs(map)` + +Returns full lists of keys, values and key-value pairs (2-element lists) for all the entries in the map +# Minecraft specific API and `scarpet` language add-ons and commands + +Here is the gist of the Minecraft related functions. Otherwise the scarpet could live without Minecraft. + +## Global scarpet options + +These options affect directly how scarpet functions and can be triggered via `/carpet` command. + - `commandScript`: disables `/script` command making it impossible to control apps in game. Apps will still load and run + when loaded with the world (i.e. present in the world/scripts folder) + - `scriptsAutoload`: when set to `false` will prevent apps loaded with the world to load automatically. You can still + load them on demand via `/script load` command + - `commandScriptACE`: command permission level that is used to trigger commands from scarpet scripts (regardless who triggers + the code that calls the command). Defaults to `ops`, could be customized to any level via a numerical value (0, 1, 2, 3 or 4) + - `scriptsOptimization`: when disabled, disables default app compile time optimizations. If your app behaves differently with + and without optimizations, please file a bug report on the bug tracker and disable code optimizations. + - `scriptsDebugging`: Puts detailed information about apps loading, performance and runtime in system log. + - `scriptsAppStore`: location of the app store for downloadable scarpet apps - can be configured to point to other scarpet app store. + +## App structure + +The main delivery method for scarpet programs into the game is in the form of apps in `*.sc` files located in the world `scripts` +folder, flat. In singleplayer, you can also save apps in `.minecraft/config/carpet/scripts` for them to be available in any world, +and here you can actually organize them in folders. +When loaded (via `/script load` command, etc.), the game will run the content of the app once, regardless of its scope +(more about the app scopes below), without executing of any functions, unless called directly, and with the exception of the +`__config()` function, if present, which will be executed once. Loading the app will also bind specific +events to the event system (check Events section for details). + +If an app defines `__on_start()` function, it will be executed once before running anything else. For global scoped apps, +this is just after they are loaded, and for player scoped apps, before they are used first time by a player. +Unlike static code (written directly in the body of the app code), that always run once per app, this may run multiple times if +its a player app nd multiple players are on the server. + +Unloading an app removes all of its state from the game, disables commands, removes bounded events, and +saves its global state. If more cleanup is needed, one can define `__on_close()` function which will be +executed when the module is unloaded, or server is closing or crashing. However, there is no need to do that +explicitly for the things that clean up automatically, as indicated in the previous statement. With `'global'` scoped +apps `__on_close()` will execute once per app, and with `'player'` scoped apps, will execute once per player per app. + +### App config via `__config()` function + +If an app defines `__config` method, and that method returns a map, it will be used to apply custom settings +for this app. Currently, the following options are supported: + +* `'strict'` : if `true`, any use of an uninitialized variable will result in program failure. Defaults to `false` if +not specified. With `'strict'`you have to assign an initial value to any variable before using it. It is very useful +to use this setting for app debugging and for beginner programmers. Explicit initialization is not required for your +code to work, but mistakes may result from improper assumptions about initial variable values of `null`. +* `'scope'`: default scope for global variables for the app, Default is `'player'`, which means that globals and defined +functions will be unique for each player so that apps for each player will run in isolation. This is useful in +tool-like applications, where behaviour of things is always from a player's perspective. With player scope the initial run +of the app creates is initial state: defined functions, global variables, config and event handlers, which is then copied for +each player that interacts with the app. With `'global'` scope - the state created by the initial load is the only variant of +the app state and all players interactions run in the same context, sharing defined functions, globals, config and events. +`'global'` scope is most applicable to world-focused apps, where either players are not relevant, or player data is stored +explicitly keyed with players, player names, uuids, etc. +Even for `'player'` scoped apps, you can access specific player app with with commandblocks using +`/execute as
+block('air') => air +block('iron_trapdoor[half=top]') => iron_trapdoor +block(0,0,0) == block('bedrock') => 1 +block('hopper[facing=north]{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') => hopper ++ +Retrieving a block with `block` function has also a side-effect of evaluating its current state and data. +So if you use it later it will reflect block state and data of the block that was when block was called, rather than +when it was used. Block values passed in various places like `scan` functions, etc, are not fully evaluated unless +its properties are needed. This means that if the block at the location changes before its queried in the program this +might result in getting the later state, which might not be desired. Consider the following example: + +Throws `unknown_block` if provided input is not valid. + +
set(10,10,10,'stone'); +scan(10,10,10,0,0,0, b = _); +set(10,10,10,'air'); +print(b); // 'air', block was remembered 'lazily', and evaluated by `print`, when it was already set to air +set(10,10,10,'stone'); +scan(10,10,10,0,0,0, b = block(_)); +set(10,10,10,'air'); +print(b); // 'stone', block was evaluated 'eagerly' but call to `block` ++ +## World Manipulation + +All the functions below can be used with block value, queried with coord triple, or 3-long list. All `pos` in the +functions referenced below refer to either method of passing block position. + +### `set(pos, block, property?, value?, ..., block_data?)`, `set(pos, block, [property?, value?, ...], block_data?)`, `set(pos, block, {property? -> value?, ...}, block_data?)` + +First argument for the `set` function is either a coord triple, list of three numbers, or a world localized block value. +Second argument, `block`, is either an existing block value, a result of `block()` function, or a string value indicating the block name +with optional state and block data. It is then followed by an optional +`property - value` pairs for extra block state (which can also be provided in a list or a map). Optional `block_data` include the block data to +be set for the target block. + +If `block` is specified only by name, then if a +destination block is the same the `set` operation is skipped, otherwise is executed, for other potential extra +properties that the original source block may have contained. + +The returned value is either the block state that has been set, or `false` if block setting was skipped, or failed + +Throws `unknown_block` if provided block to set is not valid + +
+set(0,5,0,'bedrock') => bedrock +set([0,5,0], 'bedrock') => bedrock +set(block(0,5,0), 'bedrock') => bedrock +scan(0,5,0,0,0,0,set(_,'bedrock')) => 1 +set(pos(player()), 'bedrock') => bedrock +set(0,0,0,'bedrock') => 0 // or 1 in overworlds generated in 1.8 and before +scan(0,100,0,20,20,20,set(_,'glass')) + // filling the area with glass +scan(0,100,0,20,20,20,set(_,block('glass'))) + // little bit faster due to internal caching of block state selectors +b = block('glass'); scan(0,100,0,20,20,20,set(_,b)) + // yet another option, skips all parsing +set(x,y,z,'iron_trapdoor') // sets bottom iron trapdoor + +set(x,y,z,'iron_trapdoor[half=top]') // sets the top trapdoor +set(x,y,z,'iron_trapdoor','half','top') // also correct - top trapdoor +set(x,y,z,'iron_trapdoor', ['half','top']) // same +set(x,y,z,'iron_trapdoor', {'half' -> 'top'}) // same +set(x,y,z, block('iron_trapdoor[half=top]')) // also correct, block() provides extra parsing of block state + +set(x,y,z,'hopper[facing=north]{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') // extra block data +set(x,y,z,'hopper', {'facing' -> 'north'}, nbt('{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') ) // same ++ +### `without_updates(expr)` + +Evaluates subexpression without causing updates when blocks change in the world. + +For synchronization sake, as well as from the fact that suppressed update can only happen within a tick, +the call to the `expr` is docked on the main server task. + +Consider following scenario: We would like to generate a bunch of terrain in a flat world following a perlin noise +generator. The following code causes a cascading effect as blocks placed on chunk borders will cause other chunks to get +loaded to full, thus generated: + +
+__config() -> {'scope' -> 'global'}; +__on_chunk_generated(x, z) -> ( + scan(x,0,z,0,0,0,15,15,15, + if (perlin(_x/16, _y/8, _z/16) > _y/16, + set(_, 'black_stained_glass'); + ) + ) +) ++ +The following addition resolves this issue, by not allowing block updates past chunk borders: + +
+__config() -> {'scope' -> 'global'}; +__on_chunk_generated(x, z) -> ( + without_updates( + scan(x,0,z,0,0,0,15,15,15, + if (perlin(_x/16, _y/8, _z/16) > _y/16, + set(_, 'black_stained_glass'); + ) + ) + ) +) ++ +### `place_item(item, pos, facing?, sneak?)` + +Uses a given item in the world like it was used by a player. Item names are default minecraft item name, +less the minecraft prefix. Default facing is 'up', but there are other options: 'down', 'north', 'east', 'south', +'west', but also there are other secondary directions important for placement of blocks like stairs, doors, etc. +Try experiment with options like 'north-up' which is placed facing north with cursor pointing to the upper part of the +block, or 'up-north', which means a block placed facing up (player looking down) and placed smidge away of the block +center towards north. Optional sneak is a boolean indicating if a player would be sneaking while placing the +block - this option only affects placement of chests and scaffolding at the moment. + +Works with items that have the right-click effect on the block placed, like `bone_meal` on grass or axes on logs, +but doesn't open chests / containers, so have no effect on interactive blocks, like TNT, comparators, etc. + +Returns true if placement/use was +successful, false otherwise. + +Throws `unknown_item` if `item` doesn't exist + +
+place_item('stone',x,y,z) // places a stone block on x,y,z block +place_item('piston,x,y,z,'down') // places a piston facing down +place_item('carrot',x,y,z) // attempts to plant a carrot plant. Returns true if could place carrots at that position. +place_item('bone_meal',x,y,z) // attempts to bonemeal the ground. +place_item('wooden_axe',x,y,z) // attempts to strip the log. ++ +### `set_poi(pos, type, occupancy?)` + +Sets a Point of Interest (POI) of a specified type with optional custom occupancy. By default new POIs are not occupied. +If type is `null`, POI at position is removed. In any case, previous POI is also removed. Available POI types are: + +* `'unemployed', 'armorer', 'butcher', 'cartographer', 'cleric', 'farmer', 'fisherman', 'fletcher', 'leatherworker', 'librarian', 'mason', 'nitwit', 'shepherd', 'toolsmith', 'weaponsmith', 'home', 'meeting', 'beehive', 'bee_nest', 'nether_portal'` + +Interestingly, `unemployed`, and `nitwit` are not used in the game, meaning, they could be used as permanent spatial +markers for scarpet apps. `meeting` is the only one with increased max occupancy of 32. + +Throws `unknown_poi` if the provided point of interest doesn't exist + +### `set_biome(pos, biome_name, update=true)` + +Changes the biome at that block position. if update is specified and false, then chunk will not be refreshed +on the clients. Biome changes can only be sent to clients with the entire data from the chunk. + +Be aware that depending on the MC version and dimension settings biome can be set either in a 1x1x256 +column or 4x4x4 hyperblock, so for some versions Y will be ignored and for some precision of biome +setting is less than 1x1x1 block. + +Throws `unknown_biome` if the `biome_name` doesn't exist. + +### `update(pos)` + +Causes a block update at position. + +### `block_tick(pos)` + +Causes a block to tick at position. + +### `random_tick(pos)` + +Causes a random tick at position. + +### `destroy(pos), destroy(pos, -1), destroy(pos,
+mine(x,y,z) -> +( + p = player(); + slot = p~'selected_slot'; + item_tuple = inventory_get(p, slot); + if (!item_tuple, destroy(x,y,z,'air'); return()); // empty hand, just break with 'air' + [item, count, tag] = item_tuple; + tag_back = destroy(x,y,z, item, tag); + if (tag_back == false, // failed to break the item + return(tag_back) + ); + if (tag_back == true, // block broke, tool has no tag + return(tag_back) + ); + if (tag_back == null, //item broke + delete(tag:'Damage'); + inventory_set(p, slot, count-1, item, tag); + return(tag_back) + ); + if (type(tag_back) == 'nbt', // item didn't break, here is the effective nbt + inventory_set(p, slot, count, item, tag_back); + return(tag_back) + ); + print('How did we get here?'); +) ++ +### `harvest(player, pos)` + +Causes a block to be harvested by a specified player entity. Honors player item enchantments, as well as damages the +tool if applicable. If the entity is not a valid player, no block gets destroyed. If a player is not allowed to break +that block, a block doesn't get destroyed either. + +### `create_explosion(pos, power?, mode?, fire?, source?, attacker?)` + +Creates an explosion at a given position. Parameters work as follows: + - `'power'` - how strong the blast is, negative values count as 0 (default: `4` (TNT power)) + - `'mode'` - how to deal with broken blocks: `keep` keeps them, `destroy` destroys them and drops items, and `destroy_with_decay` destroys them, but doesn't always drop the items (default: `destroy_with_decay`) + - `fire` - whether extra fire blocks should be created (default: `false`) + - `source` - entity that is exploding. Note that it will not take explosion damage from this explosion (default: `null`) + - `attacker` - entity responsible for triggering, this will be displayed in death messages, and count towards kill counts, and can be damaged by the explosion (default: `null`) +Explosions created with this endpoint cannot be captured with `__on_explosion` event, however they will be captured +by `__on_explosion_outcome`. + +### `weather()`,`weather(type)`,`weather(type, ticks)` + +If called with no args, returns `'clear'`, `'rain` or `'thunder'` based on the current weather. If thundering, will +always return `'thunder'`, if not will return `'rain'` or `'clear'` based on the current weather. + +With one arg, (either `'clear'`, `'rain` or `'thunder'`), returns the number of remaining ticks for that weather type. +NB: It can thunder without there being a thunderstorm; there has to be both rain and thunder to form a storm. So if +running `weather()` returns `'thunder'`, you can use `weather('rain')>0` to see if there's a storm going on. + +With two args, sets the weather to the given `type` for `ticks` ticks. + +## Block and World querying + +### `pos(block), pos(entity)` + +Returns a triple of coordinates of a specified block or entity. Technically entities are queried with `query` function +and the same can be achieved with `query(entity,'pos')`, but for simplicity `pos` allows to pass all positional objects. + +
+pos(block(0,5,0)) => [0,5,0] +pos(player()) => [12.3, 45.6, 32.05] +pos(block('stone')) => Error: Cannot fetch position of an unrealized block ++ +### `pos_offset(pos, direction, amount?)` + +Returns a coords triple that is offset in a specified `direction` by `amount` of blocks. The default offset amount is +1 block. To offset into opposite facing, use negative numbers for the `amount`. + +
+pos_offset(block(0,5,0), 'up', 2) => [0,7,0] +pos_offset([0,5,0], 'up', -2 ) => [0,3,0] ++ +### `(Deprecated) block_properties(pos)` + +Deprecated by `keys(block_state(pos))`. + +### `(Deprecated) property(pos, name)` + +Deprecated by `block_state(pos, name)` + +### `block_state(block)`, `block_state(block, property)` + +If used with a `block` argument only, it returns a map of block properties and their values. If a block has no properties, returns an +empty map. + +If `property` is specified, returns a string value of that property, or `null` if property is not applicable. + +Returned values or properties are always strings. It is expected from the user to know what to expect and convert +values to numbers using `number()` function or booleans using `bool()` function. Returned string values can be directly used +back in state definition in various applications where block properties are required. + +`block_state` can also accept block names as input, returning block's default state. + +Throws `unknown_block` if the provided input is not valid. + +
+set(x,y,z,'iron_block'); block_state(x,y,z) => {} +set(x,y,z,'iron_trapdoor','half','top'); block_state(x,y,z) => {waterlogged: false, half: top, open: false, ...} +set(x,y,z,'iron_trapdoor','half','top'); block_state(x,y,z,'half') => top +block_state('iron_trapdoor','half') => top +set(x,y,z,'air'); block_state(x,y,z,'half') => null +block_state(block('iron_trapdoor[half=top]'),'half') => top +block_state(block('iron_trapdoor[half=top]'),'powered') => false +bool(block_state(block('iron_trapdoor[half=top]'),'powered')) => 0 ++ +### `block_list()`, `block_list(tag)` + +Returns list of all blocks in the game. If `tag` is provided, returns list of all blocks that belong to this block tag. +
+block_list() => [dark_oak_button, wall_torch, structure_block, polished_blackstone_brick_slab, cherry_sapling... ] +block_list('impermeable') => [glass, white_stained_glass, orange_stained_glass, magenta_stained_glass... ] //All da glass +block_list('rails') => [rail, powered_rail, detector_rail, activator_rail] +block_list('not_a_valid_block_tag') => null //Not a valid block tag ++ + +### `block_tags()`, `block_tags(block)`, `block_tags(block, tag)` + +Without arguments, returns list of available tags, with block supplied (either by coordinates, or via block name), returns lost +of tags the block belongs to, and if a tag is specified, returns `null` if tag is invalid, `false` if this block doesn't belong +to this tag, and `true` if the block belongs to the tag. + +Throws `unknown_block` if `block` doesn't exist + +
+block_tags() => [geode_invalid_blocks, wall_post_override, ice, wooden_stairs, bamboo_blocks, stone_bricks... ] +block_tags('iron_block') => [mineable/pickaxe, needs_stone_tool, beacon_base_blocks] +block_tags('glass') => [impermeable] +block_tags('glass', 'impermeable') => true +block_tags('glass', 'beacon_base_blocks') => false ++ +### `block_data(pos)` + +Return NBT string associated with specific location, or null if the block does not carry block data. Can be currently +used to match specific information from it, or use it to copy to another block + +
+block_data(x,y,z) => '{TransferCooldown:0,x:450,y:68, ... }' ++ +### `poi(pos), poi(pos, radius?, type?, status?, column_search?)` + +Queries a POI (Point of Interest) at a given position, returning `null` if none is found, or tuple of poi type and its +occupancy load. With optional `type`, `radius` and `status`, returns a list of POIs around `pos` within a +given `radius`. If the `type` is specified, returns only poi types of that types, or everything if omitted or `'any'`. +If `status` is specified (either `'any'`, `'available'`, or `'occupied'`) returns only POIs with that status. +With `column_search` set to `true`, it will return all POIs in a cuboid with `radius` blocks away on x and z, in the entire +block column from 0 to 255. Default (`false`) returns POIs within a spherical area centered on `pos` and with `radius` +radius. + +All results of `poi` calls are returned in sorted order with respect to the euclidean distance to the requested center of `pos`. + +The return format of the results is a list of poi type, occupancy load, and extra triple of coordinates. + +Querying for POIs using the radius is the intended use of POI mechanics, and the ability of accessing individual POIs +via `poi(pos)` in only provided for completeness. + +
+poi(x,y,z) => null // nothing set at position +poi(x,y,z) => ['meeting',3] // its a bell-type meeting point occupied by 3 villagers +poi(x,y,z,5) => [] // nothing around +poi(x,y,z,5) => [['nether_portal',0,[7,8,9]],['nether_portal',0,[7,9,9]]] // two portal blocks in the range ++ +### `biome()` `biome(name)` `biome(block)` `biome(block/name, feature)`, `biome(noise_map)` + +Without arguments, returns the list of biomes in the world. + +With block, or name, returns the name of the biome in that position, or throws `'unknown_biome'` if provided biome or block are not valid. + +(1.18+) if passed a map of `continentalness`, `depth`, `erosion`, `humidity`, `temperature`, `weirdness`, returns the biome that exists at those noise values. +Note: Have to pass all 6 of the mentioned noise types and only these noise types for it to evaluate a biome. + +With an optional feature, it returns value for the specified attribute for that biome. Available and queryable features include: +* `'top_material'`: unlocalized block representing the top surface material (1.17.1 and below only) +* `'under_material'`: unlocalized block representing what sits below topsoil (1.17.1 and below only) +* `'category'`: the parent biome this biome is derived from. Possible values include (1.18.2 and below only): +`'none'`, `'taiga'`, `'extreme_hills'`, `'jungle'`, `'mesa'`, `'plains'`, `'savanna'`, +`'icy'`, `'the_end'`, `'beach'`, `'forest'`, `'ocean'`, `'desert'`, `'river'`, +`'swamp'`, `'mushroom'` , `'nether'`, `'underground'` (1.18+) and `'mountain'` (1.18+). +* `'tags'`: list of biome tags associated with this biome +* `'temperature'`: temperature from 0 to 1 +* `'fog_color'`: RGBA color value of fog +* `'foliage_color'`: RGBA color value of foliage +* `'sky_color'`: RGBA color value of sky +* `'water_color'`: RGBA color value of water +* `'water_fog_color'`: RGBA color value of water fog +* `'humidity'`: value from 0 to 1 indicating how wet is the biome +* `'precipitation'`: `'rain'` `'snot'`, or `'none'`... ok, maybe `'snow'`, but that means snots for sure as well. +* `'depth'`: (1.17.1 and below only) float value indicating how high or low the terrain should generate. Values > 0 indicate generation above sea level +and values < 0, below sea level. +* `'scale'`: (1.17.1 and below only) float value indicating how flat is the terrain. +* `'features'`: list of features that generate in the biome, grouped by generation steps +* `'structures'`: (1.17.1 and below only) list of structures that generate in the biome. + +### `solid(pos)` + +Boolean function, true if the block is solid. + +### `air(pos)` + +Boolean function, true if a block is air... or cave air... or void air... or any other air they come up with. + +### `liquid(pos)` + +Boolean function, true if the block is liquid, or waterlogged (with any liquid). + +### `flammable(pos)` + +Boolean function, true if the block is flammable. + +### `transparent(pos)` + +Boolean function, true if the block is transparent. + +### `opacity(pos)` + +Numeric function, returning the opacity level of a block. + +### `blocks_daylight(pos)` + +Boolean function, true if the block blocks daylight. + +### `emitted_light(pos)` + +Numeric function, returning the light level emitted from the block. + +### `light(pos)` + +Numeric function, returning the total light level at position. + +### `block_light(pos)` + +Numeric function, returning the block light at position (from torches and other light sources). + +### `sky_light(pos)` + +Numeric function, returning the sky light at position (from sky access). + +### `effective_light(pos)` + +Numeric function, returning the "real" light at position, which is affected by time and weather. which also affects mobs spawning, frosted ice blocks melting. + +### `see_sky(pos)` + +Boolean function, returning true if the block can see sky. + +### `hardness(pos)` + +Numeric function, indicating hardness of a block. + +### `blast_resistance(pos)` + +Numeric function, indicating blast_resistance of a block. + +### `in_slime_chunk(pos)` + +Boolean indicating if the given block position is in a slime chunk. + +### `top(type, pos)` + +Returns the Y value of the topmost block at given x, z coords (y value of a block is not important), according to the +heightmap specified by `type`. Valid options are: + +* `light`: topmost light blocking block (1.13 only) +* `motion`: topmost motion blocking block +* `terrain`: topmost motion blocking block except leaves +* `ocean_floor`: topmost non-water block +* `surface`: topmost surface block + +
+top('motion', x, y, z) => 63 +top('ocean_floor', x, y, z) => 41 ++ +### `suffocates(pos)` + +Boolean function, true if the block causes suffocation. + +### `power(pos)` + +Numeric function, returning redstone power level at position. + +### `ticks_randomly(pos)` + +Boolean function, true if the block ticks randomly. + +### `blocks_movement(pos)` + +Boolean function, true if the block at position blocks movement. + +### `block_sound(pos)` + +Returns the name of sound type made by the block at position. One of: + +`'wood'`, `'gravel'`, `'grass'`, `'stone'`, `'metal'`, `'glass'`, `'wool'`, `'sand'`, `'snow'`, +`'ladder'`, `'anvil'`, `'slime'`, `'sea_grass'`, `'coral'`, `'bamboo'`', `'shoots'`', `'scaffolding'`', `'berry'`', `'crop'`', +`'stem'`', `'wart'`', +`'lantern'`', `'fungi_stem'`', `'nylium'`', `'fungus'`', `'roots'`', `'shroomlight'`', `'weeping_vines'`', `'soul_sand'`', + `'soul_soil'`', `'basalt'`', +`'wart'`', `'netherrack'`', `'nether_bricks'`', `'nether_sprouts'`', `'nether_ore'`', `'bone'`', `'netherite'`', `'ancient_debris'`', +`'lodestone'`', `'chain'`', `'nether_gold_ore'`', `'gilded_blackstone'`', +`'candle'`', `'amethyst'`', `'amethyst_cluster'`', `'small_amethyst_bud'`', `'large_amethyst_bud'`', `'medium_amethyst_bud'`', +`'tuff'`', `'calcite'`', `'copper'`' + +### `(Deprecated) material(pos)` + +Returns `'unknown'`. The concept of material for blocks is removed. On previous versions it returned the name of the material the block +was made of. + +### `map_colour(pos)` + +Returns the map colour of a block at position. One of: + +`'air'`, `'grass'`, `'sand'`, `'wool'`, `'tnt'`, `'ice'`, `'iron'`, `'foliage'`, `'snow'`, `'clay'`, `'dirt'`, +`'stone'`, `'water'`, `'wood'`, `'quartz'`, `'adobe'`, `'magenta'`, `'light_blue'`, `'yellow'`, `'lime'`, `'pink'`, +`'gray'`, `'light_gray'`, `'cyan'`, `'purple'`, `'blue'`, `'brown'`, `'green'`, `'red'`, `'black'`, `'gold'`, +`'diamond'`, `'lapis'`, `'emerald'`, `'obsidian'`, `'netherrack'`, `'white_terracotta'`, `'orange_terracotta'`, +`'magenta_terracotta'`, `'light_blue_terracotta'`, `'yellow_terracotta'`, `'lime_terracotta'`, `'pink_terracotta'`, +`'gray_terracotta'`, `'light_gray_terracotta'`, `'cyan_terracotta'`, `'purple_terracotta'`, `'blue_terracotta'`, +`'brown_terracotta'`, `'green_terracotta'`, `'red_terracotta'`, `'black_terracotta'`, +`'crimson_nylium'`, `'crimson_stem'`, `'crimson_hyphae'`, `'warped_nylium'`, `'warped_stem'`, `'warped_hyphae'`, `'warped_wart'` + +### `sample_noise()`, `sample_noise(pos, ... types?)` 1.18+ + +Samples the world generation noise values / data driven density function(s) at a given position. + +If no types are passed in, or no arguments are given, it returns a list of all the available registry defined density functions. + +With a single function name passed in, it returns a scalar. With multiple function names passed in, it returns a list of results. + +Function accepts any registry defined density functions, both built in, as well as namespaced defined in datapacks. +On top of that, scarpet provides the following list of noises sampled directly from the current level (and not returned with no-argument call): + + +`'barrier_noise'`, `'fluid_level_floodedness_noise'`, `'fluid_level_spread_noise'`, `'lava_noise'`, +`'temperature'`, `'vegetation'`, `'continents'`, `'erosion'`, `'depth'`, `'ridges'`, +`'initial_density_without_jaggedness'`, `'final_density'`, `'vein_toggle'`, `'vein_ridged'` and `'vein_gap'` + +
+// requesting single value +sample_density(pos, 'continents') => 0.211626790923 +// passing type as multiple arguments +sample_density(pos, 'continents', 'depth', 'overworld/caves/pillars', 'mydatapack:foo/my_function') => [-0.205013844481, 1.04772473438, 0.211626790923, 0.123] ++ +### `loaded(pos)` + +Boolean function, true if the block is accessible for the game mechanics. Normally `scarpet` doesn't check if operates +on loaded area - the game will automatically load missing blocks. We see this as an advantage. Vanilla `fill/clone` +commands only check the specified corners for loadness. + +To check if a block is truly loaded, I mean in memory, use `generation_status(x) != null`, as chunks can still be loaded +outside of the playable area, just are not used by any of the game mechanic processes. + +
+loaded(pos(player())) => 1 +loaded(100000,100,1000000) => 0 ++ +### `(Deprecated) loaded_ep(pos)` + +Boolean function, true if the block is loaded and entity processing, as per 1.13.2 + +Deprecated as of scarpet 1.6, use `loaded_status(x) > 0`, or just `loaded(x)` with the same effect + +### `loaded_status(pos)` + +Returns loaded status as per new 1.14 chunk ticket system, 0 for inaccessible, 1 for border chunk, 2 for redstone ticking, +3 for entity ticking + +### `is_chunk_generated(pos)`, `is_chunk_generated(pos, force)` + +Returns `true` if the region file for the chunk exists, +`false` otherwise. If optional force is `true` it will also check if the chunk has a non-empty entry in its region file +Can be used to assess if the chunk has been touched by the game or not. + +`generation_status(pos, false)` only works on currently loaded chunks, and `generation_status(pos, true)` will create +an empty loaded chunk, even if it is not needed, so `is_chunk_generated` can be used as a efficient proxy to determine +if the chunk physically exists. + +Running `is_chunk_generated` is has no effects on the world, but since it is an external file operation, it is +considerably more expensive (unless area is loaded) than other generation and loaded checks. + +### `generation_status(pos), generation_status(pos, true)` + +Returns generation status as per the ticket system. Can return any value from several available but chunks +can only be stable in a few states: `full`, `features`, `liquid_carvers`, and `structure_starts`. Returns `null` +if the chunk is not in memory unless called with optional `true`. + +### `inhabited_time(pos)` + +Returns inhabited time for a chunk. + +### `spawn_potential(pos)` + +Returns spawn potential at a location (1.16+ only) + +### `reload_chunk(pos)` + +Sends full chunk data to clients. Useful when lots stuff happened and you want to refresh it on the clients. + +### `reset_chunk(pos)`, `reset_chunk(from_pos, to_pos)`, `reset_chunk([pos, ...])` +Removes and resets the chunk, all chunks in the specified area or all chunks in a list at once, removing all previous +blocks and entities, and replacing it with a new generation. For all currently loaded chunks, they will be brought +to their current generation status, and updated to the player. All chunks that are not in the loaded area, will only +be generated to the `'structure_starts'` status, allowing to generate them fully as players are visiting them. +Chunks in the area that has not been touched yet by the game will not be generated / regenerated. + +It returns a `map` with a report indicating how many chunks were affected, and how long each step took: + * `requested_chunks`: total number of chunks in the requested area or list + * `affected_chunks`: number of chunks that will be removed / regenerated + * `loaded_chunks`: number of currently loaded chunks in the requested area / list + * `relight_count`: number of relit chunks + * `relight_time`: time took to relit chunks + * `layer_count_
+for(neighbours(x,y,z),air(_)) => 4 // number of air blocks around a block ++ +### `rect(center, range?, upper_range?)` + +Returns an iterator, just like `range` function that iterates over a rectangular area of blocks. If only center +point is specified, it iterates over 27 blocks (range of 1). If `range` arguments are specified, expands selection by +the respective number of blocks in each direction. If `upper_range` arguments are specified, it uses `range` for +negative offset, and `upper_range` for positive, similar to `scan`. + +Basically the arguments are the same as the first three arguments of `scan`, except this function returns the list of +blocks that `scan` would evaluate over. If you are going to iterate over these blocks, like `for(rect(args), do_something())`, +then `scan(args, do_something())` is an equivalent, yet more compute-friendly alternative, especially for very large areas. + +`center` can be defined either as three coordinates, a list of three coords, or a block value. +`range` and `upper_range` can have the same representations, just if they are block values, it computes the distance to the center +as range instead of taking the values as is.` + +### `diamond(center_pos, radius?, height?)` + +Iterates over a diamond like area of blocks. With no radius and height, its 7 blocks centered around the middle +(block + neighbours). With a radius specified, it expands shape on x and z coords, and with a custom height, on y. +Any of these can be zero as well. radius of 0 makes a stick, height of 0 makes a diamond shape pad. + +If radius and height are the same, creats a 3D diamond, of all the blocks which are a manhattan distance of `radius` away +from the center. +# Entity API + +## Entity Selection + +Entities have to be fetched before using them. Entities can also change their state between calls to the script if +game ticks occur either in between separate calls to the programs, or if the program calls `game_tick` on its own. +In this case - entities would need to be re-fetched, or the code should account for entities dying. + +### `player(), player(type), player(name)` + +With no arguments, it returns the calling player or the player closest to the caller. +For player-scoped apps (which is a default) its always the owning player or `null` if it not present even if some code +still runs in their name. +Note that the main context +will receive `p` variable pointing to this player. With `type` or `name` specified, it will try first to match a type, +returning a list of players matching a type, and if this fails, will assume its player name query retuning player with +that name, or `null` if no player was found. With `'all'`, list of all players in the game, in all dimensions, so end +user needs to be cautious, that you might be referring to wrong blocks and entities around the player in question. +With `type = '*'` it returns all players in caller dimension, `'survival'` returns all survival and adventure players, +`'creative'` returns all creative players, `'spectating'` returns all spectating players, and `'!spectating'`, +all not-spectating players. If all fails, with `name`, the player in question, if he/she is logged in. + +### `entity_id(uuid), entity_id(id)` + +Fetching entities either by their ID obtained via `entity ~ 'id'`, which is unique for a dimension and current world +run, or by UUID, obtained via `entity ~ 'uuid'`. It returns null if no such entity is found. Safer way to 'store' +entities between calls, as missing entities will be returning `null`. Both calls using UUID or numerical ID are `O(1)`, +but obviously using UUIDs takes more memory and compute. + +### `entity_list(descriptor)` + +Returns global lists of entities in the current dimension matching specified descriptor. +Calls to `entity_list` always fetch entities from the current world that the script executes. + +### `entity_types(descriptor)` + +Resolves a given descriptor returning list of entity types that match it. The returned list of types is also a valid list +of descriptors that can be use elsewhere where entity types are required. + +Currently, the following descriptors are available: + +* `*`: all entities, even `!valid`, matches all entity types. +* `valid` - all entities that are not dead (health > 0). All main categories below also return only +entities in the `valid` category. matches all entity types. `!valid` matches all entites that are already dead of all types. +* `living` - all entities that resemble a creature of some sort +* `projectile` - all entities or types that are not living that can be throw or projected, `!projectile` matches all types that + are not living, but cannot the thrown or projected. +* `minecarts` matches all minecart types. `!minecarts` matches all types that are not live, but also not minecarts. Using plural +since `minecart` is a proper entity type on its own. +* `undead`, `arthropod`, `aquatic`, `regular`, `illager` - all entities / types that belong to any of these groups. All +living entities belong to one and only one of these. Corresponding negative (e.g. `!undead`) corresponds to all mobs that are +living but don't belong to that group. Entity groups are used in interaction / battle mechanics like smite for undead, or impaling +for aquatic. Also certain mechanics interact with groups, like ringing a bell with illagers. All other mobs that don't have any of these traits belong +to the `regular` group. +* `monster`, `creature`, `ambient`, `water_creature`, `water_ambient`, `misc` - another categorization of +living entities based on their spawn group. Negative descriptor resolves to all living types that don't belong to that +category. +* All entity tags including those provided with datapacks. Built-in entity tags include: `skeletons`, `raiders`, +`beehive_inhabitors` (bee, duh), `arrows` and `impact_projectiles`. +* Any of the standard entity types, equivalent to selection from `/summon` vanilla command, which is one of the options returned +by `entity_types()`, except for `'fishing_bobber'` and `'player'`. + +All categories can be preceded with `'!'` which will fetch all entities (unless otherwise noted) that are valid (health > 0) but not +belonging to that group. + +### `entity_area(type, center, distance)` + + +Returns entities of a specified type in an area centered on `center` and at most `distance` blocks away from +the center point/area. Uses the same `type` selectors as `entities_list`. + +`center` and `distance` can either be a triple of coordinates or three consecutive arguments for `entity_area`. `center` can +also be represented as a block, in this case the search box will be centered on the middle of the block, or an entity - in this case +entire bounding box of the entity serves as a 'center' of search which is then expanded in all directions with the `'distance'` vector. + +In any case - returns all entities which bounding box collides with the bounding box defined by `'center'` and `'distance'`. + +entity_area is simpler than `entity_selector` and runs about 20% faster, but is limited to predefined selectors and +cuboid search area. + +### `entity_selector(selector)` + +Returns entities satisfying given vanilla entity selector. Most complex among all the methods of selecting entities, +but the most capable. Selectors are cached so it should be as fast as other methods of selecting entities. Unlike other +entities fetching / filtering method, this one doesn't guarantee to return entities from current dimension, since +selectors can return any loaded entity in the world. + +### `spawn(name, pos, nbt?)` + +Spawns and places an entity in world, like `/summon` vanilla command. Requires a position to spawn, and optional +extra nbt data to merge with the entity. What makes it different from calling `run('summon ...')`, is the fact that +you get the entity back as a return value, which is swell. + +## Entity Manipulation + +Unlike with blocks, that use a plethora of vastly different querying functions, entities are queried with the `query` +function and altered via the `modify` function. Type of information needed or values to be modified are different for +each call. + +Using `~` (in) operator is an alias for `query`. Especially useful if a statement has no arguments, +which in this case can be radically simplified: + +
+query(p, 'name') <=> p ~ 'name' // much shorter and cleaner +query(p, 'holds', 'offhand') <=> p ~ ['holds', 'offhand'] // not really but can be done ++ +### `query(e, 'removed')` + +Boolean. True if the entity is removed. + +### `query(e, 'id')` + +Returns numerical id of the entity. Most efficient way to keep track of entities in a script. +Ids are only unique within current game session (ids are not preserved between restarts), +and dimension (each dimension has its own ids which can overlap). + +### `query(e, 'uuid')` + +Returns the UUID (unique id) of the entity. Can be used to access entities with the other vanilla commands and +remains unique regardless of the dimension, and is preserved between game restarts. Apparently players cannot be +accessed via UUID, but should be accessed with their name instead. + +
+map(entities_area('*',x,y,z,30,30,30),run('kill '+query(_,'id'))) // doesn't kill the player ++ +### `query(e, 'pos')` + +Triple of the entity's position + +### `query(e, 'location')` + +Quin-tuple of the entity's position (x, y, and z coords), and rotation (yaw, pitch) + +### `query(e, 'x'), query(e, 'y'), query(e, 'z')` + +Respective component of entity's coordinates + +### `query(e, 'pitch')`, `query(e, 'yaw')` + +Pitch and Yaw or where entity is looking. + +### `query(e, 'head_yaw')`, `query(e, 'body_yaw')` + +Applies to living entites. Sets their individual head and body facing angle. + +### `query(e, 'look')` + +Returns a 3d vector where the entity is looking. + +### `query(e, 'motion')` + +Triple of entity's motion vector, `[motion_x, motion_y, motion_z]`. Motion represents the velocity from all the forces +that exert on the given entity. Things that are not 'forces' like voluntary movement, or reaction from the ground are +not part of said forces. + +### `query(e, 'motion_x'), query(e, 'motion_y'), query(e, 'motion_z')` + +Respective component of the entity's motion vector + +### `query(e, 'on_ground')` + +Returns `true` if an entity is standing on firm ground and falling down due to that. + +### `query(e, 'name'), query(e, 'display_name'), query(e, 'custom_name'), query(e, 'type')` + +String of entity name or formatted text in the case of `display_name` + +
+query(e,'name') => Leatherworker +query(e,'custom_name') => null +query(e,'type') => villager ++ +### `query(e, 'command_name')` + +Returns a valid string to be used in commands to address an entity. Its UUID for all entities except +player, where its their name. + +
+run('/kill ' + e~'command_name'); ++ +### `query(e, 'persistence')` + +Returns if a mob has a persistence tag or not. Returns `null` for non-mob entities. + +### `query(e, 'is_riding')` + +Boolean, true if the entity is riding another entity. + +### `query(e, 'is_ridden')` + +Boolean, true if another entity is riding it. + +### `query(e, 'passengers')` + +List of entities riding the entity. + +### `query(e, 'mount')` + +Entity that `e` rides. + +### `query(e, 'unmountable')` + +Boolean, true if the entity cannot be mounted. + +### `(deprecated) query(e, 'tags')` + +Deprecated by `query(e, 'scoreboard_tags')` + +### `query(e, 'scoreboard_tags')` + +List of entity's scoreboard tags. + +### `(deprecated) query(e, 'has_tag',tag)` + +Deprecated by `query(e, 'has_scoreboard_tag',tag)` + +### `query(e, 'has_scoreboard_tag',tag)` + +Boolean, true if the entity is marked with a `tag` scoreboad tag. + +### `query(e, 'entity_tags')` + +List of entity tags assigned to the type this entity represents. + +### `query(e, 'has_entity_tag', tag)` + +Returns `true` if the entity matches that entity tag, `false` if it doesn't, and `null` if the tag is not valid. + +### `query(e, 'is_burning')` + +Boolean, true if the entity is burning. + +### `query(e, 'fire')` + +Number of remaining ticks of being on fire. + +### `query(e, 'is_freezing')` + +Boolean, true if the entity is freezing. + +### `query(e, 'frost')` + +Number of remaining ticks of being frozen. + +### `query(e, 'silent')` + +Boolean, true if the entity is silent. + +### `query(e, 'gravity')` + +Boolean, true if the entity is affected by gravity, like most entities are. + +### `query(e, 'invulnerable')` + +Boolean, true if the entity is invulnerable. + +### `query(e, 'immune_to_fire')` + +Boolean, true if the entity is immune to fire. + +### `query(e, 'immune_to_frost')` + +Boolean, true if the entity is immune to frost. + +### `query(e, 'dimension')` + +Name of the dimension the entity is in. + +### `query(e, 'height')` + +Height of the entity in blocks. + +### `query(e, 'width')` + +Width of the entity in blocks. + +### `query(e, 'eye_height')` + +Eye height of the entity in blocks. + +### `query(e, 'age')` + +Age of the entity in ticks, i.e. how long it existed. + +### `query(e, 'breeding_age')` + +Breeding age of passive entity, in ticks. If negative, time to adulthood, if positive, breeding cooldown. + +### `query(e, 'despawn_timer')` + +For living entities, the number of ticks they fall outside of immediate player presence. + +### `query(e, 'portal_cooldown')` + +Number of ticks remaining until an entity can use a portal again. + +### `query(e, 'portal_timer')` + +Number of ticks an entity sits in a portal. + +### `query(e, 'item')` + +The item triple (name, count, nbt) if its an item or item frame entity, `null` otherwise. + +### `query(e, 'offering_flower')` + +Whether the given iron golem has a red flower in their hand. returns null for all other entities + + +### `query(e, 'blue_skull')` + +Whether the given wither skull entity is blue. returns null for all other entities + + +### `query(e, 'count')` + +Number of items in a stack from item entity, `null` otherwise. + +### `query(e, 'pickup_delay')` + +Retrieves pickup delay timeout for an item entity, `null` otherwise. + +### `query(e, 'is_baby')` + +Boolean, true if its a baby. + +### `query(e, 'target')` + +Returns mob's attack target or null if none or not applicable. + +### `query(e, 'home')` + +Returns creature's home position (as per mob's AI, leash etc) or null if none or not applicable. + +### `query(e, 'spawn_point')` + +Returns position tuple, dimension, spawn angle, and whether spawn is forced, assuming the player has a spawn position. +Returns `false` if spawn position is not set, and `null` if `e` is not a player. + +### `query(e, 'path')` + +Returns path of the entity if present, `null` otherwise. The path comprises of list of nodes, each is a list +of block value, node type, penalty, and a boolean indicated if the node has been visited. + +### `query(e, 'pose')` + +Returns a pose of an entity, one of the following options: + * `'standing'` + * `'fall_flying'` + * `'sleeping'` + * `'swimming'` + * `'spin_attack'` + * `'crouching'` + * `'dying'` + +### `query(e, 'sneaking')` + +Boolean, true if the entity is sneaking. + +### `query(e, 'sprinting')` + +Boolean, true if the entity is sprinting. + +### `query(e, 'swimming')` + +Boolean, true if the entity is swimming. + +### `query(e, 'jumping')` + +Boolean, true if the entity is jumping. + +### `query(e, 'swinging')` + +Returns `true` if the entity is actively swinging their hand, `false` if not and `null` if swinging is not applicable to +that entity. + +### `query(e, 'gamemode')` + +String with gamemode, or `null` if not a player. + +### `query(e, 'gamemode_id')` + +Good'ol gamemode id, or null if not a player. + +### `query(e, 'player_type')` + +Returns `null` if the argument is not a player, otherwise: + +* `singleplayer`: for singleplayer game +* `multiplayer`: for players on a dedicated server +* `lan_host`: for singleplayer owner that opened the game to LAN +* `lan_player`: for all other players that connected to a LAN host +* `fake`: any carpet-spawned fake player +* `shadow`: any carpet-shadowed real player +* `realms`: ? + +### `query(e, 'category')` +Returns a lowercase string containing the category of the entity (hostile, passive, water, ambient, misc). + +### `query(e, 'team')` + +Team name for entity, or `null` if no team is assigned. + +### `query(e, 'ping')` + +Player's ping in milliseconds, or `null` if its not a player. + +### `query(e, 'permission_level')` + +Player's permission level, or `null` if not applicable for this entity. + +### `query(e, 'client_brand')` + +Returns recognized type of client of the client connected. Possible results include `'vanilla'`, or `'carpet
+query(p,'effect') => [[haste, 0, 177], [speed, 0, 177]] +query(p,'effect','haste') => [0, 177] +query(p,'effect','resistance') => null ++ +### `query(e, 'health')` + +Number indicating remaining entity health, or `null` if not applicable. + +### `query(e, 'may_fly')` + +Returns a boolean indicating if the player can fly. + +### `query(e, 'flying')` + +Returns a boolean indicating if the player is flying. + +### `query(e, 'may_build')` + +Returns a boolean indicating if the player is allowed to place blocks. + +### `query(e, 'insta_build')` + +Returns a boolean indicating if the player can place blocks without consuming the item and if the player can shoot arrows without having them in the inventory. + +### `query(e, 'fly_speed')` + +Returns a number indicating the speed at which the player moves while flying. + +### `query(e, 'walk_speed')` + +Returns a number indicating the speed at which the player moves while walking. + +### `query(e, 'hunger')` +### `query(e, 'saturation')` +### `query(e, 'exhaustion')` + +Retrieves player hunger related information. For non-players, returns `null`. + +### `query(e, 'absorption')` + +Gets the absorption of the player (yellow hearts, e.g. when having a golden apple.) + +### `query(e, 'xp')` +### `query(e, 'xp_level')` +### `query(e, 'xp_progress')` +### `query(e, 'score')` + +Numbers related to player's xp. `xp` is the overall xp player has, `xp_level` is the levels seen in the hotbar, +`xp_progress` is a float between 0 and 1 indicating the percentage of the xp bar filled, and `score` is the number displayed upon death + +### `query(e, 'air')` + +Number indicating remaining entity air, or `null` if not applicable. + +### `query(e, 'language')` + +Returns `null` for any non-player entity, if not returns the player's language as a string. + +### `query(e, 'holds', slot?)` + +Returns triple of short name, stack count, and NBT of item held in `slot`, or `null` if nothing or not applicable. Available options for `slot` are: + +* `mainhand` +* `offhand` +* `head` +* `chest` +* `legs` +* `feet` + +If `slot` is not specified, it defaults to the main hand. + +### `query(e, 'selected_slot')` + +Number indicating the selected slot of entity's inventory. Currently only applicable to players. + +### `query(e, 'active_block')` + +Returns currently mined block by the player, as registered by the game server. + +### `query(e, 'breaking_progress')` + +Returns current breaking progress of a current player mining block, or `null`, if no block is mined. +Breaking progress, if not null, is any number 0 or above, while 10 means that the block should already be +broken by the client. This value may tick above 10, if the client / connection is lagging. + +Example: + +The following program provides custom breaking times, including nice block breaking animations, including instamine, for +blocks that otherwise would take longer to mine. + +[Video demo](https://youtu.be/zvEEuGxgCio) +```py +global_blocks = { + 'oak_planks' -> 0, + 'obsidian' -> 1, + 'end_portal_frame' -> 5, + 'bedrock' -> 10 +}; + +__on_player_clicks_block(player, block, face) -> +( + step = global_blocks:str(block); + if (step == 0, + destroy(block, -1); // instamine + , step != null, + schedule(0, '_break', player, pos(block), str(block), step, 0); + ) +); + +_break(player, pos, name, step, lvl) -> +( + current = player~'active_block'; + if (current != name || pos(current) != pos, + modify(player, 'breaking_progress', null); + , + modify(player, 'breaking_progress', lvl); + if (lvl >= 10, destroy(pos, -1)); + schedule(step, '_break', player, pos, name, step, lvl+1) + ); +) +``` + +### `query(e, 'facing', order?)` + +Returns where the entity is facing. optional order (number from 0 to 5, and negative), indicating primary directions +where entity is looking at. From most prominent (order 0) to opposite (order 5, or -1). + +### `query(e, 'trace', reach?, options?...)` + +Returns the result of ray tracing from entity perspective, indicating what it is looking at. Default reach is 4.5 +blocks (5 for creative players), and by default it traces for blocks and entities, identical to player attack tracing +action. This can be customized with `options`, use `'blocks'` to trace for blocks, `'liquids'` to include liquid blocks +as possible results, and `'entities'` to trace entities. You can also specify `'exact'` which returns the actual hit + coordinate as a triple, instead of a block or entity value. Any combination of the above is possible. When tracing +entities and blocks, blocks will take over the priority even if transparent or non-colliding +(aka fighting chickens in tall grass). + +Regardless of the options selected, the result could be: + - `null` if nothing is in reach + - an entity if look targets an entity + - block value if block is in reach, or + - a coordinate triple if `'exact'` option was used and hit was successful. + +### `query(e, 'attribute')` `query(e, 'attribute', name)` + +returns the value of an attribute of the living entity. If the name is not provided, +returns a map of all attributes and values of this entity. If an attribute doesn't apply to the entity, +or the entity is not a living entity, `null` is returned. + +### `query(e, 'brain', memory)` + +Retrieves brain memory for entity. Possible memory units highly depend on the game version. Brain is availalble +for villagers (1.15+) and Piglins, Hoglins, Zoglins and Piglin Brutes (1.16+). If memory is not present or +not available for the entity, `null` is returned. + +Type of the returned value (entity, position, number, list of things, etc) depends on the type of the requested +memory. On top of that, since 1.16, memories can have expiry - in this case the value is returned as a list of whatever +was there, and the current ttl in ticks. + +Available retrievable memories for 1.15.2: +* `home`, `job_site`, `meeting_point`, `secondary_job_site`, `mobs`, `visible_mobs`, `visible_villager_babies`, +`nearest_players`, `nearest_visible_player`, `walk_target`, `look_target`, `interaction_target`, +`breed_target`, `path`, `interactable_doors`, `opened_doors`, `nearest_bed`, `hurt_by`, `hurt_by_entity`, +`nearest_hostile`, `hiding_place`, `heard_bell_time`, `cant_reach_walk_target_since`, +`golem_last_seen_time`, `last_slept`, `last_woken`, `last_worked_at_poi` + +Available retrievable memories as of 1.16.2: +* `home`, `job_site`, `potential_job_site`, `meeting_point`, `secondary_job_site`, `mobs`, `visible_mobs`, +`visible_villager_babies`, `nearest_players`, `nearest_visible_players`, `nearest_visible_targetable_player`, +`walk_target`, `look_target`, `attack_target`, `attack_cooling_down`, `interaction_target`, `breed_target`, +`ride_target`, `path`, `interactable_doors`, `opened_doors`, `nearest_bed`, `hurt_by`, `hurt_by_entity`, `avoid_target`, +`nearest_hostile`, `hiding_place`, `heard_bell_time`, `cant_reach_walk_target_since`, `golem_detected_recently`, +`last_slept`, `last_woken`, `last_worked_at_poi`, `nearest_visible_adult`, `nearest_visible_wanted_item`, +`nearest_visible_nemesis`, `angry_at`, `universal_anger`, `admiring_item`, `time_trying_to_reach_admire_item`, +`disable_walk_to_admire_item`, `admiring_disabled`, `hunted_recently`, `celebrate_location`, `dancing`, +`nearest_visible_huntable_hoglin`, `nearest_visible_baby_hoglin`, `nearest_targetable_player_not_wearing_gold`, +`nearby_adult_piglins`, `nearest_visible_adult_piglins`, `nearest_visible_adult_hoglins`, +`nearest_visible_adult_piglin`, `nearest_visible_zombiefied`, `visible_adult_piglin_count`, +`visible_adult_hoglin_count`, `nearest_player_holding_wanted_item`, `ate_recently`, `nearest_repellent`, `pacified` + + +### `query(e, 'nbt', path?)` + +Returns full NBT of the entity. If path is specified, it fetches only the portion of the NBT that corresponds to the +path. For specification of `path` attribute, consult vanilla `/data get entity` command. + +Note that calls to `nbt` are considerably more expensive comparing to other calls in Minecraft API, and should only +be used when there is no other option. Returned value is of type `nbt`, which can be further manipulated with nbt +type objects via `get, put, has, delete`, so try to use API calls first for that. + +## Entity Modification + +Like with entity querying, entity modifications happen through one function. + +### `modify(e, 'remove')` + +Removes (not kills) entity from the game. + +### `modify(e, 'kill')` + +Kills the entity. + +### `modify(e, 'pos', x, y, z), modify(e, 'pos', [x,y,z] )` + +Moves the entity to a specified coords. + +### `modify(e, 'location', x, y, z, yaw, pitch), modify(e, 'location', [x, y, z, yaw, pitch] )` + +Changes full location vector all at once. + +### `modify(e, 'x', x), modify(e, 'y', y), modify(e, 'z', z)` + +Changes entity's location in the specified direction. + +### `modify(e, 'pitch', angle), modify(e, 'yaw', angle)` + +Changes entity's pitch or yaw angle. + +### `modify(e, 'look', x, y, z), modify(e, 'look', [x,y,z] )` + +Sets entity's 3d vector where the entity is looking. +For cases where the vector has a length of 0, yaw and pitch won't get changed. +When pointing straight up or down, yaw will stay the same. + +### `modify(e, 'head_yaw', angle)`, `modify(e, 'body_yaw', angle)` + +For living entities, controls their head and body yaw angle. + +### `modify(e, 'move', x, y, z), modify(e, 'move', [x,y,z] )` + +Moves the entity by a vector from its current location. + +### `modify(e, 'motion', x, y, z), modify(e, 'motion', [x,y,z] )` + +Sets the motion vector (where and how much entity is moving). + +### `modify(e, 'motion_x', x), modify(e, 'motion_y', y), modify(e, 'motion_z', z)` + +Sets the corresponding component of the motion vector. + +### `modify(e, 'accelerate', x, y, z), modify(e, 'accelerate', [x, y, z] )` + +Adds a vector to the motion vector. Most realistic way to apply a force to an entity. + +### `modify(e, 'custom_name')`, `modify(e, 'custom_name', name)`, `modify(e, 'custom_name', name, visible)` + +Sets the custom name of the entity. Without arguments - clears current custom name. Optional visible affects +if the custom name is always visible, even through blocks. + +### `modify(e, 'persistence', bool?)` + +Sets the entity persistence tag to `true` (default) or `false`. Only affects mobs. Persistent mobs +don't despawn and don't count towards the mobcap. + +### `modify(e, 'item', item_triple)` + +Sets the item for the item or item frame entity. (The item triple is a list of `[item_name, count, nbt]`, or just an item name.) + +### `modify(e, 'offering_flower', bool)` + +Sets if the iron golem has a red flower in hand. + +### `modify(e, 'blue_skull', bool)` + +Sets whether the wither skull entity is blue. + +### `modify(e, 'age', number)` + +Modifies entity's internal age counter. Fiddling with this will affect directly AI behaviours of complex +entities, so use it with caution. + +### `modify(e, 'pickup_delay', number)` + +Sets the pickup delay for the item entity. + +### `modify(e, 'breeding_age', number)` + +Sets the breeding age for the animal. + +### `modify(e, 'despawn_timer', number)` + +Sets a custom despawn timer value. + +### `modify(e, 'portal_cooldown', number)` + +Sets a custom number of ticks remaining until an entity can use a portal again. + +### `modify(e, 'portal_timer', number)` + +Sets a custom number of ticks an entity sits in a portal. + +### `modify(e, 'dismount')` + +Dismounts riding entity. + +### `modify(e, 'mount', other)` + +Mounts the entity to the `other`. + +### `modify(e, 'unmountable', boolean)` + +Denies or allows an entity to be mounted. + +### `modify(e, 'drop_passengers')` + +Shakes off all passengers. + +### `modify(e, 'mount_passengers', passenger, ? ...), modify(e, 'mount_passengers', [passengers] )` + +Mounts on all listed entities on `e`. + +### `modify(e, 'tag', tag, ? ...), modify(e, 'tag', [tags] )` + +Adds tag(s) to the entity. + +### `modify(e, 'clear_tag', tag, ? ...), modify(e, 'clear_tag', [tags] )` + +Removes tag(s) from the entity. + +### `modify(e, 'talk')` + +Make noises. + +### `modify(e, 'ai', boolean)` + +If called with `false` value, it will disable AI in the mob. `true` will enable it again. + +### `modify(e, 'no_clip', boolean)` + +Sets if the entity obeys any collisions, including collisions with the terrain and basic physics. Not affecting +players, since they are controlled client side. + +### `modify(e, 'effect', name?, duration?, amplifier?, show_particles?, show_icon?, ambient?)` + +Applies status effect to the living entity. Takes several optional parameters, which default to `0`, `true`, +`true` and `false`. If no duration is specified, or if it's null or 0, the effect is removed. If duration is less than 0, it will represent infinity. If name is not specified, +it clears all effects. + +### `modify(e, 'health', float)` + +Modifies the health of an entity. + +### `modify(e, 'may_fly', boolean)` + +Allows or denies the player the ability to fly. If the player is flying and the ability is removed, the player will stop flying. + +### `modify(e, 'flying', boolean)` + +Changes the flight status of the player (if it is flying or not). + +### `modify(e, 'may_build', boolean)` + +Allows or denies the player the ability to place blocks. + +### `modify(e, 'insta_build', boolean)` + +Allows or denies the player to place blocks without reducing the item count of the used stack and to shoot arrows without having them in the inventory. + +### `modify(e, 'fly_speed', float)` + +Modifies the value of the speed at which the player moves while flying. + +### `modify(e, 'walk_speed', float)` + +Modifies the value of the speed at which the player moves while walking. + +### `modify(e, 'selected_slot', int)` + +Changes player's selected slot. + +### `modify(e, 'home', null), modify(e, 'home', block, distance?), modify(e, 'home', x, y, z, distance?)` + +Sets AI to stay around the home position, within `distance` blocks from it. `distance` defaults to 16 blocks. +`null` removes it. _May_ not work fully with mobs that have this AI built in, like Villagers. + + +### `modify(e, 'spawn_point')`, `modify(e, 'spawn_point', null)`, `modify(e, 'spawn_point', pos, dimension?, angle?, forced?)` + +Changes player respawn position to given position, optional dimension (defaults to current player dimension), angle (defaults to +current player facing) and spawn forced/fixed (defaults to `false`). If `none` or nothing is passed, the respawn point +will be reset (as removed) instead. + +### `modify(e, 'gamemode', gamemode?), modify(e, 'gamemode', gamemode_id?)` + +Modifies gamemode of player to whatever string (case-insensitive) or number you put in. + +* 0: survival +* 1: creative +* 2: adventure +* 3: spectator + +### `modify(e, 'jumping', boolean)` + +Will make the entity constantly jump if set to true, and will stop the entity from jumping if set to false. +Note that jumping parameter can be fully controlled by the entity AI, so don't expect that this will have +a permanent effect. Use `'jump'` to make an entity jump once for sure. + +Requires a living entity as an argument. + +### `modify(e, 'jump')` + +Will make the entity jump once. + +### `modify(e, 'swing')` `modify(e, 'swing', 'offhand')` + +Makes the living entity swing their required limb. + +### `modify(e, 'silent', boolean)` + +Silences or unsilences the entity. + +### `modify(e, 'gravity', boolean)` + +Toggles gravity for the entity. + +### `modify(e, 'invulnerable', boolean)` + +Toggles invulnerability for the entity. + +### `modify(e, 'fire', ticks)` + +Will set entity on fire for `ticks` ticks. Set to 0 to extinguish. + +### `modify(e, 'frost', ticks)` + +Will give entity frost for `ticks` ticks. Set to 0 to unfreeze. + +### `modify(e, 'hunger', value)` +### `modify(e, 'saturation', value)` +### `modify(e, 'exhaustion', value)` + +Modifies directly player raw hunger components. Has no effect on non-players + +### `modify(e, 'absorption', value)` + +Sets the absorption value for the player. Each point is half a yellow heart. + +### `modify(e, 'add_xp', value)` +### `modify(e, 'xp_level', value)` +### `modify(e, 'xp_progress', value)` +### `modify(e, 'xp_score', value)` + +Manipulates player xp values - `'add_xp'` the method you probably want to use +to manipulate how much 'xp' an action should give. `'xp_score'` only affects the number you see when you die, and +`'xp_progress'` controls the xp progressbar above the hotbar, should take values from 0 to 1, but you can set it to any value, +maybe you will get a double, who knows. + +### `modify(e, 'air', ticks)` + +Modifies entity air. + +### `modify(e, 'add_exhaustion', value)` + +Adds exhaustion value to the current player exhaustion level - that's the method you probably want to use +to manipulate how much 'food' an action costs. + +### `modify(e, 'breaking_progress', value)` + +Modifies the breaking progress of a player currently mined block. Value of `null`, `-1` makes it reset. +Values `0` to `10` will show respective animation of a breaking block. Check `query(e, 'breaking_progress')` for +examples. + +### `modify(e, 'nbt_merge', partial_tag)` + +Merges a partial tag into the entity data and reloads the entity from its updated tag. Cannot be applied to players. + +### `modify(e, 'nbt', tag)` + +Reloads the entity from a supplied tag. Better use a valid entity tag, what can go wrong? Wonder what would happen if you +transplant rabbit's brain into a villager? Cannot be applied to players. + +## Entity Events + +There is a number of events that happen to entities that you can attach your own code to in the form of event handlers. +The event handler is any function that runs in your package that accepts certain expected parameters, which you can +expand with your own arguments. When it comes to the moment when the given command needs to be executed, it does so +providing that number of arguments it accepts is equal number of event arguments, and extra arguments passed when +defining the callback with `entity_event`. + +The following events can be handled by entities: + +* `'on_tick'`: executes every tick right before the entity is ticked in the game. Required arguments: `entity` +* `'on_move'`: executes every time an entity changes position, invoked just after it has been moved to the new position. Required arguments: `entity, velocity, pos1, pos2` +* `'on_death'`: executes once when a living entity dies. Required arguments: `entity, reason` +* `'on_removed'`: execute once when an entity is removed. Required arguments: `entity` +* `'on_damaged'`: executed every time a living entity is about to receive damage. +Required arguments: `entity, amount, source, attacking_entity` + +It doesn't mean that all entity types will have a chance to execute a given event, but entities will not error +when you attach an inapplicable event to it. + +In case you want to pass an event handler that is not defined in your module, please read the tips on + "Passing function references to other modules of your application" section in the `call(...)` section. + + +### `entity_load_handler(descriptor / descriptors, function)`, `entity_load_handler(descriptor / descriptors, call_name, ... args?)` + +Attaches a callback to trigger when any entity matching the following type / types is loaded in the game, allowing to grab a handle +to an entity right when it is loaded to the world without querying them every tick. Callback expects two parameters - the entity, +and a boolean value indicating if the entity was newly created(`true`) or just loaded from disk. Single argument functions accepting +only entities are allowed, but deprecated and will be removed at some point. + +If callback is `null`, then the current entity handler, if present, is removed. Consecutive calls to `entity_load_handler` will add / subtract +of the currently targeted entity types pool. + +Like other global events, calls to `entity_load_handler` should only be attached in apps with global scope. For player scope apps, +it will be called multiple times, once for each player. That's likely not what you want to do. + +``` +// veryfast method of getting rid of all the zombies. Callback is so early, its packets haven't reached yet the clients +// so to save on log errors, removal of mobs needs to be scheduled for later. +entity_load_handler('zombie', _(e, new) -> schedule(0, _(outer(e)) -> modify(e, 'remove'))) + +// another way to do it is to remove the entity when it starts ticking +entity_load_handler('zombie', _(e, new) -> entity_event(e, 'on_tick', _(e) -> modify(e, 'remove'))) + +// making all zombies immediately faster and less susceptible to friction of any sort +entity_load_handler('zombie', _(e, new) -> entity_event(e, 'on_tick', _(e) -> modify(e, 'motion', 1.2*e~'motion'))) +``` + +Word of caution: entities can be loaded with chunks in various states, for instance when a chunk is being generated, this means +that accessing world blocks would cause the game to freeze due to force generating that chunk while generating the chunk. Make +sure to never assume the chunk is ready and use `entity_load_handler` to schedule actions around the loaded entity, +or manipulate entity directly. + +Also, it is possible that mobs that spawn with world generation, while being 'added' have their metadata serialized and cached +internally (vanilla limitation), so some modifications to these entities may have no effect on them. This affects mobs created with +world generation. + +For instance the following handler is safe, as it only accesses the entity directly. It makes all spawned pigmen jump +``` +/script run entity_load_handler('zombified_piglin', _(e, new) -> if(new, modify(e, 'motion', 0, 1, 0)) ) +``` +But the following handler, attempting to despawn pigmen that spawn in portals, will cause the game to freeze due to cascading access to blocks that would cause neighbouring chunks +to force generate, causing also error messages for all pigmen caused by packets send after entity is removed by script. +``` +/script run entity_load_handler('zombified_piglin', _(e, new) -> if(new && block(pos(e))=='nether_portal', modify(e, 'remove') ) ) +``` +Easiest method to circumvent these issues is delay the check, which may or may not cause cascade load to happen, but +will definitely break the infinite chain. +``` +/script run entity_load_handler('zombified_piglin', _(e, new) -> if(new, schedule(0, _(outer(e)) -> if(block(pos(e))=='nether_portal', modify(e, 'remove') ) ) ) ) +``` +But the best is to perform the check first time the entity will be ticked - giving the game all the time to ensure chunk +is fully loaded and entity processing, removing the tick handler: +``` +/script run entity_load_handler('zombified_piglin', _(e, new) -> if(new, entity_event(e, 'on_tick', _(e) -> ( if(block(pos(e))=='nether_portal', modify(e, 'remove')); entity_event(e, 'on_tick', null) ) ) ) ) +``` +Looks little convoluted, but that's the safest method to ensure your app won't crash. + +### `entity_event(e, event, function)`, `entity_event(e, event, call_name, ... args?)` + +Attaches specific function from the current package to be called upon the `event`, with extra `args` carried to the +original required arguments for the event handler. + +
+protect_villager(entity, amount, source, source_entity, healing_player) -> +( + if(source_entity && source_entity~'type' != 'player', + modify(entity, 'health', amount + entity~'health' ); + particle('end_rod', pos(entity)+[0,3,0]); + print(str('%s healed thanks to %s', entity, healing_player)) + ) +); +__on_player_interacts_with_entity(player, entity, hand) -> +( + if (entity~'type' == 'villager', + entity_event(entity, 'on_damage', 'protect_villager', player~'name') + ) +) ++ +In this case this will protect a villager from entity damage (zombies, etc.) except from players by granting all the +health back to the villager after being harmed. +# Inventory and Items API + +## Manipulating inventories of blocks and entities + +Most functions in this category require inventory as the first argument. Inventory could be specified by an entity, +or a block, or position (three coordinates) of a potential block with inventory, or can be preceded with inventory +type. +Inventory type can be `null` (default), `'enderchest'` denoting player enderchest storage, or `'equipment'` applying to +entities hand and armour pieces. Then the type can be followed by entity, block or position coordinates. +For instance, player enderchest inventory requires +two arguments, keyword `'enderchest'`, followed by the player entity argument, (or a single argument as a string of a +form: `'enderchest_steve'` for legacy support). If your player name starts with `'enderchest_'`, first of all, tough luck, +but then it can be always accessed by passing a player +entity value. If all else fails, it will try to identify first three arguments as coordinates of a block position of +a block inventory. Player inventories can also be called by their name. + +A few living entities can have both: their regular inventory, and their equipment inventory. +Player's regular inventory already contains the equipment, but you can access the equipment part as well, as well as +their enderchest separately. For entity types that only have +their equipment inventory, the equipment is returned by default (`null` type). + +If that's confusing see examples under `inventory_size` on how to access inventories. All other `inventory_...()` functions +use the same scheme. + + + If the entity or a block doesn't have +an inventory, all API functions typically do nothing and return null. + +Most items returned are in the form of a triple of item name, count, and the full nbt of an item. When saving an item, if the +nbt is provided, it overrides the item type provided in the name. + +### `item_list(tag?)` + +With no arguments, returns a list of all items in the game. With an item tag provided, list items matching the tag, or `null` if tag is not valid. + +### `item_tags(item, tag?)` + +Returns list of tags the item belongs to, or, if tag is provided, `true` if an item matches the tag, `false` if it doesn't and `null` if that's not a valid tag + +Throws `unknown_item` if item doesn't exist. + +### `stack_limit(item)` + +Returns number indicating what is the stack limit for the item. Its typically 1 (non-stackable), 16 (like buckets), +or 64 - rest. It is recommended to consult this, as other inventory API functions ignore normal stack limits, and +it is up to the programmer to keep it at bay. As of 1.13, game checks for negative numbers and setting an item to +negative is the same as empty. + +Throws `unknown_item` if item doesn't exist. + +
+stack_limit('wooden_axe') => 1 +stack_limit('ender_pearl') => 16 +stack_limit('stone') => 64 ++ +### `recipe_data(item, type?)`, `recipe_data(recipe, type?)` + +returns all recipes matching either an `item`, or represent actual `recipe` name. In vanilla datapack, for all items +that have one recipe available, the recipe name is the same as the item name but if an item has multiple recipes, its +direct name can be different. + +Recipe type can take one of the following options: + * `'crafting'` - default, crafting table recipe + * `'smelting'` - furnace recipe + * `'blasting'` - blast furnace recipe + * `'smoking'` - smoker recipe + * `'campfire_cooking'` - campfire recipe + * `'stonecutting'` - stonecutter recipe + * `'smithing'` - smithing table (1.16+) + + The return value is a list of available recipes (even if there is only one recipe available). Each recipe contains of + an item triple of the crafting result, list of ingredients, each containing a list of possible variants of the + ingredients in this slot, as item triples, or `null` if its a shaped recipe and a given slot in the patterns is left + empty, and recipe specification as another list. Possible recipe specs is: + * `['shaped', width, height]` - shaped crafting. `width` and `height` can be 1, 2 or 3. + * `['shapeless']` - shapeless crafting + * `['smelting', duration, xp]` - smelting/cooking recipes + * `['cutting']` - stonecutter recipe + * `['special']` - special crafting recipe, typically not present in the crafting menu + * `['custom']` - other recipe types + +Note that ingredients are specified as tripes, with count and nbt information. Currently all recipes require always one +of the ingredients, and for some recipes, even if the nbt data for the ingredient is specified (e.g. `dispenser`), it +can accept items of any tags. + +Also note that some recipes leave some products in the crafting window, and these can be determined using + `crafting_remaining_item()` function + + Examples: +
+ recipe_data('iron_ingot_from_nuggets') + recipe_data('iron_ingot') + recipe_data('glass', 'smelting') ++ +### `crafting_remaining_item(item)` + +returns `null` if the item has no remaining item in the crafting window when used as a crafting ingredient, or an +item name that serves as a replacement after crafting is done. Currently it can only be buckets and glass bottles. + +### `inventory_size(inventory)` + +Returns the size of the inventory for the entity or block in question. Returns null if the block or entity don't +have an inventory. + +
+inventory_size(player()) => 41 +inventory_size('enderchest', player()) => 27 // enderchest +inventory_size('equipment', player()) => 6 // equipment +inventory_size(null, player()) => 41 // default inventory for players + +inventory_size(x,y,z) => 27 // chest +inventory_size(block(pos)) => 5 // hopper + +horse = spawn('horse', x, y, z); +inventory_size(horse); => 2 // default horse inventory +inventory_size('equipment', horse); => 6 // unused horse equipment inventory +inventory_size(null, horse); => 2 // default horse + +creeper = spawn('creeper', x, y, z); +inventory_size(creeper); => 6 // default creeper inventory is equipment since it has no other +inventory_size('equipment', creeper); => 6 // unused horse equipment inventory +inventory_size(null, creeper); => 6 // creeper default is its equipment ++ +### `inventory_has_items(inventory)` + +Returns true, if the inventory is not empty, false if it is empty, and null, if its not an inventory. + +
inventory_has_items(player()) => true + inventory_has_items(x,y,z) => false // empty chest + inventory_has_items(block(pos)) => null // stone ++ +### `inventory_get(inventory, slot)` + +Returns the item in the corresponding inventory slot, or null if slot empty or inventory is invalid. You can use +negative numbers to indicate slots counted from 'the back'. + +
+inventory_get(player(), 0) => null // nothing in first hotbar slot +inventory_get(x,y,z, 5) => ['stone', 1, {id:"minecraft:stone"}] +inventory_get(player(), -1) => ['diamond_pickaxe', 1, {components:{"minecraft:damage":4},id:"minecraft:diamond_pickaxe"}] // slightly damaged diamond pick in the offhand ++ +### `inventory_set(inventory, slot, count, item?, nbt?)` + +Modifies or sets a stack in inventory. specify count 0 to empty the slot. If item is not specified, keeps existing +item, just modifies the count. If item is provided - replaces current item. If nbt is provided - uses the tag to create the item fully +ignoring the item name. If nbt is provided and count is not null, the sets the custom count on the tag from the count parameter. +If count is `null` and item is `null`, an item is entirely defined by the `nbt` parameter. Returns previous stack in that slot. + +
+inventory_set(player(), 0, 0) => ['stone', 64, {id:"minecraft:stone"}] // player had a stack of stone in first hotbar slot +inventory_set(player(), 0, 6) => ['diamond', 64, {id:"minecraft:diamond"}] // changed stack of diamonds in player slot to 6 +inventory_set(player(), 0, 1, 'diamond_axe','{components:{"minecraft:damage":5},id:"minecraft:diamond_axe"}') => null //added slightly damaged diamond axe to first player slot +inventory_set(player(), 0, null, null, '{components:{"minecraft:damage":5},id:"minecraft:diamond_axe"}') => null // same effect as above ++ +### `inventory_find(inventory, item, start_slot?, ), inventory_find(inventory, null, start_slot?)` + +Finds the first slot with a corresponding item in the inventory, or if queried with null: the first empty slot. +Returns slot number if found, or null otherwise. Optional start_slot argument allows to skip all preceeding slots +allowing for efficient (so not slot-by-slot) inventory search for items. + +
+inventory_find(player(), 'stone') => 0 // player has stone in first hotbar slot +inventory_find(player(), null) => null // player's inventory has no empty spot +while( (slot = inventory_find(p, 'diamond', slot)) != null, 41, drop_item(p, slot) ) + // spits all diamonds from player inventory wherever they are +inventory_drop(x,y,z, 0) => 64 // removed and spawned in the world a full stack of items ++ +Throws `unknown_item` if item doesn't exist. + +### `inventory_remove(inventory, item, amount?)` + +Removes amount (defaults to 1) of item from inventory. If the inventory doesn't have the defined amount, nothing +happens, otherwise the given amount of items is removed wherever they are in the inventory. Returns boolean +whether the removal operation was successful. Easiest way to remove a specific item from player inventory +without specifying the slot. + +
+inventory_remove(player(), 'diamond') => 1 // removed diamond from player inventory +inventory_remove(player(), 'diamond', 100) => 0 // player doesn't have 100 diamonds, nothing happened ++ +### `drop_item(inventory, slot, amount?, )` + +Drops the items from indicated inventory slot, like player that Q's an item or villager, that exchanges food. +You can Q items from block inventories as well. default amount is 0 - which is all from the slot. +NOTE: hoppers are quick enough to pick all the queued items from their inventory anyways. +Returns size of the actual dropped items. + +
+inventory_drop(player(), 0, 1) => 1 // Q's one item on the ground +inventory_drop(x,y,z, 0) => 64 // removed and spawned in the world a full stack of items ++ +## Screens + +A screen is a value type used to open screens for a player and interact with them. +For example, this includes the chest inventory gui, the crafting table gui and many more. + +### `create_screen(player, type, name, callback?)` + +Creates and opens a screen for a `player`. + +Available `type`s: + +* `anvil` +* `beacon` +* `blast_furnace` +* `brewing_stand` +* `cartography_table` +* `crafting` +* `enchantment` +* `furnace` +* `generic_3x3` +* `generic_9x1` +* `generic_9x2` +* `generic_9x3` +* `generic_9x4` +* `generic_9x5` +* `generic_9x6` +* `grindstone` +* `hopper` +* `lectern` +* `loom` +* `merchant` +* `shulker_box` +* `smithing` +* `smoker` +* `stonecutter` + +The `name` parameter can be a formatted text and will be displayed at the top of the screen. +Some screens like the lectern or beacon screen don't show it. + +Optionally, a `callback` function can be passed as the fourth argument. +This functions needs to have four parameters: +`_(screen, player, action, data) -> ...` + +The `screen` parameter is the screen value of the screen itself. +`player` is the player who interacted with the screen. +`action` is a string corresponding to the interaction type. +Can be any of the following: + +Slot interactions: + +* `pickup` +* `quick_move` +* `swap` +* `clone` +* `throw` +* `quick_craft` +* `pickup_all` + +The `data` for this interaction is a map, with a `slot` and `button` value. +`slot` is the slot index of the slot that was clicked. +When holding an item in the cursor stack and clicking inside the screen, +but not in a slot, this is -1. +If clicked outside the screen (where it would drop held items), this value is null. +The `button` is the mouse button used to click the slot. + +For the `swap` action, the `button` is the number key 0-8 for a certain hotbar slot. + +For the `quick_craft` action, the `data` also contains the `quick_craft_stage`, +which is either 0 (beginning of quick crafting), 1 (adding item to slot) or 2 (end of quick crafting). + +Other interactions: + +* `button` Pressing a button in certain screens that have button elements (enchantment table, lectern, loom and stonecutter) +The `data` provides a `button`, which is the index of the button that was pressed. +Note that for lecterns, this index can be certain a value above 100, for jumping to a certain page. +This can come from formatted text inside the book, with a `change_page` click event action. + +* `close` Triggers when the screen gets closed. No `data` provided. + +* `select_recipe` When clicking on a recipe in the recipe book. +`data` contains a `recipe`, which is the identifier of the clicked recipe, +as well as `craft_all`, which is a boolean specifying whether +shift was pressed when selecting the recipe. + +* `slot_update` Gets called **after** a slot has changed contents. `data` provides a `slot` and `stack`. + +By returning a string `'cancel'` in the callback function, +the screen interaction can be cancelled. +This doesn't work for the `close` action. + +The `create_screen` function returns a `screen` value, +which can be used in all inventory related functions to access the screens' slots. +The screen inventory covers all slots in the screen and the player inventory. +The last slot is the cursor stack of the screen, +meaning that using `-1` can be used to modify the stack the players' cursor is holding. + +### `close_screen(screen)` + +Closes the screen of the given screen value. +Returns `true` if the screen was closed. +If the screen is already closed, returns `false`. + +### `screen_property(screen, property)` + +### `screen_property(screen, property, value)` + +Queries or modifies a certain `property` of a `screen`. +The `property` is a string with the name of the property. +When called with `screen` and `property` parameter, returns the current value of the property. +When specifying a `value`, +the property will be assigned the new `value` and synced with the client. + +**Options for `property` string:** + +| `property` | Required screen type | Type | Description | +|---|---|---|---| +| `name` | **All** | text | The name of the screen, as specified in the `create_screen()` function. Can only be queried. | +| `open` | **All** | boolean | Returns `true` if the screen is open, `false` otherwise. Can only be queried. | +| `fuel_progress` | furnace/smoker/blast_furnace | number | Current value of the fuel indicator. | +| `max_fuel_progress` | furnace/smoker/blast_furnace | number | Maximum value for the full fuel indicator. | +| `cook_progress` | furnace/smoker/blast_furnace | number | Cooking progress indicator value. | +| `max_cook_progress` | furnace/smoker/blast_furnace | number | Maximum value for the cooking progress indicator. | +| `level_cost` | anvil | number | Displayed level cost for the anvil. | +| `page` | lectern | number | Opened page in the lectern screen. | +| `beacon_level` | beacon | number | The power level of the beacon screen. This affects how many effects under primary power are grayed out. Should be a value between 0-5. | +| `primary_effect` | beacon | number | The effect id of the primary effect. This changes the effect icon on the button on the secondary power side next to the regeneration effect. | +| `secondary_effect` | beacon | number | The effect id of the secondary effect. This seems to change nothing, but it exists. | +| `brew_time` | brewing_stand | number | The brewing time indicator value. This goes from 0 to 400. | +| `brewing_fuel` | brewing_stand | number | The fuel indicator progress. Values range between 0 to 20. | +| `enchantment_power_x` | enchantment | number | The level cost of the shown enchantment. Replace `x` with 1, 2 or 3 (e.g. `enchantment_power_2`) to target the first, second or third enchantment. | +| `enchantment_id_x` | enchantment | number | The id of the enchantment shown (replace `x` with the enchantment slot 1/2/3). | +| `enchantment_level_x` | enchantment | number | The enchantment level of the enchantment. | +| `enchantment_seed` | enchantment | number | The seed of the enchanting screen. This affects the text shown in the standard Galactic alphabet. | +| `banner_pattern` | loom | number | The selected banner pattern inside the loom. | +| `stonecutter_recipe` | stonecutter | number | The selected recipe in the stonecutter. | + +### Screen example scripts + +
+foo(a) -> print(a); +handle_event('boohoo', 'foo'); + +bar(a, b, c) -> print([a, b, c]); +handle_event('boohoo', 'bar', 2, 3) // using b = 2, c = 3, a - passed by the caller + +handle_event('tick', _() -> foo('tick happened')); // built-in event + +handle_event('tick', null) // nah, ima good, kthxbai ++ +In case you want to pass an event handler that is not defined in your module, please read the tips on + "Passing function references to other modules of your application" section in the `call(...)` section. + + +### `signal_event(event, target_player?, ... args?)` + +Fires a specific event. If the event does not exist (only `handle_event` creates missing new events), or provided argument list +was not matching the callee expected arguments, returns `null`, +otherwise returns number of apps notified. If `target_player` is specified and not `null` triggers a player specific event, targeting +only `player` scoped apps for that player. Apps with globals scope will not be notified even if they handle this event. +If the `target_player` is omitted or `null`, it will target `global` scoped apps and all instances of `player` scoped apps. +Note that all built-in player events have a player as a first argument, so to trigger these events, you need to +provide them twice - once to specify the target player scope and second - to provide as an argument to the handler function. + +
+signal_event('player_breaks_block', player, player, block); // to target all player scoped apps +signal_event('player_breaks_block', null , player, block); // to target all global scoped apps and all player instances +signal_event('tick') // trigger all apps with a tick event ++ +## Custom events example + +The following example shows how you can communicate between different instances of the same player scoped app. It important to note +that signals can trigger other apps as well, assuming the name of the event matches. In this case the request name is called +`tp_request` and is triggered with a command. + + +``` +// tpa.sc +global_requester = null; +__config() -> { + 'commands' -> { + '
+scoreboard_add('counter') +scoreboard_add('lvl','level') ++ +### `scoreboard_remove(objective)` `scoreboard_remove(objective, key)` + +Removes an entire objective, or an entry in the scoreboard associated with the key. +Returns `true` if objective has existed and has been removed, or previous +value of the scoreboard if players score is removed. Returns `null` if objective didn't exist, or a key was missing +for the objective. + +### `scoreboard_display(place, objective)` + +Sets display location for a specified `objective`. If `objective` is `null`, then display is cleared. If objective is invalid, +returns `null`. + +### `scoreboard_property(objective, property)` `scoreboard_property(objective, property, value)` + +Reads a property of an `objective` or sets it to a `value` if specified. Available properties are: + +* `criterion` +* `display_name` (Formatted text supported) +* `display_slot`: When reading, returns a list of slots this objective is displayed in, when modifying, displays the objective in the specified slot +* `render_type`: Either `'integer'` or `'hearts'`, defaults to `'integer'` if invalid value specified + +# Team + +### `team_list()`, `team_list(team)` + +Returns all available teams as a list with no arguments. + +When a `team` is specified, it returns all the players inside that team. If the `team` is invalid, returns `null`. + +### `team_add(team)`, `team_add(team,player)` + +With one argument, creates a new `team` and returns its name if successful, or `null` if team already exists. + + +`team_add('admin')` -> Create a team with the name 'admin' +`team_add('admin','Steve')` -> Joing the player 'Steve' into the team 'admin' + +If a `player` is specified, the player will join the given `team`. Returns `true` if player joined the team, or `false` if nothing changed since the player was already in this team. If the team is invalid, returns `null` + +### `team_remove(team)` + +Removes a `team`. Returns `true` if the team was deleted, or `null` if the team is invalid. + +### `team_leave(player)` + +Removes the `player` from the team he is in. Returns `true` if the player left a team, otherwise `false`. + +`team_leave('Steve')` -> Removes Steve from the team he is currently in +`for(team_list('admin'), team_leave('admin', _))` -> Remove all players from team 'admin' + +### `team_property(team,property,value?)` + +Reads the `property` of the `team` if no `value` is specified. If a `value` is added as a third argument, it sets the `property` to that `value`. + +* `collisionRule` + * Type: String + * Options: always, never, pushOtherTeams, pushOwnTeam + +* `color` + * Type: String + * Options: See [team command](https://minecraft.wiki/w/Commands/team#Arguments) (same strings as `'teamcolor'` [command argument](https://github.com/gnembon/fabric-carpet/blob/master/docs/scarpet/Full.md#command-argument-types) options) + +* `displayName` + * Type: String or FormattedText, when querying returns FormattedText + +* `prefix` + * Type: String or FormattedText, when querying returns FormattedText + +* `suffix` + * Type: String or FormattedText, when querying returns FormattedText + +* `friendlyFire` + * Type: boolean + +* `seeFriendlyInvisibles` + * Type: boolean + +* `nametagVisibility` + * Type: String + * Options: always, never, hideForOtherTeams, hideForOwnTeam + +* `deathMessageVisibility` + * Type: String + * Options: always, never, hideForOtherTeams, hideForOwnTeam + +Examples: + +``` +team_property('admin','color','dark_red') Make the team color for team 'admin' dark red +team_property('admin','prefix',format('r Admin | ')) Set prefix of all players in 'admin' +team_property('admin','display_name','Administrators') Set display name for team 'admin' +team_property('admin','seeFriendlyInvisibles',true) Make all players in 'admin' see other admins even when invisible +team_property('admin','deathMessageVisibility','hideForOtherTeams') Make all players in 'admin' see other admins even when invisible +``` + +## `bossbar()`, `bossbar(id)`, `bossbar(id,property,value?)` + +Manage bossbars just like with the `/bossbar` command. + +Without any arguments, returns a list of all bossbars. + +When an id is specified, creates a bossbar with that `id` and returns the id of the created bossbar. +Bossbar ids need a namespace and a name. If no namespace is specified, it will automatically use `minecraft:`. +In that case you should keep track of the bossbar with the id that `bossbar(id)` returns, because a namespace may be added automatically. +If the id was invalid (for example by having more than one colon), returns `null`. +If the bossbar already exists, returns `false`. + +`bossbar('timer') => 'minecraft:timer'` (Adds the namespace `minecraft:` because none is specified) + +`bossbar('scarpet:test') => 'scarpet:test'` In this case there is already a namespace specified + +`bossbar('foo:bar:baz') => null` Invalid identifier + +`bossbar(id,property)` is used to query the `property` of a bossbar. + +`bossbar(id,property,value)` can modify the `property` of the bossbar to a specified `value`. + +Available properties are: + +* color: can be `'pink'`, `'blue'`, `'red'`, `'green'`, `'yellow'`, `'purple'` or `'white'` + +* style: can be `'progress'`, `'notched_6'`, `'notched_10'`, `'notched_12'` or `'notched_20'` + +* value: value of the bossbar progress + +* max: maximum value of the bossbar progress, by default 100 + +* name: Text to display above the bossbar, supports formatted text + +* visible: whether the bossbar is visible or not + +* players: List of players that can see the bossbar + +* add_player: add a player to the players that can see this bossbar, this can only be used for modifying (`value` must be present) + +* remove: remove this bossbar, no `value` required + +``` +bossbar('script:test','style','notched_12') +bossbar('script:test','value',74) +bossbar('script:test','name',format('rb Test')) -> Change text +bossbar('script:test','visible',false) -> removes visibility, but keeps players +bossbar('script:test','players',player('all')) -> Visible for all players +bossbar('script:test','players',player('Steve')) -> Visible for Steve only +bossbar('script:test','players',null) -> Invalid player, removing all players +bossbar('script:test','add_player',player('Alex')) -> Add Alex to the list of players that can see the bossbar +bossbar('script:test','remove') -> remove bossbar 'script:test' +for(bossbar(),bossbar(_,'remove')) -> remove all bossbars +``` + + + + +# Auxiliary aspects + +Collection of other methods that control smaller, yet still important aspects of the game + +## Sounds + +### `sound()`, `sound(name, pos, volume?, pitch?, mixer?)` + +Plays a specific sound `name`, at block or position `pos`, with optional `volume` and modified `pitch`, and under +optional `mixer`. Default values for `volume`, `pitch` and `mixer` are `1.0`, `1.0`, and `master`. +Valid mixer options are `master`, `music`, `record`, `weather`, `block`, `hostile`,`neutral`, `player`, `ambient` +and `voice`. `pos` can be either a block, triple of coords, or a list of three numbers. Uses the same options as a + corresponding `playsound` command. + +Used with no arguments, returns a list of available sound names. Note that this list may not include all sounds that +clients will actually be able to receive (they may have more available via resourcepacks for example). + +## Particles + +### `particle()`, `particle(name, pos, count?. spread?, speed?, player?)` + +Renders a cloud of particles `name` centered around `pos` position, by default `count` 10 of them, default `speed` +of 0, and to all players nearby, but these options can be changed via optional arguments. Follow vanilla `/particle` +command on details on those options. Valid particle names are +for example `'angry_villager', 'item diamond', 'block stone', 'dust 0.8 0.1 0.1 4'`. + +Used with no arguments, return the list of available particle names. Note that some of the names do not correspond to a valid +particle that can be fed to `particle(...)` function due to a fact that some particles need more configuration +to be valid, like `dust`, `block` etc. Should be used as a reference only. + +Throws `unknown_particle` if particle doesn't exist. + +### `particle_line(name, pos, pos2, density?, player?)` + +Renders a line of particles from point `pos` to `pos2` with supplied density (defaults to 1), which indicates how far +apart you would want particles to appear, so `0.1` means one every 10cm. If a player (or player name) is supplied, only +that player will receive particles. + +Throws `unknown_particle` if particle doesn't exist. + +### `particle_box(name, pos, pos2, density?, player?)` +### `particle_rect` (deprecated) + +Renders a cuboid of particles between points `pos` and `pos2` with supplied density. If a player (or player name) is +supplied, only that player will receive particles. + +Throws `unknown_particle` if particle doesn't exist. + +## Markers + +### `draw_shape(shape, duration, key?, value?, ... )`, +### `draw_shape(shape, duration, [key?, value?, ... ])`, +### `draw_shape(shape, duration, attribute_map)` +### `draw_shape(shape_list)` + +Draws a shape in the world that will expire in `duration` ticks. Other attributes of the shape should be provided as +consecutive key - value argument pairs, either as next arguments, or packed in a list, or supplied as a proper key-value +`map`. Arguments may include shared shape attributes, which are all optional, as well as shape-specific attributes, that +could be either optional or required. Shapes will draw properly on all carpet clients. Other connected players that don't +have carpet installed will still be able to see the required shapes in the form of dust particles. Replacement shapes +are not required to follow all attributes precisely, but will allow vanilla clients to receive some experience of your +apps. One of the attributes that will definitely not be honored is the duration - particles will be send once +per shape and last whatever they typically last in the game. + +Shapes can be send one by one, using either of the first three invocations, or batched as a list of shape descriptors. +Batching has this benefit that they will be send possibly as one packet, limiting network overhead of +sending many small packets to draw several shapes at once. The drawback of sending shapes is batches is that they need to address +the same list of players, i.e. if multiple players from the list target different players, all shapes will be sent to all of them. + +Shapes will fail to draw and raise a runtime error if not all its required parameters +are specified and all available shapes have some parameters that are required, so make sure to have them in place: + +On the client, shapes can recognize that they are being redrawn again with the same parameters, disregarding the +duration parameter. This updates the expiry on the drawn shape to the new value, instead of adding new shape in its +place. This can be used for toggling the shapes on and off that has been send previously with very large durations, +or simply refresh the shapes periodically in more dynamic applications. + +Optional shared shape attributes: + * `color` - integer value indicating the main color of the shape in the form of red, green, blue and alpha components + in the form of `0xRRGGBBAA`, with the default of `-1`, so white opaque, or `0xFFFFFFFF`. + * `player` - name or player entity to send the shape to, or a list of players. If specified, the shapes will appear only for the specified + players (regardless where they are), otherwise it will be send to all players in the current dimension. + * `line` - (Deprecated) line thickness, defaults to 2.0pt. Not supported in 1.17's 3.2 core GL renderer. + * `fill` - color for the faces, defaults to no fill. Use `color` attribute format + * `follow` - entity, or player name. Shape will follow an entity instead of being static. + Follow attribute requires all positional arguments to be relative to the entity and disallow + of using entity or block as position markers. You must specify positions as a triple. + * `snap` - if `follow` is present, indicated on which axis the snapping to entity coordinates occurs, and which axis + will be treated statically, i.e. the coordinate passed in a coord triple is the actual value in the world. Default + value is `'xyz'`, meaning the shape will be drawn relatively to the entity in all three directions. Using `xz` for + instance makes so that the shape follows the entity, but stays at the same, absolute Y coordinate. Preceeding an axis + with `d`, like `dxdydz` would make so that entity position is treated discretely (rounded down). + * `debug` - if True, it will only be visible when F3+B entity bounding boxes is enabled. + * `facing` - applicable only to `'text'`, `'block'` or '`item'` shapes, where its facing. Possible options are: + * `player`: Default. Element always rotates to face the player eye position, + * `camera`: Element is placed on the plane orthogonal to player look vector, + * `north`, `south`, `east`, `west`, `up`, `down`: obvious + +Available shapes: + * `'line'` - draws a straight line between two points. + * Required attributes: + * `from` - triple coordinates, entity, or block value indicating one end of the line + * `to` - other end of the line, same format as `from` + + * `'box'` - draws a box with corners in specified points + * Required attributes: + * `from` - triple coordinates, entity, or block value indicating one corner of the box + * `to` - other corner, same format as `from` + + * `'sphere'`: + * Required attributes: + * `center` - center of the sphere + * `radius` - radius of the sphere + * Optional attributes: + * `level` - level of details, or grid size. The more the denser your sphere. Default level of 0, means that the + level of detail will be selected automatically based on radius. + + * `'cylinder'`: + * Required attributes: + * `center` - center of the base + * `radius` - radius of the base circle + * Optional attributes: + * `axis` - cylinder direction, one of `'x'`, `'y'`, `'z'` defaults to `'y'` + * `height` - height of the cyllinder, defaults to `0`, so flat disk. Can be negative. + * `level` - level of details, see `'sphere'`. + + * `'polygon'`: + * Required attributes: + * `points` - list of points defining vertices of the polygon + * Optional attributes: + * `relative` - list of bools. vertices of the polygon that affected by 'follow'. Could be a single bools to affact allpoints too. Default means that every point is affacted. + * `mode` - how those points are connected. may be "polygon"(default),"strip" or "triangles". "polygon" means that it will be viewed as vertices of a polygon center on the first one. "strip" means that it will be viewed as a triangles strip. "triangles" means that it will be viewed as some triangles that are not related to each other (therefor length of `points` in this mode have to be a multiple of 3). + * `inner` - if `true` it will make the inner edges be drawn as well. + * `doublesided` - if `true` it will make the shapes visible from the back as well. Default is `true`. + + * `'label'` - draws a text in the world. Default `line` attribute controls main font color. + `fill` controls the color of the background. + * Required attributes: + * `pos` - position + * `text` - string or formatted text to display + * Optional attributes + * `value` - string or formatted text to display instead of the main `text`. `value` unlike `text` + is not used to determine uniqueness of the drawn text so can be used to + display smoothly dynamic elements where value of an element is constantly + changing and updates to it are being sent from the server. + * `size` - float. Default font size is 10. + * `doublesided` - if `true` it will make the text visible from the back as well. Default is `false` (1.16+) + * `align` - text alignment with regards to `pos`. Default is `center` (displayed text is + centered with respect to `pos`), `left` (`pos` indicates beginning of text), and `right` (`pos` + indicates the end of text). + * `tilt`, `lean`, `turn` - additional rotations of the text on the canvas along all three axis + * `indent`, `height`, `raise` - offsets for text rendering on X (`indent`), Y (`height`), and Z axis (`raise`) + with regards to the plane of the text. One unit of these corresponds to 1 line spacing, which + can be used to display multiple lines of text bound to the same `pos` + + * `'block'`: draws a block at the specified position: + * Required attributes: + * `pos` - position of the object. + * `block` - the object to show. It is a block value or a name of a block with optional NBT data. + * Optional attributes: + * `tilt`, `lean`, `turn` - additional rotations along all three axis. It uses the block center as the origin. + * `scale` - scale of it in 3 axis-direction. should be a number or a list of 3 numbers (x,y,z). + * `skylight`, `blocklight` - light level. omit it to use local light level. should between 0~15. + + * `'item'`: draws an item at the specified position: + * Required attributes: + * `pos` - position of the object. + * `item` - the object to show. It is an item tuple or a string identified item that may have NBT data. + * Optional attributes: + * `tilt`, `lean`, `turn` - additional rotations along all three axis. for `block`, it use its block center as the origin. + * `scale` - scale of it in 3 axis-direction. should be a number or a list of 3 numbers (x,y,z). + * `skylight`, `blocklight` - light level. omit it to use local light level. should between 0~15. + * `variant` - one of `'none'`, `'thirdperson_lefthand'`, `'thirdperson_righthand'`, `'firstperson_lefthand'`, + `'firstperson_righthand'`, `'head'`, `'gui'`, `'ground'`, `'fixed'`. In addition to the literal meaning, + it can also be used to use special models of tridents and telescopes. + This attribute is experimental and use of it will change in the future. + + +### `create_marker(text, pos, rotation?, block?, interactive?)` + +Spawns a (permanent) marker entity with text or block at position. Returns that entity for further manipulations. +Unloading the app that spawned them will cause all the markers from the loaded portion of the world to be removed. +Also, if the game loads that marker in the future and the app is not loaded, it will be removed as well. + +If `interactive` (`true` by default) is `false`, the armorstand will be a marker and would not be interactive in any +gamemode. But blocks can be placed inside markers and will not catch any interaction events. + +Y Position of a marker text or block will be adjusted to make blocks or text appear at the specified position. +This makes so that actual armorstand position may be offset on Y axis. You would need to adjust your entity +locations if you plan to move the armorstand around after the fact. If both text and block are specified - one of them +will be aligned (armorstand type markers text shows up at their feet, while for regular armorstands - above the head, +while block on the head always render in the same position regardless if its a marker or not). + + +### `remove_all_markers()` + +Removes all scarpet markers from the loaded portion of the world created by this app, in case you didn't want to do + the proper cleanup. + +## System function + +### `nbt(expr)` + +Treats the argument as a nbt serializable string and returns its nbt value. In case nbt is not in a correct nbt +compound tag format, it will return `null` value. + +Consult section about container operations in `Expression` to learn about possible operations on nbt values. + +### `escape_nbt(expr)` + +Excapes all the special characters in the string or nbt tag and returns a string that can be stored in nbt directly +as a string value. + +### `tag_matches(daddy_tag, baby_tag, match_lists?)` + +Utility returning `true` if `baby_tag` is fully contained in `daddy_tag`. Anything matches `null` baby tag, and +Nothing is contained in a `null` daddy tag. If `match_lists` is specified and `false`, content of nested lists is ignored. +Default behaviour is to match them. + +### `parse_nbt(tag)` + +Converts NBT tag to a scarpet value, which you can navigate through much better. + +Converts: + - Compound tags into maps with string keys + - List tags into list values + - Numbers (Ints, Floats, Doubles, Longs) into a number + - Rest is converted to strings. + +### `encode_nbt(expr, force?)` + +Encodes value of the expression as an NBT tag. By default (or when `force` is false), it will only allow +to encode values that are guaranteed to return the same value when applied the resulting tag to `parse_nbt()`. +Supported types that can reliably convert back and forth to and from NBT values are: + - Maps with string keywords + - Lists of items of the same type (scarpet will take care of unifying value types if possible) + - Numbers (encoded as Ints -> Longs -> Doubles, as needed) + - Strings + +Other value types will only be converted to tags (including NBT tags) if `force` is true. They would require +extra treatment when loading them back from NBT, but using `force` true will always produce output / never +produce an exception. + +### `print(expr)`, `print(player/player_list, expr)` + +Displays the result of the expression to the chat. Overrides default `scarpet` behaviour of sending everything to stderr. +For player scoped apps it always by default targets the player for whom the app runs on behalf. +Can optionally define player or list of players to send the message to. + +### `format(components, ...)`, `format([components, ...])` + +Creates a line of formatted text. Each component is either a string indicating formatting and text it corresponds to +or a decorator affecting the component preceding it. + +Regular formatting components is a string that have the structure of: +`'
+ print(format('rbu Error: ', 'r Stuff happened!')) + print(format('w Click ','tb [HERE]', '^di Awesome!', '!/kill', 'w \ button to win $1000')) + // the reason why I backslash the second space is that otherwise command parser may contract consecutive spaces + // not a problem in apps ++ +### `item_display_name(item)` + Returns the name of the item as a Text Value. `item` should be a list of `[item_name, count, nbt]`, or just an item name. + + Please note that it is a translated value. treating it like a string (eg.slicing, breaking, changing its case) will turn it back into a normal string without translatable properties. just like a colorful formatted text loose its color. And the result of it converting to a string will use en-us (in a server) or your single player's language, but when you use print() or others functions that accept a text value to broadcast it to players, it will use each player's own language. + + If the item is renamed, it will also be reflected in the results. + + +### `display_title(players, type, text?, fadeInTicks?, stayTicks?, fadeOutTicks),` + +Sends the player (or players if `players` is a list) a title of a specific type, with optionally some times. + * `players` is either an online player or a list of players. When sending a single player, it will throw if the player is invalid or offline. + * `type` is either `'title'`, `'subtitle'`, `actionbar` or `clear`. + Note: `subtitle` will only be displayed if there is a title being displayed (can be an empty one) + * `title` is what title to send to the player. It is required except for `clear` type. Can be a text formatted using `format()` + * `...Ticks` are the number of ticks the title will stay in that state. + If not specified, it will use current defaults (those defaults may have changed from a previous `/title times` execution). + Executing with those will set the times to the specified ones. + Note that `actionbar` type doesn't support changing times (vanilla bug, see [MC-106167](https://bugs.mojang.com/browse/MC-106167)). + +### `display_title(players, 'player_list_header', text)` +### `display_title(players, 'player_list_footer', text)` + +Changes the header or footer of the player list for the specified targets. +If `text` is `null` or an empty string it will remove the header or footer for the specified targets. +In case the player has Carpet loggers running, the footer specified by Scarpet will appear above the loggers. + +### `logger(msg), logger(type, msg)` + +Prints the message to system logs, and not to chat. +By default prints an info, unless you specify otherwise in the `type` parameter. + +Available output types: + +`'debug'`, `'warn'`, `'fatal'`, `'info'` and `'error'` + + +### `read_file(resource, type)` +### `delete_file(resource, type)` +### `write_file(resource, type, data, ...)` +### `list_files(resource, type)` + +With the specified `resource` in the scripts folder, of a specific `type`, writes/appends `data` to it, reads its + content, deletes the resource, or lists other files under this resource. + +Resource is identified by a path to the file. +A path can contain letters, numbers, characters `-`, `+`, or `_`, and a folder separator: `'/'`. Any other characters are stripped +from the name. Empty descriptors are invalid, except for `list_files` where it means the root folder. + Do not add file extensions to the descriptor - extensions are inferred +based on the `type` of the file. A path can have one `'.zip'` component indicating a zip folder allowing to read / write to and from +zip files, although you cannot nest zip files in other zip files. + +Resources can be located in the app specific space, or a shared space for all the apps. Accessing of app-specific +resources is guaranteed to be isolated from other apps. Shared resources are... well, shared across all apes, meaning +they can eat of each others file, however all access to files is synchronized, and files are never left open, so +this should not lead to any access problems. + +If the app's name is `'foo'`, the script location would +be `world/scripts/foo.sc`, app +specific data directory is under `world/scripts/foo.data/...`, and shared data space is under +`world/scripts/shared/...`. + +The default no-name app, via `/script run` command can only save/load/read files from the shared space. + +Functions return `null` if no file is present (for read, list and delete operations). Returns `true` +for success writes and deletes, and requested data, based on the file type, for read operations. It returns list of files +for folder listing. + +Supported values for resource `type` are: + * `nbt` - NBT tag + * `json` - JSON file + * `text` - text resource with automatic newlines added + * `raw` - text resource without implied newlines + * `folder` - for `list_files` only - indicating folder listing instead of files + * `shared_nbt`, `shared_text`, `shared_raw`, `shared_folder`, `shared_json` - shared versions of the above + +NBT files have extension `.nbt`, store one NBT tag, and return a NBT type value. JSON files have `.json` extension, store +Scarpet numbers, strings, lists, maps and `null` values. Anything else will be saved as a string (including NBT). +Text files have `.txt` extension, +stores multiple lines of text and returns lists of all lines from the file. With `write_file`, multiple lines can be +sent to the file at once. The only difference between `raw` and `text` types are automatic newlines added after each +record to the file. Since files are closed after each write, sending multiple lines of data to +write is beneficial for writing speed. To send multiple packs of data, either provide them flat or as a list in the +third argument. + +Throws: +- `nbt_read_error`: When failed to read NBT file. +- `json_read_error`: When failed to read JSON file. The exception data will contain details about the problem. +- `io_exception`: For all other errors when handling data on disk not related to encoding issues + +All other errors resulting of improper use of input arguments should result in `null` returned from the function, rather than exception +thrown. + +
+write_file('foo', 'shared_text, ['one', 'two']); +write_file('foo', 'shared_text', 'three\n', 'four\n'); +write_file('foo', 'shared_raw', 'five\n', 'six\n'); + +read_file('foo', 'shared_text') => ['one', 'two', 'three', '', 'four', '', 'five', 'six'] ++ +### `run(expr)` + +Runs a vanilla command from the string result of the `expr` and returns a triple of 0 (unused after success count removal), +intercepted list of output messages, and error message if the command resulted in a failure. +Successful commands return `null` as their error. + +
+run('fill 1 1 1 10 10 10 air') -> [0, ["Successfully filled 123 blocks"], null] +run('give @s stone 4') -> [0, ["Gave 4 [Stone] to gnembon"], null] +run('seed') -> [0, ["Seed: [4031384495743822299]"], null] +run('sed') -> [0, [], "sed<--[HERE]"] // wrong command ++ +### `save()` + +Performs autosave, saves all chunks, player data, etc. Useful for programs where autosave is disabled due to +performance reasons and saves the world only on demand. + +### `load_app_data()` + +NOTE: usages with arguments, so `load_app_data(file)` and `load_app_data(file, shared?)` are deprecated. +Use `read_file` instead. + +Loads the app data associated with the app from the world /scripts folder. Without argument returns the memory +managed and buffered / throttled NBT tag. With a file name, reads explicitly a file with that name from the +scripts folder that belongs exclusively to the app. if `shared` is true, the file location is not exclusive +to the app anymore, but located in a shared app space. + +File descriptor can contain letters, numbers and folder separator: `'/'`. Any other characters are stripped +from the name before saving/loading. Empty descriptors are invalid. Do not add file extensions to the descriptor + +Function returns nbt value with the file content, or `null` if the file is missing or there were problems +with retrieving the data. + +The default no-name app, via `/script run` command can only save/load file from the shared data location. + +If the app's name is `'foo'`, the script location would +be `world/scripts/foo.sc`, system-managed default app data storage is in `world/scripts/foo.data.nbt`, app +specific data directory is under `world/scripts/foo.data/bar/../baz.nbt`, and shared data space is under +`world/scripts/shared/bar/../baz.nbt`. + +You can use app data to save non-vanilla information separately from the world and other scripts. + +Throws `nbt_read_error` if failed to read app data. + +### `store_app_data(tag)` + +Note: `store_app_data(tag, file)` and `store_app_data(tag, file, shared?)` usages deprecated. Use `write_file` instead. + +Stores the app data associated with the app from the world `/scripts` folder. With the `file` parameter saves +immediately and with every call to a specific file defined by the `file`, either in app space, or in the scripts +shared space if `shared` is true. Without `file` parameter, it may take up to 10 + seconds for the output file +to sync preventing flickering in case this tag changes frequently. It will be synced when server closes. + +Returns `true` if the file was saved successfully, `false` otherwise. + +Uses the same file structure for exclusive app data, and shared data folder as `load_app_data`. + +### `create_datapack(name, data)` + +Creates and loads custom datapack. The data has to be a map representing the file structure and the content of the +json files of the target pack. + +Returns `null` if the pack with this name already exists or is loaded, meaning no change has been made. +Returns `false` if adding of the datapack wasn't successful. +Returns `true` if creation and loading of the datapack was successful. Loading of a datapack results in +reloading of all other datapacks (vanilla restrictions, identical to /datapack enable), however unlike with `/reload` +command, scarpet apps will not be reloaded by adding a datapack using `create_datapack`. + +Currently, only json/nbt/mcfunction files are supported in the packs. `'pack.mcmeta'` file is added automatically. + +Reloading of datapacks that define new dimensions is not implemented in vanilla. Vanilla game only loads +dimension information on server start. `create_datapack` is therefore a direct replacement of manually ploping of the specified +file structure in a datapack file and calling `/datapack enable` on the new datapack with all its quirks and sideeffects +(like no worldgen changes, reloading all other datapacks, etc.). To enable newly added custom dimensions, call much more +experimental `enable_hidden_dimensions()` after adding a datapack if needed. + +Synopsis: +
+script run create_datapack('foo', +{ + 'foo' -> { 'bar.json' -> { + 'c' -> true, + 'd' -> false, + 'e' -> {'foo' -> [1,2,3]}, + 'a' -> 'foobar', + 'b' -> 5 + } } +}) ++ +Custom dimension example: +
+// 1.17 +script run create_datapack('funky_world', { + 'data' -> { 'minecraft' -> { 'dimension' -> { 'custom_ow.json' -> { + 'type' -> 'minecraft:the_end', + 'generator' -> { + 'biome_source' -> { + 'seed' -> 0, + 'large_biomes' -> false, + 'type' -> 'minecraft:vanilla_layered' + }, + 'seed' -> 0, + 'settings' -> 'minecraft:nether', + 'type' -> 'minecraft:noise' + } } } } } +}); + +// 1.18 +script run a() -> create_datapack('funky_world', { + 'data' -> { 'minecraft' -> { 'dimension' -> { 'custom_ow.json' -> { + 'type' -> 'minecraft:overworld', + 'generator' -> { + 'biome_source' -> { + 'biomes' -> [ + { + 'parameters' -> { + 'erosion' -> [-1.0,1.0], + 'depth' -> 0.0, + 'weirdness' -> [-1.0,1.0], + 'offset' -> 0.0, + 'temperature' -> [-1.0,1.0], + 'humidity' -> [-1.0,1.0], + 'continentalness' -> [ -1.2,-1.05] + }, + 'biome' -> 'minecraft:mushroom_fields' + } + ], + 'type' -> 'minecraft:multi_noise' + }, + 'seed' -> 0, + 'settings' -> 'minecraft:overworld', + 'type' -> 'minecraft:noise' + } + } } } } +}); +enable_hidden_dimensions(); => ['funky_world'] ++ +Loot table example: +
+script run create_datapack('silverfishes_drop_gravel', { + 'data' -> { 'minecraft' -> { 'loot_tables' -> { 'entities' -> { 'silverfish.json' -> { + 'type' -> 'minecraft:entity', + 'pools' -> [ + { + 'rolls' -> { + 'min' -> 0, + 'max' -> 1 + }, + 'entries' -> [ + { + 'type' -> 'minecraft:item', + 'name' -> 'minecraft:gravel' + } + ] + } + ] + } } } } } +}); ++ +Recipe example: +
+script run create_datapack('craftable_cobwebs', { + 'data' -> { 'scarpet' -> { 'recipes' -> { 'cobweb.json' -> { + 'type' -> 'crafting_shaped', + 'pattern' -> [ + 'SSS', + 'SSS', + 'SSS' + ], + 'key' -> { + 'S' -> { + 'item' -> 'minecraft:string' + } + }, + 'result' -> { + 'item' -> 'minecraft:cobweb', + 'count' -> 1 + } + } } } } +}); ++ +Function example: +
+ script run create_datapack('example',{'data/test/functions/talk.mcfunction'->'say 1\nsay 2'}) ++### `enable_hidden_dimensions()` (1.18.1 and lower) + +The function reads current datapack settings detecting new dimensions defined by these datapacks that have not yet been added +to the list of current dimensions and adds them so that they can be used and accessed right away. It doesn't matter how the +datapacks have been added to the game, either with `create_datapack()` or manually by dropping a datapack file and calling +`/datapack enable` on it. Returns the list of valid dimension names / identifiers that has been added in the process. + +Fine print: The function should be +considered experimental. For example: is not supposed to work at all in vanilla, and its doing exactly that in 1.18.2+. +There 'should not be' (famous last words) any side-effects if no worlds are added. Already connected +clients will not see suggestions for commands that use dimensions `/execute in
+loop(1000,game_tick()) // runs the game as fast as it can for 1000 ticks +loop(1000,game_tick(100)) // runs the game twice as slow for 1000 ticks ++ + +### `seed()` deprecated + +Returns current world seed. Function is deprecated, use `system_info('world_seed')` insteads. + +### `current_dimension()` + +Returns current dimension that the script runs in. + +### `in_dimension(smth, expr)` + +Evaluates the expression `expr` with different dimension execution context. `smth` can be an entity, +world-localized block, so not `block('stone')`, or a string representing a dimension like: + `'nether'`, `'the_nether'`, `'end'` or `'overworld'`, etc. + +Throws `unknown_dimension` if provided dimension can't be found. + +### `view_distance()` + +_**Deprecated**. Use `system_info('game_view_distance')` instead._ + +Returns the view distance of the server. + +### `get_mob_counts()`, `get_mob_counts(category)` 1.16+ + +Returns either a map of mob categories with its respective counts and capacities (a.k.a. mobcaps) or just a tuple +of count and limit for a specific category. If a category was not spawning for whatever reason it may not be +returned from `get_mob_counts()`, but could be retrieved for `get_mob_counts(category)`. Returned counts is what spawning +algorithm has taken in to account last time mobs spawned. + +### `schedule(delay, function, args...)` + +Schedules a user defined function to run with a specified `delay` ticks of delay. Scheduled functions run at the end +of the tick, and they will run in order they were scheduled. + +In case you want to schedule a function that is not defined in your module, please read the tips on + "Passing function references to other modules of your application" section in the `call(...)` section. + +### `statistic(player, category, entry)` + +Queries in-game statistics for certain values. Categories include: + +* `mined`: blocks mined +* `crafted`: items crafted +* `used`: items used +* `broken`: items broken +* `picked_up`: items picked up +* `dropped`: items dropped +* `killed`: mobs killed +* `killed_by`: mobs killed by +* `custom`: various random stats + +For the options of `entry`, consult your statistics page, or give it a guess. + +The call will return `null` if the statistics options are incorrect, or player doesn't have them in their history. +If the player encountered the statistic, or game created for him empty one, it will return a number. +Scarpet will not affect the entries of the statistics, even if it is just creating empty ones. With `null` response +it could either mean your input is wrong, or statistic effectively has a value of `0`. + + +### `system_info()`, `system_info(property)` +Fetches the value of one of the following system properties. If called without arguments, it returns a list of +available system_info options. It can be used to +fetch various information, mostly not changing, or only available via low level +system calls. In all circumstances, these are only provided as read-only. + +##### Available options in the scarpet app space: + * `app_name` - current app name or `null` if its a default app + * `app_list` - list of all loaded apps excluding default commandline app + * `app_scope` - scope of the global variables and function. Available options is `player` and `global` + * `app_players` - returns a player list that have app run under them. For `global` apps, the list is always empty + +##### Relevant world related properties + * `world_name` - name of the world + * `world_seed` - a numeric seed of the world + * `world_dimensions` - a list of dimensions in the world + * `world_path` - full path to the world saves folder + * `world_folder` - name of the direct folder in the saves that holds world files + * `world_carpet_rules` - returns all Carpet rules in a map form (`rule`->`value`). Note that the values are always returned as strings, so you can't do boolean comparisons directly. Includes rules from extensions with their namespace (`namespace:rule`->`value`). You can later listen to rule changes with the `on_carpet_rule_changes(rule, newValue)` event. + * `world_gamerules` - returns all gamerules in a map form (`rule`->`value`). Like carpet rules, values are returned as strings, so you can use appropriate value conversions using `bool()` or `number()` to convert them to other values. Gamerules are read-only to discourage app programmers to mess up with the settings intentionally applied by server admins. Isn't that just super annoying when a datapack messes up with your gamerule settings? It is still possible to change them though using `run('gamerule ...`. + * `world_spawn_point` - world spawn point in the overworld dimension + * `world_time` - Returns dimension-specific tick counter. + * `world_top` - Returns current dimensions' topmost Y value where one can place blocks. + * `world_bottom` - Returns current dimensions' bottommost Y value where one can place blocks. + * `world_center` - Returns coordinates of the center of the world with respect of the world border + * `world_size` - Returns radius of world border for current dimension. + * `world_max_size` - Returns maximum possible radius of world border for current dimension. + * `world_min_spawning_light` - Returns minimum light level at which mobs can spawn for current dimension, taking into account datapacks + +##### Relevant gameplay related properties + * `game_difficulty` - current difficulty of the game: `'peaceful'`, `'easy'`, `'normal'`, or `'hard'` + * `game_hardcore` - boolean whether the game is in hardcore mode + * `game_storage_format` - format of the world save files, either `'McRegion'` or `'Anvil'` + * `game_default_gamemode` - default gamemode for new players + * `game_max_players` - max allowed players when joining the world + * `game_view_distance` - the view distance + * `game_mod_name` - the name of the base mod. Expect `'fabric'` + * `game_version` - base version of the game + * `game_target` - target release version + * `game_major_target` - major release target. For 1.12.2, that would be 12 + * `game_minor_release` - minor release target. For 1.12.2, that would be 2 + * `game_protocol` - protocol version number + * `game_pack_version` - datapack version number + * `game_data_version` - data version of the game. Returns an integer, so it can be compared. + * `game_stable` - indicating if its a production release or a snapshot + +##### Server related properties + * `server_motd` - the motd of the server visible when joining + * `server_ip` - IP adress of the game hosted + * `server_whitelisted` - boolean indicating whether the access to the server is only for whitelisted players + * `server_whitelist` - list of players allowed to log in + * `server_banned_players` - list of banned player names + * `server_banned_ips` - list of banned IP addresses + * `server_dev_environment` - boolean indicating whether this server is in a development environment. + * `server_mods` - map with all loaded mods mapped to their versions as strings + * `server_last_tick_times` - Returns a 100-long array of recent tick times, in milliseconds. First item on the list is the most recent tick +If called outside of the main tick (either throgh scheduled tasks, or async execution), then the first item on the +list may refer to the previous tick performance. In this case the last entry (tick 100) would refer to the most current +tick. For all intent and purpose, `system_info('last_tick_times'):0` should be used as last tick execution time, but +individual tick times may vary greatly, and these need to be taken with the little grain of averaging. + +##### Source related properties + + The source is what is the cause of the code running, with Carpet using it same way as Minecraft commands use to run. Those are used in + some API functions that interact with the game or with commands, and can be manipulated if the execution is caused by an `execute` command, modified + by some functions or ran in non-standard ways. This section provides useful information from these cases (like running from a command + block, right clicking a sign, etc) + * `source_entity` - The entity associated with the execution. This is usually a player (in which case `player()` would get the entity from this), + but it may also be a different entity or `null` if the execution comes from the server console or a command block. + * `source_position` - The position associated with the execution. This is usually the position of the entity, but it may have been manipulated or + it could come from a command block (no entity then). If this call comes from the server console, it will be the world spawn. + * `source_dimension` - The dimension associated with the execution. Execution from the server console provides `overworld` as the dimension. + This can be manipulated by running code inside `in_dimension()`. + * `source_rotation` - The rotation associated with the execution. Usually `[0, 0]` in non-standard situations, the rotation of the entity otherwise. + +##### System related properties + * `java_max_memory` - maximum allowed memory accessible by JVM + * `java_allocated_memory` - currently allocated memory by JVM + * `java_used_memory` - currently used memory by JVM + * `java_cpu_count` - number of processors + * `java_version` - version of Java + * `java_bits` - number indicating how many bits the Java has, 32 or 64 + * `java_system_cpu_load` - current percentage of CPU used by the system + * `java_process_cpu_load` - current percentage of CPU used by JVM + +##### Scarpet related properties + * `scarpet_version` - returns the version of the carpet your scarpet comes with. + +## NBT Storage + +### `nbt_storage()`, `nbt_storage(key)`, `nbt_storage(key, nbt)` +Displays or modifies individual storage NBT tags. With no arguments, returns the list of current NBT storages. With specified `key`, returns the `nbt` associated with current `key`, or `null` if storage does not exist. With specified `key` and `nbt`, sets a new `nbt` value, returning previous value associated with the `key`. +NOTE: This NBT storage is shared with all vanilla datapacks and scripts of the entire server and is persistent between restarts and reloads. You can also access this NBT storage with vanilla `/data
+foo = 1; +//This is a comment +bar = 2; +// This never worked, so I commented it out +// baz = foo() ++ +### `/script load/unload
+/script run example_function(const, phrase, price) -> print(const+' '+phrase+' '+price) +/script invoke example_function pi costs 5 ++ +### `/script invokepoint
+/script outline 0 100 0 -40 60 -40 40 140 40 "x*x+y*y+z*z < 32*32" white_stained_glass replace air +/script outline 0 100 0 -40 60 -40 40 140 40 "x*x+y*y+z*z <= 32*32" white_stained_glass replace air +/script outline 0 100 0 -40 60 -40 40 140 40 "x*x+y*y+z*z < 32.5*32.5" white_stained_glass replace air +/script fill 0 100 0 -40 60 -40 40 140 40 "floor(sqrt(x*x+y*y+z*z)) == 32" white_stained_glass replace air +/script fill 0 100 0 -40 60 -40 40 140 40 "round(sqrt(x*x+y*y+z*z)) == 32" white_stained_glass replace air +/script fill 0 100 0 -40 60 -40 40 140 40 "ceil(sqrt(x*x+y*y+z*z)) == 32" white_stained_glass replace air +/draw sphere 0 100 0 32 white_stained_glass replace air // fluffy ball round(sqrt(x*x+y*y+z*z)-rand(abs(y)))==32 ++ +The last method is the one that world edit is using (part of carpet mod). It turns out that the outline method +with `32.5` radius, fill method with `round` function and draw command are equivalent + +# `script stop/script resume` command + +`/script stop` allows to stop execution of any script currently running that calls the `game_tick()` function which +allows the game loop to regain control of the game and process other commands. This will also make sure that all +current and future programs will stop their execution. Execution of all programs will be prevented +until `/script resume` command is called. + +Lets look at the following example. This is a program computes Fibonacci number in a recursive manner: + +
+fib(n) -> if(n<3, 1, fib(n-1)+fib(n-2) ); fib(8) ++ +That's really bad way of doing it, because the higher number we need to compute the compute requirements will +rise exponentially with `n`. It takes a little over 50 milliseconds to do fib(24), so above one tick, but about +a minute to do fib(40). Calling fib(40) will not only freeze the game, but also you woudn't be able to interrupt +its execution. We can modify the script as follows + +
+fib(n) -> ( game_tick(50); if(n<3, 1, fib(n-1)+fib(n-2) ) ); fib(40) ++ +But this would never finish as such call would finish after `~ 2^40` ticks. To make our computations responsive, +yet able to respond to user interactions, other commands, as well as interrupt execution, we could do the following: + +
+fib(n) -> ( if(n==23, game_tick(50) ); if(n<3, 1, fib(n-1)+fib(n-2) ) ); fib(40) ++ +This would slow down the computation of fib(40) from a minute to two, but allows the game to keep continue running +and be responsive to commands, using about half of each tick to advance the computation. Obviously depending on the +problem, and available hardware, certain things can take more or less time to execute, so portioning of work with +calling `gametick` should be balanced in each case separately + +# `/script download` command + +`/script download
+ print(format('rbu Error: ', 'r Stuff happened!')) + print(format('w Click ','tb [HERE]', '^di Awesome!', '!/kill', 'w \ button to win $1000')) + // the reason why I backslash the second space is that otherwise command parser may contract consecutive spaces + // not a problem in apps ++ +### `item_display_name(item)` + Returns the name of the item as a Text Value. `item` should be a list of `[item_name, count, nbt]`, or just an item name. + + Please note that it is a translated value. treating it like a string (eg.slicing, breaking, changing its case) will turn it back into a normal string without translatable properties. just like a colorful formatted text loose its color. And the result of it converting to a string will use en-us (in a server) or your single player's language, but when you use print() or others functions that accept a text value to broadcast it to players, it will use each player's own language. + + If the item is renamed, it will also be reflected in the results. + + +### `display_title(players, type, text?, fadeInTicks?, stayTicks?, fadeOutTicks),` + +Sends the player (or players if `players` is a list) a title of a specific type, with optionally some times. + * `players` is either an online player or a list of players. When sending a single player, it will throw if the player is invalid or offline. + * `type` is either `'title'`, `'subtitle'`, `actionbar` or `clear`. + Note: `subtitle` will only be displayed if there is a title being displayed (can be an empty one) + * `title` is what title to send to the player. It is required except for `clear` type. Can be a text formatted using `format()` + * `...Ticks` are the number of ticks the title will stay in that state. + If not specified, it will use current defaults (those defaults may have changed from a previous `/title times` execution). + Executing with those will set the times to the specified ones. + Note that `actionbar` type doesn't support changing times (vanilla bug, see [MC-106167](https://bugs.mojang.com/browse/MC-106167)). + +### `display_title(players, 'player_list_header', text)` +### `display_title(players, 'player_list_footer', text)` + +Changes the header or footer of the player list for the specified targets. +If `text` is `null` or an empty string it will remove the header or footer for the specified targets. +In case the player has Carpet loggers running, the footer specified by Scarpet will appear above the loggers. + +### `logger(msg), logger(type, msg)` + +Prints the message to system logs, and not to chat. +By default prints an info, unless you specify otherwise in the `type` parameter. + +Available output types: + +`'debug'`, `'warn'`, `'fatal'`, `'info'` and `'error'` + + +### `read_file(resource, type)` +### `delete_file(resource, type)` +### `write_file(resource, type, data, ...)` +### `list_files(resource, type)` + +With the specified `resource` in the scripts folder, of a specific `type`, writes/appends `data` to it, reads its + content, deletes the resource, or lists other files under this resource. + +Resource is identified by a path to the file. +A path can contain letters, numbers, characters `-`, `+`, or `_`, and a folder separator: `'/'`. Any other characters are stripped +from the name. Empty descriptors are invalid, except for `list_files` where it means the root folder. + Do not add file extensions to the descriptor - extensions are inferred +based on the `type` of the file. A path can have one `'.zip'` component indicating a zip folder allowing to read / write to and from +zip files, although you cannot nest zip files in other zip files. + +Resources can be located in the app specific space, or a shared space for all the apps. Accessing of app-specific +resources is guaranteed to be isolated from other apps. Shared resources are... well, shared across all apes, meaning +they can eat of each others file, however all access to files is synchronized, and files are never left open, so +this should not lead to any access problems. + +If the app's name is `'foo'`, the script location would +be `world/scripts/foo.sc`, app +specific data directory is under `world/scripts/foo.data/...`, and shared data space is under +`world/scripts/shared/...`. + +The default no-name app, via `/script run` command can only save/load/read files from the shared space. + +Functions return `null` if no file is present (for read, list and delete operations). Returns `true` +for success writes and deletes, and requested data, based on the file type, for read operations. It returns list of files +for folder listing. + +Supported values for resource `type` are: + * `nbt` - NBT tag + * `json` - JSON file + * `text` - text resource with automatic newlines added + * `raw` - text resource without implied newlines + * `folder` - for `list_files` only - indicating folder listing instead of files + * `shared_nbt`, `shared_text`, `shared_raw`, `shared_folder`, `shared_json` - shared versions of the above + +NBT files have extension `.nbt`, store one NBT tag, and return a NBT type value. JSON files have `.json` extension, store +Scarpet numbers, strings, lists, maps and `null` values. Anything else will be saved as a string (including NBT). +Text files have `.txt` extension, +stores multiple lines of text and returns lists of all lines from the file. With `write_file`, multiple lines can be +sent to the file at once. The only difference between `raw` and `text` types are automatic newlines added after each +record to the file. Since files are closed after each write, sending multiple lines of data to +write is beneficial for writing speed. To send multiple packs of data, either provide them flat or as a list in the +third argument. + +Throws: +- `nbt_read_error`: When failed to read NBT file. +- `json_read_error`: When failed to read JSON file. The exception data will contain details about the problem. +- `io_exception`: For all other errors when handling data on disk not related to encoding issues + +All other errors resulting of improper use of input arguments should result in `null` returned from the function, rather than exception +thrown. + +
+write_file('foo', 'shared_text, ['one', 'two']); +write_file('foo', 'shared_text', 'three\n', 'four\n'); +write_file('foo', 'shared_raw', 'five\n', 'six\n'); + +read_file('foo', 'shared_text') => ['one', 'two', 'three', '', 'four', '', 'five', 'six'] ++ +### `run(expr)` + +Runs a vanilla command from the string result of the `expr` and returns a triple of 0 (unused after success count removal), +intercepted list of output messages, and error message if the command resulted in a failure. +Successful commands return `null` as their error. + +
+run('fill 1 1 1 10 10 10 air') -> [0, ["Successfully filled 123 blocks"], null] +run('give @s stone 4') -> [0, ["Gave 4 [Stone] to gnembon"], null] +run('seed') -> [0, ["Seed: [4031384495743822299]"], null] +run('sed') -> [0, [], "sed<--[HERE]"] // wrong command ++ +### `save()` + +Performs autosave, saves all chunks, player data, etc. Useful for programs where autosave is disabled due to +performance reasons and saves the world only on demand. + +### `load_app_data()` + +NOTE: usages with arguments, so `load_app_data(file)` and `load_app_data(file, shared?)` are deprecated. +Use `read_file` instead. + +Loads the app data associated with the app from the world /scripts folder. Without argument returns the memory +managed and buffered / throttled NBT tag. With a file name, reads explicitly a file with that name from the +scripts folder that belongs exclusively to the app. if `shared` is true, the file location is not exclusive +to the app anymore, but located in a shared app space. + +File descriptor can contain letters, numbers and folder separator: `'/'`. Any other characters are stripped +from the name before saving/loading. Empty descriptors are invalid. Do not add file extensions to the descriptor + +Function returns nbt value with the file content, or `null` if the file is missing or there were problems +with retrieving the data. + +The default no-name app, via `/script run` command can only save/load file from the shared data location. + +If the app's name is `'foo'`, the script location would +be `world/scripts/foo.sc`, system-managed default app data storage is in `world/scripts/foo.data.nbt`, app +specific data directory is under `world/scripts/foo.data/bar/../baz.nbt`, and shared data space is under +`world/scripts/shared/bar/../baz.nbt`. + +You can use app data to save non-vanilla information separately from the world and other scripts. + +Throws `nbt_read_error` if failed to read app data. + +### `store_app_data(tag)` + +Note: `store_app_data(tag, file)` and `store_app_data(tag, file, shared?)` usages deprecated. Use `write_file` instead. + +Stores the app data associated with the app from the world `/scripts` folder. With the `file` parameter saves +immediately and with every call to a specific file defined by the `file`, either in app space, or in the scripts +shared space if `shared` is true. Without `file` parameter, it may take up to 10 + seconds for the output file +to sync preventing flickering in case this tag changes frequently. It will be synced when server closes. + +Returns `true` if the file was saved successfully, `false` otherwise. + +Uses the same file structure for exclusive app data, and shared data folder as `load_app_data`. + +### `create_datapack(name, data)` + +Creates and loads custom datapack. The data has to be a map representing the file structure and the content of the +json files of the target pack. + +Returns `null` if the pack with this name already exists or is loaded, meaning no change has been made. +Returns `false` if adding of the datapack wasn't successful. +Returns `true` if creation and loading of the datapack was successful. Loading of a datapack results in +reloading of all other datapacks (vanilla restrictions, identical to /datapack enable), however unlike with `/reload` +command, scarpet apps will not be reloaded by adding a datapack using `create_datapack`. + +Currently, only json/nbt/mcfunction files are supported in the packs. `'pack.mcmeta'` file is added automatically. + +Reloading of datapacks that define new dimensions is not implemented in vanilla. Vanilla game only loads +dimension information on server start. `create_datapack` is therefore a direct replacement of manually ploping of the specified +file structure in a datapack file and calling `/datapack enable` on the new datapack with all its quirks and sideeffects +(like no worldgen changes, reloading all other datapacks, etc.). To enable newly added custom dimensions, call much more +experimental `enable_hidden_dimensions()` after adding a datapack if needed. + +Synopsis: +
+script run create_datapack('foo', +{ + 'foo' -> { 'bar.json' -> { + 'c' -> true, + 'd' -> false, + 'e' -> {'foo' -> [1,2,3]}, + 'a' -> 'foobar', + 'b' -> 5 + } } +}) ++ +Custom dimension example: +
+// 1.17 +script run create_datapack('funky_world', { + 'data' -> { 'minecraft' -> { 'dimension' -> { 'custom_ow.json' -> { + 'type' -> 'minecraft:the_end', + 'generator' -> { + 'biome_source' -> { + 'seed' -> 0, + 'large_biomes' -> false, + 'type' -> 'minecraft:vanilla_layered' + }, + 'seed' -> 0, + 'settings' -> 'minecraft:nether', + 'type' -> 'minecraft:noise' + } } } } } +}); + +// 1.18 +script run a() -> create_datapack('funky_world', { + 'data' -> { 'minecraft' -> { 'dimension' -> { 'custom_ow.json' -> { + 'type' -> 'minecraft:overworld', + 'generator' -> { + 'biome_source' -> { + 'biomes' -> [ + { + 'parameters' -> { + 'erosion' -> [-1.0,1.0], + 'depth' -> 0.0, + 'weirdness' -> [-1.0,1.0], + 'offset' -> 0.0, + 'temperature' -> [-1.0,1.0], + 'humidity' -> [-1.0,1.0], + 'continentalness' -> [ -1.2,-1.05] + }, + 'biome' -> 'minecraft:mushroom_fields' + } + ], + 'type' -> 'minecraft:multi_noise' + }, + 'seed' -> 0, + 'settings' -> 'minecraft:overworld', + 'type' -> 'minecraft:noise' + } + } } } } +}); +enable_hidden_dimensions(); => ['funky_world'] ++ +Loot table example: +
+script run create_datapack('silverfishes_drop_gravel', { + 'data' -> { 'minecraft' -> { 'loot_tables' -> { 'entities' -> { 'silverfish.json' -> { + 'type' -> 'minecraft:entity', + 'pools' -> [ + { + 'rolls' -> { + 'min' -> 0, + 'max' -> 1 + }, + 'entries' -> [ + { + 'type' -> 'minecraft:item', + 'name' -> 'minecraft:gravel' + } + ] + } + ] + } } } } } +}); ++ +Recipe example: +
+script run create_datapack('craftable_cobwebs', { + 'data' -> { 'scarpet' -> { 'recipes' -> { 'cobweb.json' -> { + 'type' -> 'crafting_shaped', + 'pattern' -> [ + 'SSS', + 'SSS', + 'SSS' + ], + 'key' -> { + 'S' -> { + 'item' -> 'minecraft:string' + } + }, + 'result' -> { + 'item' -> 'minecraft:cobweb', + 'count' -> 1 + } + } } } } +}); ++ +Function example: +
+ script run create_datapack('example',{'data/test/functions/talk.mcfunction'->'say 1\nsay 2'}) ++### `enable_hidden_dimensions()` (1.18.1 and lower) + +The function reads current datapack settings detecting new dimensions defined by these datapacks that have not yet been added +to the list of current dimensions and adds them so that they can be used and accessed right away. It doesn't matter how the +datapacks have been added to the game, either with `create_datapack()` or manually by dropping a datapack file and calling +`/datapack enable` on it. Returns the list of valid dimension names / identifiers that has been added in the process. + +Fine print: The function should be +considered experimental. For example: is not supposed to work at all in vanilla, and its doing exactly that in 1.18.2+. +There 'should not be' (famous last words) any side-effects if no worlds are added. Already connected +clients will not see suggestions for commands that use dimensions `/execute in
+loop(1000,game_tick()) // runs the game as fast as it can for 1000 ticks +loop(1000,game_tick(100)) // runs the game twice as slow for 1000 ticks ++ + +### `seed()` deprecated + +Returns current world seed. Function is deprecated, use `system_info('world_seed')` insteads. + +### `current_dimension()` + +Returns current dimension that the script runs in. + +### `in_dimension(smth, expr)` + +Evaluates the expression `expr` with different dimension execution context. `smth` can be an entity, +world-localized block, so not `block('stone')`, or a string representing a dimension like: + `'nether'`, `'the_nether'`, `'end'` or `'overworld'`, etc. + +Throws `unknown_dimension` if provided dimension can't be found. + +### `view_distance()` + +_**Deprecated**. Use `system_info('game_view_distance')` instead._ + +Returns the view distance of the server. + +### `get_mob_counts()`, `get_mob_counts(category)` 1.16+ + +Returns either a map of mob categories with its respective counts and capacities (a.k.a. mobcaps) or just a tuple +of count and limit for a specific category. If a category was not spawning for whatever reason it may not be +returned from `get_mob_counts()`, but could be retrieved for `get_mob_counts(category)`. Returned counts is what spawning +algorithm has taken in to account last time mobs spawned. + +### `schedule(delay, function, args...)` + +Schedules a user defined function to run with a specified `delay` ticks of delay. Scheduled functions run at the end +of the tick, and they will run in order they were scheduled. + +In case you want to schedule a function that is not defined in your module, please read the tips on + "Passing function references to other modules of your application" section in the `call(...)` section. + +### `statistic(player, category, entry)` + +Queries in-game statistics for certain values. Categories include: + +* `mined`: blocks mined +* `crafted`: items crafted +* `used`: items used +* `broken`: items broken +* `picked_up`: items picked up +* `dropped`: items dropped +* `killed`: mobs killed +* `killed_by`: mobs killed by +* `custom`: various random stats + +For the options of `entry`, consult your statistics page, or give it a guess. + +The call will return `null` if the statistics options are incorrect, or player doesn't have them in their history. +If the player encountered the statistic, or game created for him empty one, it will return a number. +Scarpet will not affect the entries of the statistics, even if it is just creating empty ones. With `null` response +it could either mean your input is wrong, or statistic effectively has a value of `0`. + + +### `system_info()`, `system_info(property)` +Fetches the value of one of the following system properties. If called without arguments, it returns a list of +available system_info options. It can be used to +fetch various information, mostly not changing, or only available via low level +system calls. In all circumstances, these are only provided as read-only. + +##### Available options in the scarpet app space: + * `app_name` - current app name or `null` if its a default app + * `app_list` - list of all loaded apps excluding default commandline app + * `app_scope` - scope of the global variables and function. Available options is `player` and `global` + * `app_players` - returns a player list that have app run under them. For `global` apps, the list is always empty + +##### Relevant world related properties + * `world_name` - name of the world + * `world_seed` - a numeric seed of the world + * `world_dimensions` - a list of dimensions in the world + * `world_path` - full path to the world saves folder + * `world_folder` - name of the direct folder in the saves that holds world files + * `world_carpet_rules` - returns all Carpet rules in a map form (`rule`->`value`). Note that the values are always returned as strings, so you can't do boolean comparisons directly. Includes rules from extensions with their namespace (`namespace:rule`->`value`). You can later listen to rule changes with the `on_carpet_rule_changes(rule, newValue)` event. + * `world_gamerules` - returns all gamerules in a map form (`rule`->`value`). Like carpet rules, values are returned as strings, so you can use appropriate value conversions using `bool()` or `number()` to convert them to other values. Gamerules are read-only to discourage app programmers to mess up with the settings intentionally applied by server admins. Isn't that just super annoying when a datapack messes up with your gamerule settings? It is still possible to change them though using `run('gamerule ...`. + * `world_spawn_point` - world spawn point in the overworld dimension + * `world_time` - Returns dimension-specific tick counter. + * `world_top` - Returns current dimensions' topmost Y value where one can place blocks. + * `world_bottom` - Returns current dimensions' bottommost Y value where one can place blocks. + * `world_center` - Returns coordinates of the center of the world with respect of the world border + * `world_size` - Returns radius of world border for current dimension. + * `world_max_size` - Returns maximum possible radius of world border for current dimension. + * `world_min_spawning_light` - Returns minimum light level at which mobs can spawn for current dimension, taking into account datapacks + +##### Relevant gameplay related properties + * `game_difficulty` - current difficulty of the game: `'peaceful'`, `'easy'`, `'normal'`, or `'hard'` + * `game_hardcore` - boolean whether the game is in hardcore mode + * `game_storage_format` - format of the world save files, either `'McRegion'` or `'Anvil'` + * `game_default_gamemode` - default gamemode for new players + * `game_max_players` - max allowed players when joining the world + * `game_view_distance` - the view distance + * `game_mod_name` - the name of the base mod. Expect `'fabric'` + * `game_version` - base version of the game + * `game_target` - target release version + * `game_major_target` - major release target. For 1.12.2, that would be 12 + * `game_minor_release` - minor release target. For 1.12.2, that would be 2 + * `game_protocol` - protocol version number + * `game_pack_version` - datapack version number + * `game_data_version` - data version of the game. Returns an integer, so it can be compared. + * `game_stable` - indicating if its a production release or a snapshot + +##### Server related properties + * `server_motd` - the motd of the server visible when joining + * `server_ip` - IP adress of the game hosted + * `server_whitelisted` - boolean indicating whether the access to the server is only for whitelisted players + * `server_whitelist` - list of players allowed to log in + * `server_banned_players` - list of banned player names + * `server_banned_ips` - list of banned IP addresses + * `server_dev_environment` - boolean indicating whether this server is in a development environment. + * `server_mods` - map with all loaded mods mapped to their versions as strings + * `server_last_tick_times` - Returns a 100-long array of recent tick times, in milliseconds. First item on the list is the most recent tick +If called outside of the main tick (either throgh scheduled tasks, or async execution), then the first item on the +list may refer to the previous tick performance. In this case the last entry (tick 100) would refer to the most current +tick. For all intent and purpose, `system_info('last_tick_times'):0` should be used as last tick execution time, but +individual tick times may vary greatly, and these need to be taken with the little grain of averaging. + +##### Source related properties + + The source is what is the cause of the code running, with Carpet using it same way as Minecraft commands use to run. Those are used in + some API functions that interact with the game or with commands, and can be manipulated if the execution is caused by an `execute` command, modified + by some functions or ran in non-standard ways. This section provides useful information from these cases (like running from a command + block, right clicking a sign, etc) + * `source_entity` - The entity associated with the execution. This is usually a player (in which case `player()` would get the entity from this), + but it may also be a different entity or `null` if the execution comes from the server console or a command block. + * `source_position` - The position associated with the execution. This is usually the position of the entity, but it may have been manipulated or + it could come from a command block (no entity then). If this call comes from the server console, it will be the world spawn. + * `source_dimension` - The dimension associated with the execution. Execution from the server console provides `overworld` as the dimension. + This can be manipulated by running code inside `in_dimension()`. + * `source_rotation` - The rotation associated with the execution. Usually `[0, 0]` in non-standard situations, the rotation of the entity otherwise. + +##### System related properties + * `java_max_memory` - maximum allowed memory accessible by JVM + * `java_allocated_memory` - currently allocated memory by JVM + * `java_used_memory` - currently used memory by JVM + * `java_cpu_count` - number of processors + * `java_version` - version of Java + * `java_bits` - number indicating how many bits the Java has, 32 or 64 + * `java_system_cpu_load` - current percentage of CPU used by the system + * `java_process_cpu_load` - current percentage of CPU used by JVM + +##### Scarpet related properties + * `scarpet_version` - returns the version of the carpet your scarpet comes with. + +## NBT Storage + +### `nbt_storage()`, `nbt_storage(key)`, `nbt_storage(key, nbt)` +Displays or modifies individual storage NBT tags. With no arguments, returns the list of current NBT storages. With specified `key`, returns the `nbt` associated with current `key`, or `null` if storage does not exist. With specified `key` and `nbt`, sets a new `nbt` value, returning previous value associated with the `key`. +NOTE: This NBT storage is shared with all vanilla datapacks and scripts of the entire server and is persistent between restarts and reloads. You can also access this NBT storage with vanilla `/data
+for(neighbours(x,y,z),air(_)) => 4 // number of air blocks around a block ++ +### `rect(center, range?, upper_range?)` + +Returns an iterator, just like `range` function that iterates over a rectangular area of blocks. If only center +point is specified, it iterates over 27 blocks (range of 1). If `range` arguments are specified, expands selection by +the respective number of blocks in each direction. If `upper_range` arguments are specified, it uses `range` for +negative offset, and `upper_range` for positive, similar to `scan`. + +Basically the arguments are the same as the first three arguments of `scan`, except this function returns the list of +blocks that `scan` would evaluate over. If you are going to iterate over these blocks, like `for(rect(args), do_something())`, +then `scan(args, do_something())` is an equivalent, yet more compute-friendly alternative, especially for very large areas. + +`center` can be defined either as three coordinates, a list of three coords, or a block value. +`range` and `upper_range` can have the same representations, just if they are block values, it computes the distance to the center +as range instead of taking the values as is.` + +### `diamond(center_pos, radius?, height?)` + +Iterates over a diamond like area of blocks. With no radius and height, its 7 blocks centered around the middle +(block + neighbours). With a radius specified, it expands shape on x and z coords, and with a custom height, on y. +Any of these can be zero as well. radius of 0 makes a stick, height of 0 makes a diamond shape pad. + +If radius and height are the same, creats a 3D diamond, of all the blocks which are a manhattan distance of `radius` away +from the center. diff --git a/docs/scarpet/api/BlocksAndWorldAccess.md b/docs/scarpet/api/BlocksAndWorldAccess.md new file mode 100644 index 0000000..d3f3c10 --- /dev/null +++ b/docs/scarpet/api/BlocksAndWorldAccess.md @@ -0,0 +1,837 @@ +# Blocks / World API + +## Specifying blocks + +### `block(x, y, z)`, `block([x,y,z])`, `block(state)` + +Returns either a block from specified location, or block with a specific state (as used by `/setblock` command), +so allowing for block properties, block entity data etc. Blocks otherwise can be referenced everywhere by its simple +string name, but its only used in its default state. + +
+block('air') => air +block('iron_trapdoor[half=top]') => iron_trapdoor +block(0,0,0) == block('bedrock') => 1 +block('hopper[facing=north]{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') => hopper ++ +Retrieving a block with `block` function has also a side-effect of evaluating its current state and data. +So if you use it later it will reflect block state and data of the block that was when block was called, rather than +when it was used. Block values passed in various places like `scan` functions, etc, are not fully evaluated unless +its properties are needed. This means that if the block at the location changes before its queried in the program this +might result in getting the later state, which might not be desired. Consider the following example: + +Throws `unknown_block` if provided input is not valid. + +
set(10,10,10,'stone'); +scan(10,10,10,0,0,0, b = _); +set(10,10,10,'air'); +print(b); // 'air', block was remembered 'lazily', and evaluated by `print`, when it was already set to air +set(10,10,10,'stone'); +scan(10,10,10,0,0,0, b = block(_)); +set(10,10,10,'air'); +print(b); // 'stone', block was evaluated 'eagerly' but call to `block` ++ +## World Manipulation + +All the functions below can be used with block value, queried with coord triple, or 3-long list. All `pos` in the +functions referenced below refer to either method of passing block position. + +### `set(pos, block, property?, value?, ..., block_data?)`, `set(pos, block, [property?, value?, ...], block_data?)`, `set(pos, block, {property? -> value?, ...}, block_data?)` + +First argument for the `set` function is either a coord triple, list of three numbers, or a world localized block value. +Second argument, `block`, is either an existing block value, a result of `block()` function, or a string value indicating the block name +with optional state and block data. It is then followed by an optional +`property - value` pairs for extra block state (which can also be provided in a list or a map). Optional `block_data` include the block data to +be set for the target block. + +If `block` is specified only by name, then if a +destination block is the same the `set` operation is skipped, otherwise is executed, for other potential extra +properties that the original source block may have contained. + +The returned value is either the block state that has been set, or `false` if block setting was skipped, or failed + +Throws `unknown_block` if provided block to set is not valid + +
+set(0,5,0,'bedrock') => bedrock +set([0,5,0], 'bedrock') => bedrock +set(block(0,5,0), 'bedrock') => bedrock +scan(0,5,0,0,0,0,set(_,'bedrock')) => 1 +set(pos(player()), 'bedrock') => bedrock +set(0,0,0,'bedrock') => 0 // or 1 in overworlds generated in 1.8 and before +scan(0,100,0,20,20,20,set(_,'glass')) + // filling the area with glass +scan(0,100,0,20,20,20,set(_,block('glass'))) + // little bit faster due to internal caching of block state selectors +b = block('glass'); scan(0,100,0,20,20,20,set(_,b)) + // yet another option, skips all parsing +set(x,y,z,'iron_trapdoor') // sets bottom iron trapdoor + +set(x,y,z,'iron_trapdoor[half=top]') // sets the top trapdoor +set(x,y,z,'iron_trapdoor','half','top') // also correct - top trapdoor +set(x,y,z,'iron_trapdoor', ['half','top']) // same +set(x,y,z,'iron_trapdoor', {'half' -> 'top'}) // same +set(x,y,z, block('iron_trapdoor[half=top]')) // also correct, block() provides extra parsing of block state + +set(x,y,z,'hopper[facing=north]{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') // extra block data +set(x,y,z,'hopper', {'facing' -> 'north'}, nbt('{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') ) // same ++ +### `without_updates(expr)` + +Evaluates subexpression without causing updates when blocks change in the world. + +For synchronization sake, as well as from the fact that suppressed update can only happen within a tick, +the call to the `expr` is docked on the main server task. + +Consider following scenario: We would like to generate a bunch of terrain in a flat world following a perlin noise +generator. The following code causes a cascading effect as blocks placed on chunk borders will cause other chunks to get +loaded to full, thus generated: + +
+__config() -> {'scope' -> 'global'}; +__on_chunk_generated(x, z) -> ( + scan(x,0,z,0,0,0,15,15,15, + if (perlin(_x/16, _y/8, _z/16) > _y/16, + set(_, 'black_stained_glass'); + ) + ) +) ++ +The following addition resolves this issue, by not allowing block updates past chunk borders: + +
+__config() -> {'scope' -> 'global'}; +__on_chunk_generated(x, z) -> ( + without_updates( + scan(x,0,z,0,0,0,15,15,15, + if (perlin(_x/16, _y/8, _z/16) > _y/16, + set(_, 'black_stained_glass'); + ) + ) + ) +) ++ +### `place_item(item, pos, facing?, sneak?)` + +Uses a given item in the world like it was used by a player. Item names are default minecraft item name, +less the minecraft prefix. Default facing is 'up', but there are other options: 'down', 'north', 'east', 'south', +'west', but also there are other secondary directions important for placement of blocks like stairs, doors, etc. +Try experiment with options like 'north-up' which is placed facing north with cursor pointing to the upper part of the +block, or 'up-north', which means a block placed facing up (player looking down) and placed smidge away of the block +center towards north. Optional sneak is a boolean indicating if a player would be sneaking while placing the +block - this option only affects placement of chests and scaffolding at the moment. + +Works with items that have the right-click effect on the block placed, like `bone_meal` on grass or axes on logs, +but doesn't open chests / containers, so have no effect on interactive blocks, like TNT, comparators, etc. + +Returns true if placement/use was +successful, false otherwise. + +Throws `unknown_item` if `item` doesn't exist + +
+place_item('stone',x,y,z) // places a stone block on x,y,z block +place_item('piston,x,y,z,'down') // places a piston facing down +place_item('carrot',x,y,z) // attempts to plant a carrot plant. Returns true if could place carrots at that position. +place_item('bone_meal',x,y,z) // attempts to bonemeal the ground. +place_item('wooden_axe',x,y,z) // attempts to strip the log. ++ +### `set_poi(pos, type, occupancy?)` + +Sets a Point of Interest (POI) of a specified type with optional custom occupancy. By default new POIs are not occupied. +If type is `null`, POI at position is removed. In any case, previous POI is also removed. Available POI types are: + +* `'unemployed', 'armorer', 'butcher', 'cartographer', 'cleric', 'farmer', 'fisherman', 'fletcher', 'leatherworker', 'librarian', 'mason', 'nitwit', 'shepherd', 'toolsmith', 'weaponsmith', 'home', 'meeting', 'beehive', 'bee_nest', 'nether_portal'` + +Interestingly, `unemployed`, and `nitwit` are not used in the game, meaning, they could be used as permanent spatial +markers for scarpet apps. `meeting` is the only one with increased max occupancy of 32. + +Throws `unknown_poi` if the provided point of interest doesn't exist + +### `set_biome(pos, biome_name, update=true)` + +Changes the biome at that block position. if update is specified and false, then chunk will not be refreshed +on the clients. Biome changes can only be sent to clients with the entire data from the chunk. + +Be aware that depending on the MC version and dimension settings biome can be set either in a 1x1x256 +column or 4x4x4 hyperblock, so for some versions Y will be ignored and for some precision of biome +setting is less than 1x1x1 block. + +Throws `unknown_biome` if the `biome_name` doesn't exist. + +### `update(pos)` + +Causes a block update at position. + +### `block_tick(pos)` + +Causes a block to tick at position. + +### `random_tick(pos)` + +Causes a random tick at position. + +### `destroy(pos), destroy(pos, -1), destroy(pos,
+mine(x,y,z) -> +( + p = player(); + slot = p~'selected_slot'; + item_tuple = inventory_get(p, slot); + if (!item_tuple, destroy(x,y,z,'air'); return()); // empty hand, just break with 'air' + [item, count, tag] = item_tuple; + tag_back = destroy(x,y,z, item, tag); + if (tag_back == false, // failed to break the item + return(tag_back) + ); + if (tag_back == true, // block broke, tool has no tag + return(tag_back) + ); + if (tag_back == null, //item broke + delete(tag:'Damage'); + inventory_set(p, slot, count-1, item, tag); + return(tag_back) + ); + if (type(tag_back) == 'nbt', // item didn't break, here is the effective nbt + inventory_set(p, slot, count, item, tag_back); + return(tag_back) + ); + print('How did we get here?'); +) ++ +### `harvest(player, pos)` + +Causes a block to be harvested by a specified player entity. Honors player item enchantments, as well as damages the +tool if applicable. If the entity is not a valid player, no block gets destroyed. If a player is not allowed to break +that block, a block doesn't get destroyed either. + +### `create_explosion(pos, power?, mode?, fire?, source?, attacker?)` + +Creates an explosion at a given position. Parameters work as follows: + - `'power'` - how strong the blast is, negative values count as 0 (default: `4` (TNT power)) + - `'mode'` - how to deal with broken blocks: `keep` keeps them, `destroy` destroys them and drops items, and `destroy_with_decay` destroys them, but doesn't always drop the items (default: `destroy_with_decay`) + - `fire` - whether extra fire blocks should be created (default: `false`) + - `source` - entity that is exploding. Note that it will not take explosion damage from this explosion (default: `null`) + - `attacker` - entity responsible for triggering, this will be displayed in death messages, and count towards kill counts, and can be damaged by the explosion (default: `null`) +Explosions created with this endpoint cannot be captured with `__on_explosion` event, however they will be captured +by `__on_explosion_outcome`. + +### `weather()`,`weather(type)`,`weather(type, ticks)` + +If called with no args, returns `'clear'`, `'rain` or `'thunder'` based on the current weather. If thundering, will +always return `'thunder'`, if not will return `'rain'` or `'clear'` based on the current weather. + +With one arg, (either `'clear'`, `'rain` or `'thunder'`), returns the number of remaining ticks for that weather type. +NB: It can thunder without there being a thunderstorm; there has to be both rain and thunder to form a storm. So if +running `weather()` returns `'thunder'`, you can use `weather('rain')>0` to see if there's a storm going on. + +With two args, sets the weather to the given `type` for `ticks` ticks. + +## Block and World querying + +### `pos(block), pos(entity)` + +Returns a triple of coordinates of a specified block or entity. Technically entities are queried with `query` function +and the same can be achieved with `query(entity,'pos')`, but for simplicity `pos` allows to pass all positional objects. + +
+pos(block(0,5,0)) => [0,5,0] +pos(player()) => [12.3, 45.6, 32.05] +pos(block('stone')) => Error: Cannot fetch position of an unrealized block ++ +### `pos_offset(pos, direction, amount?)` + +Returns a coords triple that is offset in a specified `direction` by `amount` of blocks. The default offset amount is +1 block. To offset into opposite facing, use negative numbers for the `amount`. + +
+pos_offset(block(0,5,0), 'up', 2) => [0,7,0] +pos_offset([0,5,0], 'up', -2 ) => [0,3,0] ++ +### `(Deprecated) block_properties(pos)` + +Deprecated by `keys(block_state(pos))`. + +### `(Deprecated) property(pos, name)` + +Deprecated by `block_state(pos, name)` + +### `block_state(block)`, `block_state(block, property)` + +If used with a `block` argument only, it returns a map of block properties and their values. If a block has no properties, returns an +empty map. + +If `property` is specified, returns a string value of that property, or `null` if property is not applicable. + +Returned values or properties are always strings. It is expected from the user to know what to expect and convert +values to numbers using `number()` function or booleans using `bool()` function. Returned string values can be directly used +back in state definition in various applications where block properties are required. + +`block_state` can also accept block names as input, returning block's default state. + +Throws `unknown_block` if the provided input is not valid. + +
+set(x,y,z,'iron_block'); block_state(x,y,z) => {} +set(x,y,z,'iron_trapdoor','half','top'); block_state(x,y,z) => {waterlogged: false, half: top, open: false, ...} +set(x,y,z,'iron_trapdoor','half','top'); block_state(x,y,z,'half') => top +block_state('iron_trapdoor','half') => top +set(x,y,z,'air'); block_state(x,y,z,'half') => null +block_state(block('iron_trapdoor[half=top]'),'half') => top +block_state(block('iron_trapdoor[half=top]'),'powered') => false +bool(block_state(block('iron_trapdoor[half=top]'),'powered')) => 0 ++ +### `block_list()`, `block_list(tag)` + +Returns list of all blocks in the game. If `tag` is provided, returns list of all blocks that belong to this block tag. +
+block_list() => [dark_oak_button, wall_torch, structure_block, polished_blackstone_brick_slab, cherry_sapling... ] +block_list('impermeable') => [glass, white_stained_glass, orange_stained_glass, magenta_stained_glass... ] //All da glass +block_list('rails') => [rail, powered_rail, detector_rail, activator_rail] +block_list('not_a_valid_block_tag') => null //Not a valid block tag ++ + +### `block_tags()`, `block_tags(block)`, `block_tags(block, tag)` + +Without arguments, returns list of available tags, with block supplied (either by coordinates, or via block name), returns lost +of tags the block belongs to, and if a tag is specified, returns `null` if tag is invalid, `false` if this block doesn't belong +to this tag, and `true` if the block belongs to the tag. + +Throws `unknown_block` if `block` doesn't exist + +
+block_tags() => [geode_invalid_blocks, wall_post_override, ice, wooden_stairs, bamboo_blocks, stone_bricks... ] +block_tags('iron_block') => [mineable/pickaxe, needs_stone_tool, beacon_base_blocks] +block_tags('glass') => [impermeable] +block_tags('glass', 'impermeable') => true +block_tags('glass', 'beacon_base_blocks') => false ++ +### `block_data(pos)` + +Return NBT string associated with specific location, or null if the block does not carry block data. Can be currently +used to match specific information from it, or use it to copy to another block + +
+block_data(x,y,z) => '{TransferCooldown:0,x:450,y:68, ... }' ++ +### `poi(pos), poi(pos, radius?, type?, status?, column_search?)` + +Queries a POI (Point of Interest) at a given position, returning `null` if none is found, or tuple of poi type and its +occupancy load. With optional `type`, `radius` and `status`, returns a list of POIs around `pos` within a +given `radius`. If the `type` is specified, returns only poi types of that types, or everything if omitted or `'any'`. +If `status` is specified (either `'any'`, `'available'`, or `'occupied'`) returns only POIs with that status. +With `column_search` set to `true`, it will return all POIs in a cuboid with `radius` blocks away on x and z, in the entire +block column from 0 to 255. Default (`false`) returns POIs within a spherical area centered on `pos` and with `radius` +radius. + +All results of `poi` calls are returned in sorted order with respect to the euclidean distance to the requested center of `pos`. + +The return format of the results is a list of poi type, occupancy load, and extra triple of coordinates. + +Querying for POIs using the radius is the intended use of POI mechanics, and the ability of accessing individual POIs +via `poi(pos)` in only provided for completeness. + +
+poi(x,y,z) => null // nothing set at position +poi(x,y,z) => ['meeting',3] // its a bell-type meeting point occupied by 3 villagers +poi(x,y,z,5) => [] // nothing around +poi(x,y,z,5) => [['nether_portal',0,[7,8,9]],['nether_portal',0,[7,9,9]]] // two portal blocks in the range ++ +### `biome()` `biome(name)` `biome(block)` `biome(block/name, feature)`, `biome(noise_map)` + +Without arguments, returns the list of biomes in the world. + +With block, or name, returns the name of the biome in that position, or throws `'unknown_biome'` if provided biome or block are not valid. + +(1.18+) if passed a map of `continentalness`, `depth`, `erosion`, `humidity`, `temperature`, `weirdness`, returns the biome that exists at those noise values. +Note: Have to pass all 6 of the mentioned noise types and only these noise types for it to evaluate a biome. + +With an optional feature, it returns value for the specified attribute for that biome. Available and queryable features include: +* `'top_material'`: unlocalized block representing the top surface material (1.17.1 and below only) +* `'under_material'`: unlocalized block representing what sits below topsoil (1.17.1 and below only) +* `'category'`: the parent biome this biome is derived from. Possible values include (1.18.2 and below only): +`'none'`, `'taiga'`, `'extreme_hills'`, `'jungle'`, `'mesa'`, `'plains'`, `'savanna'`, +`'icy'`, `'the_end'`, `'beach'`, `'forest'`, `'ocean'`, `'desert'`, `'river'`, +`'swamp'`, `'mushroom'` , `'nether'`, `'underground'` (1.18+) and `'mountain'` (1.18+). +* `'tags'`: list of biome tags associated with this biome +* `'temperature'`: temperature from 0 to 1 +* `'fog_color'`: RGBA color value of fog +* `'foliage_color'`: RGBA color value of foliage +* `'sky_color'`: RGBA color value of sky +* `'water_color'`: RGBA color value of water +* `'water_fog_color'`: RGBA color value of water fog +* `'humidity'`: value from 0 to 1 indicating how wet is the biome +* `'precipitation'`: `'rain'` `'snot'`, or `'none'`... ok, maybe `'snow'`, but that means snots for sure as well. +* `'depth'`: (1.17.1 and below only) float value indicating how high or low the terrain should generate. Values > 0 indicate generation above sea level +and values < 0, below sea level. +* `'scale'`: (1.17.1 and below only) float value indicating how flat is the terrain. +* `'features'`: list of features that generate in the biome, grouped by generation steps +* `'structures'`: (1.17.1 and below only) list of structures that generate in the biome. + +### `solid(pos)` + +Boolean function, true if the block is solid. + +### `air(pos)` + +Boolean function, true if a block is air... or cave air... or void air... or any other air they come up with. + +### `liquid(pos)` + +Boolean function, true if the block is liquid, or waterlogged (with any liquid). + +### `flammable(pos)` + +Boolean function, true if the block is flammable. + +### `transparent(pos)` + +Boolean function, true if the block is transparent. + +### `opacity(pos)` + +Numeric function, returning the opacity level of a block. + +### `blocks_daylight(pos)` + +Boolean function, true if the block blocks daylight. + +### `emitted_light(pos)` + +Numeric function, returning the light level emitted from the block. + +### `light(pos)` + +Numeric function, returning the total light level at position. + +### `block_light(pos)` + +Numeric function, returning the block light at position (from torches and other light sources). + +### `sky_light(pos)` + +Numeric function, returning the sky light at position (from sky access). + +### `effective_light(pos)` + +Numeric function, returning the "real" light at position, which is affected by time and weather. which also affects mobs spawning, frosted ice blocks melting. + +### `see_sky(pos)` + +Boolean function, returning true if the block can see sky. + +### `hardness(pos)` + +Numeric function, indicating hardness of a block. + +### `blast_resistance(pos)` + +Numeric function, indicating blast_resistance of a block. + +### `in_slime_chunk(pos)` + +Boolean indicating if the given block position is in a slime chunk. + +### `top(type, pos)` + +Returns the Y value of the topmost block at given x, z coords (y value of a block is not important), according to the +heightmap specified by `type`. Valid options are: + +* `light`: topmost light blocking block (1.13 only) +* `motion`: topmost motion blocking block +* `terrain`: topmost motion blocking block except leaves +* `ocean_floor`: topmost non-water block +* `surface`: topmost surface block + +
+top('motion', x, y, z) => 63 +top('ocean_floor', x, y, z) => 41 ++ +### `suffocates(pos)` + +Boolean function, true if the block causes suffocation. + +### `power(pos)` + +Numeric function, returning redstone power level at position. + +### `ticks_randomly(pos)` + +Boolean function, true if the block ticks randomly. + +### `blocks_movement(pos)` + +Boolean function, true if the block at position blocks movement. + +### `block_sound(pos)` + +Returns the name of sound type made by the block at position. One of: + +`'wood'`, `'gravel'`, `'grass'`, `'stone'`, `'metal'`, `'glass'`, `'wool'`, `'sand'`, `'snow'`, +`'ladder'`, `'anvil'`, `'slime'`, `'sea_grass'`, `'coral'`, `'bamboo'`', `'shoots'`', `'scaffolding'`', `'berry'`', `'crop'`', +`'stem'`', `'wart'`', +`'lantern'`', `'fungi_stem'`', `'nylium'`', `'fungus'`', `'roots'`', `'shroomlight'`', `'weeping_vines'`', `'soul_sand'`', + `'soul_soil'`', `'basalt'`', +`'wart'`', `'netherrack'`', `'nether_bricks'`', `'nether_sprouts'`', `'nether_ore'`', `'bone'`', `'netherite'`', `'ancient_debris'`', +`'lodestone'`', `'chain'`', `'nether_gold_ore'`', `'gilded_blackstone'`', +`'candle'`', `'amethyst'`', `'amethyst_cluster'`', `'small_amethyst_bud'`', `'large_amethyst_bud'`', `'medium_amethyst_bud'`', +`'tuff'`', `'calcite'`', `'copper'`' + +### `(Deprecated) material(pos)` + +Returns `'unknown'`. The concept of material for blocks is removed. On previous versions it returned the name of the material the block +was made of. + +### `map_colour(pos)` + +Returns the map colour of a block at position. One of: + +`'air'`, `'grass'`, `'sand'`, `'wool'`, `'tnt'`, `'ice'`, `'iron'`, `'foliage'`, `'snow'`, `'clay'`, `'dirt'`, +`'stone'`, `'water'`, `'wood'`, `'quartz'`, `'adobe'`, `'magenta'`, `'light_blue'`, `'yellow'`, `'lime'`, `'pink'`, +`'gray'`, `'light_gray'`, `'cyan'`, `'purple'`, `'blue'`, `'brown'`, `'green'`, `'red'`, `'black'`, `'gold'`, +`'diamond'`, `'lapis'`, `'emerald'`, `'obsidian'`, `'netherrack'`, `'white_terracotta'`, `'orange_terracotta'`, +`'magenta_terracotta'`, `'light_blue_terracotta'`, `'yellow_terracotta'`, `'lime_terracotta'`, `'pink_terracotta'`, +`'gray_terracotta'`, `'light_gray_terracotta'`, `'cyan_terracotta'`, `'purple_terracotta'`, `'blue_terracotta'`, +`'brown_terracotta'`, `'green_terracotta'`, `'red_terracotta'`, `'black_terracotta'`, +`'crimson_nylium'`, `'crimson_stem'`, `'crimson_hyphae'`, `'warped_nylium'`, `'warped_stem'`, `'warped_hyphae'`, `'warped_wart'` + +### `sample_noise()`, `sample_noise(pos, ... types?)` 1.18+ + +Samples the world generation noise values / data driven density function(s) at a given position. + +If no types are passed in, or no arguments are given, it returns a list of all the available registry defined density functions. + +With a single function name passed in, it returns a scalar. With multiple function names passed in, it returns a list of results. + +Function accepts any registry defined density functions, both built in, as well as namespaced defined in datapacks. +On top of that, scarpet provides the following list of noises sampled directly from the current level (and not returned with no-argument call): + + +`'barrier_noise'`, `'fluid_level_floodedness_noise'`, `'fluid_level_spread_noise'`, `'lava_noise'`, +`'temperature'`, `'vegetation'`, `'continents'`, `'erosion'`, `'depth'`, `'ridges'`, +`'initial_density_without_jaggedness'`, `'final_density'`, `'vein_toggle'`, `'vein_ridged'` and `'vein_gap'` + +
+// requesting single value +sample_density(pos, 'continents') => 0.211626790923 +// passing type as multiple arguments +sample_density(pos, 'continents', 'depth', 'overworld/caves/pillars', 'mydatapack:foo/my_function') => [-0.205013844481, 1.04772473438, 0.211626790923, 0.123] ++ +### `loaded(pos)` + +Boolean function, true if the block is accessible for the game mechanics. Normally `scarpet` doesn't check if operates +on loaded area - the game will automatically load missing blocks. We see this as an advantage. Vanilla `fill/clone` +commands only check the specified corners for loadness. + +To check if a block is truly loaded, I mean in memory, use `generation_status(x) != null`, as chunks can still be loaded +outside of the playable area, just are not used by any of the game mechanic processes. + +
+loaded(pos(player())) => 1 +loaded(100000,100,1000000) => 0 ++ +### `(Deprecated) loaded_ep(pos)` + +Boolean function, true if the block is loaded and entity processing, as per 1.13.2 + +Deprecated as of scarpet 1.6, use `loaded_status(x) > 0`, or just `loaded(x)` with the same effect + +### `loaded_status(pos)` + +Returns loaded status as per new 1.14 chunk ticket system, 0 for inaccessible, 1 for border chunk, 2 for redstone ticking, +3 for entity ticking + +### `is_chunk_generated(pos)`, `is_chunk_generated(pos, force)` + +Returns `true` if the region file for the chunk exists, +`false` otherwise. If optional force is `true` it will also check if the chunk has a non-empty entry in its region file +Can be used to assess if the chunk has been touched by the game or not. + +`generation_status(pos, false)` only works on currently loaded chunks, and `generation_status(pos, true)` will create +an empty loaded chunk, even if it is not needed, so `is_chunk_generated` can be used as a efficient proxy to determine +if the chunk physically exists. + +Running `is_chunk_generated` is has no effects on the world, but since it is an external file operation, it is +considerably more expensive (unless area is loaded) than other generation and loaded checks. + +### `generation_status(pos), generation_status(pos, true)` + +Returns generation status as per the ticket system. Can return any value from several available but chunks +can only be stable in a few states: `full`, `features`, `liquid_carvers`, and `structure_starts`. Returns `null` +if the chunk is not in memory unless called with optional `true`. + +### `inhabited_time(pos)` + +Returns inhabited time for a chunk. + +### `spawn_potential(pos)` + +Returns spawn potential at a location (1.16+ only) + +### `reload_chunk(pos)` + +Sends full chunk data to clients. Useful when lots stuff happened and you want to refresh it on the clients. + +### `reset_chunk(pos)`, `reset_chunk(from_pos, to_pos)`, `reset_chunk([pos, ...])` +Removes and resets the chunk, all chunks in the specified area or all chunks in a list at once, removing all previous +blocks and entities, and replacing it with a new generation. For all currently loaded chunks, they will be brought +to their current generation status, and updated to the player. All chunks that are not in the loaded area, will only +be generated to the `'structure_starts'` status, allowing to generate them fully as players are visiting them. +Chunks in the area that has not been touched yet by the game will not be generated / regenerated. + +It returns a `map` with a report indicating how many chunks were affected, and how long each step took: + * `requested_chunks`: total number of chunks in the requested area or list + * `affected_chunks`: number of chunks that will be removed / regenerated + * `loaded_chunks`: number of currently loaded chunks in the requested area / list + * `relight_count`: number of relit chunks + * `relight_time`: time took to relit chunks + * `layer_count_
+query(p, 'name') <=> p ~ 'name' // much shorter and cleaner +query(p, 'holds', 'offhand') <=> p ~ ['holds', 'offhand'] // not really but can be done ++ +### `query(e, 'removed')` + +Boolean. True if the entity is removed. + +### `query(e, 'id')` + +Returns numerical id of the entity. Most efficient way to keep track of entities in a script. +Ids are only unique within current game session (ids are not preserved between restarts), +and dimension (each dimension has its own ids which can overlap). + +### `query(e, 'uuid')` + +Returns the UUID (unique id) of the entity. Can be used to access entities with the other vanilla commands and +remains unique regardless of the dimension, and is preserved between game restarts. Apparently players cannot be +accessed via UUID, but should be accessed with their name instead. + +
+map(entities_area('*',x,y,z,30,30,30),run('kill '+query(_,'id'))) // doesn't kill the player ++ +### `query(e, 'pos')` + +Triple of the entity's position + +### `query(e, 'location')` + +Quin-tuple of the entity's position (x, y, and z coords), and rotation (yaw, pitch) + +### `query(e, 'x'), query(e, 'y'), query(e, 'z')` + +Respective component of entity's coordinates + +### `query(e, 'pitch')`, `query(e, 'yaw')` + +Pitch and Yaw or where entity is looking. + +### `query(e, 'head_yaw')`, `query(e, 'body_yaw')` + +Applies to living entites. Sets their individual head and body facing angle. + +### `query(e, 'look')` + +Returns a 3d vector where the entity is looking. + +### `query(e, 'motion')` + +Triple of entity's motion vector, `[motion_x, motion_y, motion_z]`. Motion represents the velocity from all the forces +that exert on the given entity. Things that are not 'forces' like voluntary movement, or reaction from the ground are +not part of said forces. + +### `query(e, 'motion_x'), query(e, 'motion_y'), query(e, 'motion_z')` + +Respective component of the entity's motion vector + +### `query(e, 'on_ground')` + +Returns `true` if an entity is standing on firm ground and falling down due to that. + +### `query(e, 'name'), query(e, 'display_name'), query(e, 'custom_name'), query(e, 'type')` + +String of entity name or formatted text in the case of `display_name` + +
+query(e,'name') => Leatherworker +query(e,'custom_name') => null +query(e,'type') => villager ++ +### `query(e, 'command_name')` + +Returns a valid string to be used in commands to address an entity. Its UUID for all entities except +player, where its their name. + +
+run('/kill ' + e~'command_name'); ++ +### `query(e, 'persistence')` + +Returns if a mob has a persistence tag or not. Returns `null` for non-mob entities. + +### `query(e, 'is_riding')` + +Boolean, true if the entity is riding another entity. + +### `query(e, 'is_ridden')` + +Boolean, true if another entity is riding it. + +### `query(e, 'passengers')` + +List of entities riding the entity. + +### `query(e, 'mount')` + +Entity that `e` rides. + +### `query(e, 'unmountable')` + +Boolean, true if the entity cannot be mounted. + +### `(deprecated) query(e, 'tags')` + +Deprecated by `query(e, 'scoreboard_tags')` + +### `query(e, 'scoreboard_tags')` + +List of entity's scoreboard tags. + +### `(deprecated) query(e, 'has_tag',tag)` + +Deprecated by `query(e, 'has_scoreboard_tag',tag)` + +### `query(e, 'has_scoreboard_tag',tag)` + +Boolean, true if the entity is marked with a `tag` scoreboad tag. + +### `query(e, 'entity_tags')` + +List of entity tags assigned to the type this entity represents. + +### `query(e, 'has_entity_tag', tag)` + +Returns `true` if the entity matches that entity tag, `false` if it doesn't, and `null` if the tag is not valid. + +### `query(e, 'is_burning')` + +Boolean, true if the entity is burning. + +### `query(e, 'fire')` + +Number of remaining ticks of being on fire. + +### `query(e, 'is_freezing')` + +Boolean, true if the entity is freezing. + +### `query(e, 'frost')` + +Number of remaining ticks of being frozen. + +### `query(e, 'silent')` + +Boolean, true if the entity is silent. + +### `query(e, 'gravity')` + +Boolean, true if the entity is affected by gravity, like most entities are. + +### `query(e, 'invulnerable')` + +Boolean, true if the entity is invulnerable. + +### `query(e, 'immune_to_fire')` + +Boolean, true if the entity is immune to fire. + +### `query(e, 'immune_to_frost')` + +Boolean, true if the entity is immune to frost. + +### `query(e, 'dimension')` + +Name of the dimension the entity is in. + +### `query(e, 'height')` + +Height of the entity in blocks. + +### `query(e, 'width')` + +Width of the entity in blocks. + +### `query(e, 'eye_height')` + +Eye height of the entity in blocks. + +### `query(e, 'age')` + +Age of the entity in ticks, i.e. how long it existed. + +### `query(e, 'breeding_age')` + +Breeding age of passive entity, in ticks. If negative, time to adulthood, if positive, breeding cooldown. + +### `query(e, 'despawn_timer')` + +For living entities, the number of ticks they fall outside of immediate player presence. + +### `query(e, 'portal_cooldown')` + +Number of ticks remaining until an entity can use a portal again. + +### `query(e, 'portal_timer')` + +Number of ticks an entity sits in a portal. + +### `query(e, 'item')` + +The item triple (name, count, nbt) if its an item or item frame entity, `null` otherwise. + +### `query(e, 'offering_flower')` + +Whether the given iron golem has a red flower in their hand. returns null for all other entities + + +### `query(e, 'blue_skull')` + +Whether the given wither skull entity is blue. returns null for all other entities + + +### `query(e, 'count')` + +Number of items in a stack from item entity, `null` otherwise. + +### `query(e, 'pickup_delay')` + +Retrieves pickup delay timeout for an item entity, `null` otherwise. + +### `query(e, 'is_baby')` + +Boolean, true if its a baby. + +### `query(e, 'target')` + +Returns mob's attack target or null if none or not applicable. + +### `query(e, 'home')` + +Returns creature's home position (as per mob's AI, leash etc) or null if none or not applicable. + +### `query(e, 'spawn_point')` + +Returns position tuple, dimension, spawn angle, and whether spawn is forced, assuming the player has a spawn position. +Returns `false` if spawn position is not set, and `null` if `e` is not a player. + +### `query(e, 'path')` + +Returns path of the entity if present, `null` otherwise. The path comprises of list of nodes, each is a list +of block value, node type, penalty, and a boolean indicated if the node has been visited. + +### `query(e, 'pose')` + +Returns a pose of an entity, one of the following options: + * `'standing'` + * `'fall_flying'` + * `'sleeping'` + * `'swimming'` + * `'spin_attack'` + * `'crouching'` + * `'dying'` + +### `query(e, 'sneaking')` + +Boolean, true if the entity is sneaking. + +### `query(e, 'sprinting')` + +Boolean, true if the entity is sprinting. + +### `query(e, 'swimming')` + +Boolean, true if the entity is swimming. + +### `query(e, 'jumping')` + +Boolean, true if the entity is jumping. + +### `query(e, 'swinging')` + +Returns `true` if the entity is actively swinging their hand, `false` if not and `null` if swinging is not applicable to +that entity. + +### `query(e, 'gamemode')` + +String with gamemode, or `null` if not a player. + +### `query(e, 'gamemode_id')` + +Good'ol gamemode id, or null if not a player. + +### `query(e, 'player_type')` + +Returns `null` if the argument is not a player, otherwise: + +* `singleplayer`: for singleplayer game +* `multiplayer`: for players on a dedicated server +* `lan_host`: for singleplayer owner that opened the game to LAN +* `lan_player`: for all other players that connected to a LAN host +* `fake`: any carpet-spawned fake player +* `shadow`: any carpet-shadowed real player +* `realms`: ? + +### `query(e, 'category')` +Returns a lowercase string containing the category of the entity (hostile, passive, water, ambient, misc). + +### `query(e, 'team')` + +Team name for entity, or `null` if no team is assigned. + +### `query(e, 'ping')` + +Player's ping in milliseconds, or `null` if its not a player. + +### `query(e, 'permission_level')` + +Player's permission level, or `null` if not applicable for this entity. + +### `query(e, 'client_brand')` + +Returns recognized type of client of the client connected. Possible results include `'vanilla'`, or `'carpet
+query(p,'effect') => [[haste, 0, 177], [speed, 0, 177]] +query(p,'effect','haste') => [0, 177] +query(p,'effect','resistance') => null ++ +### `query(e, 'health')` + +Number indicating remaining entity health, or `null` if not applicable. + +### `query(e, 'may_fly')` + +Returns a boolean indicating if the player can fly. + +### `query(e, 'flying')` + +Returns a boolean indicating if the player is flying. + +### `query(e, 'may_build')` + +Returns a boolean indicating if the player is allowed to place blocks. + +### `query(e, 'insta_build')` + +Returns a boolean indicating if the player can place blocks without consuming the item and if the player can shoot arrows without having them in the inventory. + +### `query(e, 'fly_speed')` + +Returns a number indicating the speed at which the player moves while flying. + +### `query(e, 'walk_speed')` + +Returns a number indicating the speed at which the player moves while walking. + +### `query(e, 'hunger')` +### `query(e, 'saturation')` +### `query(e, 'exhaustion')` + +Retrieves player hunger related information. For non-players, returns `null`. + +### `query(e, 'absorption')` + +Gets the absorption of the player (yellow hearts, e.g. when having a golden apple.) + +### `query(e, 'xp')` +### `query(e, 'xp_level')` +### `query(e, 'xp_progress')` +### `query(e, 'score')` + +Numbers related to player's xp. `xp` is the overall xp player has, `xp_level` is the levels seen in the hotbar, +`xp_progress` is a float between 0 and 1 indicating the percentage of the xp bar filled, and `score` is the number displayed upon death + +### `query(e, 'air')` + +Number indicating remaining entity air, or `null` if not applicable. + +### `query(e, 'language')` + +Returns `null` for any non-player entity, if not returns the player's language as a string. + +### `query(e, 'holds', slot?)` + +Returns triple of short name, stack count, and NBT of item held in `slot`, or `null` if nothing or not applicable. Available options for `slot` are: + +* `mainhand` +* `offhand` +* `head` +* `chest` +* `legs` +* `feet` + +If `slot` is not specified, it defaults to the main hand. + +### `query(e, 'selected_slot')` + +Number indicating the selected slot of entity's inventory. Currently only applicable to players. + +### `query(e, 'active_block')` + +Returns currently mined block by the player, as registered by the game server. + +### `query(e, 'breaking_progress')` + +Returns current breaking progress of a current player mining block, or `null`, if no block is mined. +Breaking progress, if not null, is any number 0 or above, while 10 means that the block should already be +broken by the client. This value may tick above 10, if the client / connection is lagging. + +Example: + +The following program provides custom breaking times, including nice block breaking animations, including instamine, for +blocks that otherwise would take longer to mine. + +[Video demo](https://youtu.be/zvEEuGxgCio) +```py +global_blocks = { + 'oak_planks' -> 0, + 'obsidian' -> 1, + 'end_portal_frame' -> 5, + 'bedrock' -> 10 +}; + +__on_player_clicks_block(player, block, face) -> +( + step = global_blocks:str(block); + if (step == 0, + destroy(block, -1); // instamine + , step != null, + schedule(0, '_break', player, pos(block), str(block), step, 0); + ) +); + +_break(player, pos, name, step, lvl) -> +( + current = player~'active_block'; + if (current != name || pos(current) != pos, + modify(player, 'breaking_progress', null); + , + modify(player, 'breaking_progress', lvl); + if (lvl >= 10, destroy(pos, -1)); + schedule(step, '_break', player, pos, name, step, lvl+1) + ); +) +``` + +### `query(e, 'facing', order?)` + +Returns where the entity is facing. optional order (number from 0 to 5, and negative), indicating primary directions +where entity is looking at. From most prominent (order 0) to opposite (order 5, or -1). + +### `query(e, 'trace', reach?, options?...)` + +Returns the result of ray tracing from entity perspective, indicating what it is looking at. Default reach is 4.5 +blocks (5 for creative players), and by default it traces for blocks and entities, identical to player attack tracing +action. This can be customized with `options`, use `'blocks'` to trace for blocks, `'liquids'` to include liquid blocks +as possible results, and `'entities'` to trace entities. You can also specify `'exact'` which returns the actual hit + coordinate as a triple, instead of a block or entity value. Any combination of the above is possible. When tracing +entities and blocks, blocks will take over the priority even if transparent or non-colliding +(aka fighting chickens in tall grass). + +Regardless of the options selected, the result could be: + - `null` if nothing is in reach + - an entity if look targets an entity + - block value if block is in reach, or + - a coordinate triple if `'exact'` option was used and hit was successful. + +### `query(e, 'attribute')` `query(e, 'attribute', name)` + +returns the value of an attribute of the living entity. If the name is not provided, +returns a map of all attributes and values of this entity. If an attribute doesn't apply to the entity, +or the entity is not a living entity, `null` is returned. + +### `query(e, 'brain', memory)` + +Retrieves brain memory for entity. Possible memory units highly depend on the game version. Brain is availalble +for villagers (1.15+) and Piglins, Hoglins, Zoglins and Piglin Brutes (1.16+). If memory is not present or +not available for the entity, `null` is returned. + +Type of the returned value (entity, position, number, list of things, etc) depends on the type of the requested +memory. On top of that, since 1.16, memories can have expiry - in this case the value is returned as a list of whatever +was there, and the current ttl in ticks. + +Available retrievable memories for 1.15.2: +* `home`, `job_site`, `meeting_point`, `secondary_job_site`, `mobs`, `visible_mobs`, `visible_villager_babies`, +`nearest_players`, `nearest_visible_player`, `walk_target`, `look_target`, `interaction_target`, +`breed_target`, `path`, `interactable_doors`, `opened_doors`, `nearest_bed`, `hurt_by`, `hurt_by_entity`, +`nearest_hostile`, `hiding_place`, `heard_bell_time`, `cant_reach_walk_target_since`, +`golem_last_seen_time`, `last_slept`, `last_woken`, `last_worked_at_poi` + +Available retrievable memories as of 1.16.2: +* `home`, `job_site`, `potential_job_site`, `meeting_point`, `secondary_job_site`, `mobs`, `visible_mobs`, +`visible_villager_babies`, `nearest_players`, `nearest_visible_players`, `nearest_visible_targetable_player`, +`walk_target`, `look_target`, `attack_target`, `attack_cooling_down`, `interaction_target`, `breed_target`, +`ride_target`, `path`, `interactable_doors`, `opened_doors`, `nearest_bed`, `hurt_by`, `hurt_by_entity`, `avoid_target`, +`nearest_hostile`, `hiding_place`, `heard_bell_time`, `cant_reach_walk_target_since`, `golem_detected_recently`, +`last_slept`, `last_woken`, `last_worked_at_poi`, `nearest_visible_adult`, `nearest_visible_wanted_item`, +`nearest_visible_nemesis`, `angry_at`, `universal_anger`, `admiring_item`, `time_trying_to_reach_admire_item`, +`disable_walk_to_admire_item`, `admiring_disabled`, `hunted_recently`, `celebrate_location`, `dancing`, +`nearest_visible_huntable_hoglin`, `nearest_visible_baby_hoglin`, `nearest_targetable_player_not_wearing_gold`, +`nearby_adult_piglins`, `nearest_visible_adult_piglins`, `nearest_visible_adult_hoglins`, +`nearest_visible_adult_piglin`, `nearest_visible_zombiefied`, `visible_adult_piglin_count`, +`visible_adult_hoglin_count`, `nearest_player_holding_wanted_item`, `ate_recently`, `nearest_repellent`, `pacified` + + +### `query(e, 'nbt', path?)` + +Returns full NBT of the entity. If path is specified, it fetches only the portion of the NBT that corresponds to the +path. For specification of `path` attribute, consult vanilla `/data get entity` command. + +Note that calls to `nbt` are considerably more expensive comparing to other calls in Minecraft API, and should only +be used when there is no other option. Returned value is of type `nbt`, which can be further manipulated with nbt +type objects via `get, put, has, delete`, so try to use API calls first for that. + +## Entity Modification + +Like with entity querying, entity modifications happen through one function. + +### `modify(e, 'remove')` + +Removes (not kills) entity from the game. + +### `modify(e, 'kill')` + +Kills the entity. + +### `modify(e, 'pos', x, y, z), modify(e, 'pos', [x,y,z] )` + +Moves the entity to a specified coords. + +### `modify(e, 'location', x, y, z, yaw, pitch), modify(e, 'location', [x, y, z, yaw, pitch] )` + +Changes full location vector all at once. + +### `modify(e, 'x', x), modify(e, 'y', y), modify(e, 'z', z)` + +Changes entity's location in the specified direction. + +### `modify(e, 'pitch', angle), modify(e, 'yaw', angle)` + +Changes entity's pitch or yaw angle. + +### `modify(e, 'look', x, y, z), modify(e, 'look', [x,y,z] )` + +Sets entity's 3d vector where the entity is looking. +For cases where the vector has a length of 0, yaw and pitch won't get changed. +When pointing straight up or down, yaw will stay the same. + +### `modify(e, 'head_yaw', angle)`, `modify(e, 'body_yaw', angle)` + +For living entities, controls their head and body yaw angle. + +### `modify(e, 'move', x, y, z), modify(e, 'move', [x,y,z] )` + +Moves the entity by a vector from its current location. + +### `modify(e, 'motion', x, y, z), modify(e, 'motion', [x,y,z] )` + +Sets the motion vector (where and how much entity is moving). + +### `modify(e, 'motion_x', x), modify(e, 'motion_y', y), modify(e, 'motion_z', z)` + +Sets the corresponding component of the motion vector. + +### `modify(e, 'accelerate', x, y, z), modify(e, 'accelerate', [x, y, z] )` + +Adds a vector to the motion vector. Most realistic way to apply a force to an entity. + +### `modify(e, 'custom_name')`, `modify(e, 'custom_name', name)`, `modify(e, 'custom_name', name, visible)` + +Sets the custom name of the entity. Without arguments - clears current custom name. Optional visible affects +if the custom name is always visible, even through blocks. + +### `modify(e, 'persistence', bool?)` + +Sets the entity persistence tag to `true` (default) or `false`. Only affects mobs. Persistent mobs +don't despawn and don't count towards the mobcap. + +### `modify(e, 'item', item_triple)` + +Sets the item for the item or item frame entity. (The item triple is a list of `[item_name, count, nbt]`, or just an item name.) + +### `modify(e, 'offering_flower', bool)` + +Sets if the iron golem has a red flower in hand. + +### `modify(e, 'blue_skull', bool)` + +Sets whether the wither skull entity is blue. + +### `modify(e, 'age', number)` + +Modifies entity's internal age counter. Fiddling with this will affect directly AI behaviours of complex +entities, so use it with caution. + +### `modify(e, 'pickup_delay', number)` + +Sets the pickup delay for the item entity. + +### `modify(e, 'breeding_age', number)` + +Sets the breeding age for the animal. + +### `modify(e, 'despawn_timer', number)` + +Sets a custom despawn timer value. + +### `modify(e, 'portal_cooldown', number)` + +Sets a custom number of ticks remaining until an entity can use a portal again. + +### `modify(e, 'portal_timer', number)` + +Sets a custom number of ticks an entity sits in a portal. + +### `modify(e, 'dismount')` + +Dismounts riding entity. + +### `modify(e, 'mount', other)` + +Mounts the entity to the `other`. + +### `modify(e, 'unmountable', boolean)` + +Denies or allows an entity to be mounted. + +### `modify(e, 'drop_passengers')` + +Shakes off all passengers. + +### `modify(e, 'mount_passengers', passenger, ? ...), modify(e, 'mount_passengers', [passengers] )` + +Mounts on all listed entities on `e`. + +### `modify(e, 'tag', tag, ? ...), modify(e, 'tag', [tags] )` + +Adds tag(s) to the entity. + +### `modify(e, 'clear_tag', tag, ? ...), modify(e, 'clear_tag', [tags] )` + +Removes tag(s) from the entity. + +### `modify(e, 'talk')` + +Make noises. + +### `modify(e, 'ai', boolean)` + +If called with `false` value, it will disable AI in the mob. `true` will enable it again. + +### `modify(e, 'no_clip', boolean)` + +Sets if the entity obeys any collisions, including collisions with the terrain and basic physics. Not affecting +players, since they are controlled client side. + +### `modify(e, 'effect', name?, duration?, amplifier?, show_particles?, show_icon?, ambient?)` + +Applies status effect to the living entity. Takes several optional parameters, which default to `0`, `true`, +`true` and `false`. If no duration is specified, or if it's null or 0, the effect is removed. If duration is less than 0, it will represent infinity. If name is not specified, +it clears all effects. + +### `modify(e, 'health', float)` + +Modifies the health of an entity. + +### `modify(e, 'may_fly', boolean)` + +Allows or denies the player the ability to fly. If the player is flying and the ability is removed, the player will stop flying. + +### `modify(e, 'flying', boolean)` + +Changes the flight status of the player (if it is flying or not). + +### `modify(e, 'may_build', boolean)` + +Allows or denies the player the ability to place blocks. + +### `modify(e, 'insta_build', boolean)` + +Allows or denies the player to place blocks without reducing the item count of the used stack and to shoot arrows without having them in the inventory. + +### `modify(e, 'fly_speed', float)` + +Modifies the value of the speed at which the player moves while flying. + +### `modify(e, 'walk_speed', float)` + +Modifies the value of the speed at which the player moves while walking. + +### `modify(e, 'selected_slot', int)` + +Changes player's selected slot. + +### `modify(e, 'home', null), modify(e, 'home', block, distance?), modify(e, 'home', x, y, z, distance?)` + +Sets AI to stay around the home position, within `distance` blocks from it. `distance` defaults to 16 blocks. +`null` removes it. _May_ not work fully with mobs that have this AI built in, like Villagers. + + +### `modify(e, 'spawn_point')`, `modify(e, 'spawn_point', null)`, `modify(e, 'spawn_point', pos, dimension?, angle?, forced?)` + +Changes player respawn position to given position, optional dimension (defaults to current player dimension), angle (defaults to +current player facing) and spawn forced/fixed (defaults to `false`). If `none` or nothing is passed, the respawn point +will be reset (as removed) instead. + +### `modify(e, 'gamemode', gamemode?), modify(e, 'gamemode', gamemode_id?)` + +Modifies gamemode of player to whatever string (case-insensitive) or number you put in. + +* 0: survival +* 1: creative +* 2: adventure +* 3: spectator + +### `modify(e, 'jumping', boolean)` + +Will make the entity constantly jump if set to true, and will stop the entity from jumping if set to false. +Note that jumping parameter can be fully controlled by the entity AI, so don't expect that this will have +a permanent effect. Use `'jump'` to make an entity jump once for sure. + +Requires a living entity as an argument. + +### `modify(e, 'jump')` + +Will make the entity jump once. + +### `modify(e, 'swing')` `modify(e, 'swing', 'offhand')` + +Makes the living entity swing their required limb. + +### `modify(e, 'silent', boolean)` + +Silences or unsilences the entity. + +### `modify(e, 'gravity', boolean)` + +Toggles gravity for the entity. + +### `modify(e, 'invulnerable', boolean)` + +Toggles invulnerability for the entity. + +### `modify(e, 'fire', ticks)` + +Will set entity on fire for `ticks` ticks. Set to 0 to extinguish. + +### `modify(e, 'frost', ticks)` + +Will give entity frost for `ticks` ticks. Set to 0 to unfreeze. + +### `modify(e, 'hunger', value)` +### `modify(e, 'saturation', value)` +### `modify(e, 'exhaustion', value)` + +Modifies directly player raw hunger components. Has no effect on non-players + +### `modify(e, 'absorption', value)` + +Sets the absorption value for the player. Each point is half a yellow heart. + +### `modify(e, 'add_xp', value)` +### `modify(e, 'xp_level', value)` +### `modify(e, 'xp_progress', value)` +### `modify(e, 'xp_score', value)` + +Manipulates player xp values - `'add_xp'` the method you probably want to use +to manipulate how much 'xp' an action should give. `'xp_score'` only affects the number you see when you die, and +`'xp_progress'` controls the xp progressbar above the hotbar, should take values from 0 to 1, but you can set it to any value, +maybe you will get a double, who knows. + +### `modify(e, 'air', ticks)` + +Modifies entity air. + +### `modify(e, 'add_exhaustion', value)` + +Adds exhaustion value to the current player exhaustion level - that's the method you probably want to use +to manipulate how much 'food' an action costs. + +### `modify(e, 'breaking_progress', value)` + +Modifies the breaking progress of a player currently mined block. Value of `null`, `-1` makes it reset. +Values `0` to `10` will show respective animation of a breaking block. Check `query(e, 'breaking_progress')` for +examples. + +### `modify(e, 'nbt_merge', partial_tag)` + +Merges a partial tag into the entity data and reloads the entity from its updated tag. Cannot be applied to players. + +### `modify(e, 'nbt', tag)` + +Reloads the entity from a supplied tag. Better use a valid entity tag, what can go wrong? Wonder what would happen if you +transplant rabbit's brain into a villager? Cannot be applied to players. + +## Entity Events + +There is a number of events that happen to entities that you can attach your own code to in the form of event handlers. +The event handler is any function that runs in your package that accepts certain expected parameters, which you can +expand with your own arguments. When it comes to the moment when the given command needs to be executed, it does so +providing that number of arguments it accepts is equal number of event arguments, and extra arguments passed when +defining the callback with `entity_event`. + +The following events can be handled by entities: + +* `'on_tick'`: executes every tick right before the entity is ticked in the game. Required arguments: `entity` +* `'on_move'`: executes every time an entity changes position, invoked just after it has been moved to the new position. Required arguments: `entity, velocity, pos1, pos2` +* `'on_death'`: executes once when a living entity dies. Required arguments: `entity, reason` +* `'on_removed'`: execute once when an entity is removed. Required arguments: `entity` +* `'on_damaged'`: executed every time a living entity is about to receive damage. +Required arguments: `entity, amount, source, attacking_entity` + +It doesn't mean that all entity types will have a chance to execute a given event, but entities will not error +when you attach an inapplicable event to it. + +In case you want to pass an event handler that is not defined in your module, please read the tips on + "Passing function references to other modules of your application" section in the `call(...)` section. + + +### `entity_load_handler(descriptor / descriptors, function)`, `entity_load_handler(descriptor / descriptors, call_name, ... args?)` + +Attaches a callback to trigger when any entity matching the following type / types is loaded in the game, allowing to grab a handle +to an entity right when it is loaded to the world without querying them every tick. Callback expects two parameters - the entity, +and a boolean value indicating if the entity was newly created(`true`) or just loaded from disk. Single argument functions accepting +only entities are allowed, but deprecated and will be removed at some point. + +If callback is `null`, then the current entity handler, if present, is removed. Consecutive calls to `entity_load_handler` will add / subtract +of the currently targeted entity types pool. + +Like other global events, calls to `entity_load_handler` should only be attached in apps with global scope. For player scope apps, +it will be called multiple times, once for each player. That's likely not what you want to do. + +``` +// veryfast method of getting rid of all the zombies. Callback is so early, its packets haven't reached yet the clients +// so to save on log errors, removal of mobs needs to be scheduled for later. +entity_load_handler('zombie', _(e, new) -> schedule(0, _(outer(e)) -> modify(e, 'remove'))) + +// another way to do it is to remove the entity when it starts ticking +entity_load_handler('zombie', _(e, new) -> entity_event(e, 'on_tick', _(e) -> modify(e, 'remove'))) + +// making all zombies immediately faster and less susceptible to friction of any sort +entity_load_handler('zombie', _(e, new) -> entity_event(e, 'on_tick', _(e) -> modify(e, 'motion', 1.2*e~'motion'))) +``` + +Word of caution: entities can be loaded with chunks in various states, for instance when a chunk is being generated, this means +that accessing world blocks would cause the game to freeze due to force generating that chunk while generating the chunk. Make +sure to never assume the chunk is ready and use `entity_load_handler` to schedule actions around the loaded entity, +or manipulate entity directly. + +Also, it is possible that mobs that spawn with world generation, while being 'added' have their metadata serialized and cached +internally (vanilla limitation), so some modifications to these entities may have no effect on them. This affects mobs created with +world generation. + +For instance the following handler is safe, as it only accesses the entity directly. It makes all spawned pigmen jump +``` +/script run entity_load_handler('zombified_piglin', _(e, new) -> if(new, modify(e, 'motion', 0, 1, 0)) ) +``` +But the following handler, attempting to despawn pigmen that spawn in portals, will cause the game to freeze due to cascading access to blocks that would cause neighbouring chunks +to force generate, causing also error messages for all pigmen caused by packets send after entity is removed by script. +``` +/script run entity_load_handler('zombified_piglin', _(e, new) -> if(new && block(pos(e))=='nether_portal', modify(e, 'remove') ) ) +``` +Easiest method to circumvent these issues is delay the check, which may or may not cause cascade load to happen, but +will definitely break the infinite chain. +``` +/script run entity_load_handler('zombified_piglin', _(e, new) -> if(new, schedule(0, _(outer(e)) -> if(block(pos(e))=='nether_portal', modify(e, 'remove') ) ) ) ) +``` +But the best is to perform the check first time the entity will be ticked - giving the game all the time to ensure chunk +is fully loaded and entity processing, removing the tick handler: +``` +/script run entity_load_handler('zombified_piglin', _(e, new) -> if(new, entity_event(e, 'on_tick', _(e) -> ( if(block(pos(e))=='nether_portal', modify(e, 'remove')); entity_event(e, 'on_tick', null) ) ) ) ) +``` +Looks little convoluted, but that's the safest method to ensure your app won't crash. + +### `entity_event(e, event, function)`, `entity_event(e, event, call_name, ... args?)` + +Attaches specific function from the current package to be called upon the `event`, with extra `args` carried to the +original required arguments for the event handler. + +
+protect_villager(entity, amount, source, source_entity, healing_player) -> +( + if(source_entity && source_entity~'type' != 'player', + modify(entity, 'health', amount + entity~'health' ); + particle('end_rod', pos(entity)+[0,3,0]); + print(str('%s healed thanks to %s', entity, healing_player)) + ) +); +__on_player_interacts_with_entity(player, entity, hand) -> +( + if (entity~'type' == 'villager', + entity_event(entity, 'on_damage', 'protect_villager', player~'name') + ) +) ++ +In this case this will protect a villager from entity damage (zombies, etc.) except from players by granting all the +health back to the villager after being harmed. diff --git a/docs/scarpet/api/Events.md b/docs/scarpet/api/Events.md new file mode 100644 index 0000000..59378f9 --- /dev/null +++ b/docs/scarpet/api/Events.md @@ -0,0 +1,452 @@ +# Scarpet events system + +Scarpet provides the ability to execute specific function whenever an event occurs. The functions to be subscribed for an event +need to conform with the arguments to the event specification. There are several built-in events triggered when certain in-game +events occur, but app designers can create their own events and trigger them across all loaded apps. + +When loading the app, each function that starts +with `__on_
+foo(a) -> print(a); +handle_event('boohoo', 'foo'); + +bar(a, b, c) -> print([a, b, c]); +handle_event('boohoo', 'bar', 2, 3) // using b = 2, c = 3, a - passed by the caller + +handle_event('tick', _() -> foo('tick happened')); // built-in event + +handle_event('tick', null) // nah, ima good, kthxbai ++ +In case you want to pass an event handler that is not defined in your module, please read the tips on + "Passing function references to other modules of your application" section in the `call(...)` section. + + +### `signal_event(event, target_player?, ... args?)` + +Fires a specific event. If the event does not exist (only `handle_event` creates missing new events), or provided argument list +was not matching the callee expected arguments, returns `null`, +otherwise returns number of apps notified. If `target_player` is specified and not `null` triggers a player specific event, targeting +only `player` scoped apps for that player. Apps with globals scope will not be notified even if they handle this event. +If the `target_player` is omitted or `null`, it will target `global` scoped apps and all instances of `player` scoped apps. +Note that all built-in player events have a player as a first argument, so to trigger these events, you need to +provide them twice - once to specify the target player scope and second - to provide as an argument to the handler function. + +
+signal_event('player_breaks_block', player, player, block); // to target all player scoped apps +signal_event('player_breaks_block', null , player, block); // to target all global scoped apps and all player instances +signal_event('tick') // trigger all apps with a tick event ++ +## Custom events example + +The following example shows how you can communicate between different instances of the same player scoped app. It important to note +that signals can trigger other apps as well, assuming the name of the event matches. In this case the request name is called +`tp_request` and is triggered with a command. + + +``` +// tpa.sc +global_requester = null; +__config() -> { + 'commands' -> { + '
+stack_limit('wooden_axe') => 1 +stack_limit('ender_pearl') => 16 +stack_limit('stone') => 64 ++ +### `recipe_data(item, type?)`, `recipe_data(recipe, type?)` + +returns all recipes matching either an `item`, or represent actual `recipe` name. In vanilla datapack, for all items +that have one recipe available, the recipe name is the same as the item name but if an item has multiple recipes, its +direct name can be different. + +Recipe type can take one of the following options: + * `'crafting'` - default, crafting table recipe + * `'smelting'` - furnace recipe + * `'blasting'` - blast furnace recipe + * `'smoking'` - smoker recipe + * `'campfire_cooking'` - campfire recipe + * `'stonecutting'` - stonecutter recipe + * `'smithing'` - smithing table (1.16+) + + The return value is a list of available recipes (even if there is only one recipe available). Each recipe contains of + an item triple of the crafting result, list of ingredients, each containing a list of possible variants of the + ingredients in this slot, as item triples, or `null` if its a shaped recipe and a given slot in the patterns is left + empty, and recipe specification as another list. Possible recipe specs is: + * `['shaped', width, height]` - shaped crafting. `width` and `height` can be 1, 2 or 3. + * `['shapeless']` - shapeless crafting + * `['smelting', duration, xp]` - smelting/cooking recipes + * `['cutting']` - stonecutter recipe + * `['special']` - special crafting recipe, typically not present in the crafting menu + * `['custom']` - other recipe types + +Note that ingredients are specified as tripes, with count and nbt information. Currently all recipes require always one +of the ingredients, and for some recipes, even if the nbt data for the ingredient is specified (e.g. `dispenser`), it +can accept items of any tags. + +Also note that some recipes leave some products in the crafting window, and these can be determined using + `crafting_remaining_item()` function + + Examples: +
+ recipe_data('iron_ingot_from_nuggets') + recipe_data('iron_ingot') + recipe_data('glass', 'smelting') ++ +### `crafting_remaining_item(item)` + +returns `null` if the item has no remaining item in the crafting window when used as a crafting ingredient, or an +item name that serves as a replacement after crafting is done. Currently it can only be buckets and glass bottles. + +### `inventory_size(inventory)` + +Returns the size of the inventory for the entity or block in question. Returns null if the block or entity don't +have an inventory. + +
+inventory_size(player()) => 41 +inventory_size('enderchest', player()) => 27 // enderchest +inventory_size('equipment', player()) => 6 // equipment +inventory_size(null, player()) => 41 // default inventory for players + +inventory_size(x,y,z) => 27 // chest +inventory_size(block(pos)) => 5 // hopper + +horse = spawn('horse', x, y, z); +inventory_size(horse); => 2 // default horse inventory +inventory_size('equipment', horse); => 6 // unused horse equipment inventory +inventory_size(null, horse); => 2 // default horse + +creeper = spawn('creeper', x, y, z); +inventory_size(creeper); => 6 // default creeper inventory is equipment since it has no other +inventory_size('equipment', creeper); => 6 // unused horse equipment inventory +inventory_size(null, creeper); => 6 // creeper default is its equipment ++ +### `inventory_has_items(inventory)` + +Returns true, if the inventory is not empty, false if it is empty, and null, if its not an inventory. + +
inventory_has_items(player()) => true + inventory_has_items(x,y,z) => false // empty chest + inventory_has_items(block(pos)) => null // stone ++ +### `inventory_get(inventory, slot)` + +Returns the item in the corresponding inventory slot, or null if slot empty or inventory is invalid. You can use +negative numbers to indicate slots counted from 'the back'. + +
+inventory_get(player(), 0) => null // nothing in first hotbar slot +inventory_get(x,y,z, 5) => ['stone', 1, {id:"minecraft:stone"}] +inventory_get(player(), -1) => ['diamond_pickaxe', 1, {components:{"minecraft:damage":4},id:"minecraft:diamond_pickaxe"}] // slightly damaged diamond pick in the offhand ++ +### `inventory_set(inventory, slot, count, item?, nbt?)` + +Modifies or sets a stack in inventory. specify count 0 to empty the slot. If item is not specified, keeps existing +item, just modifies the count. If item is provided - replaces current item. If nbt is provided - uses the tag to create the item fully +ignoring the item name. If nbt is provided and count is not null, the sets the custom count on the tag from the count parameter. +If count is `null` and item is `null`, an item is entirely defined by the `nbt` parameter. Returns previous stack in that slot. + +
+inventory_set(player(), 0, 0) => ['stone', 64, {id:"minecraft:stone"}] // player had a stack of stone in first hotbar slot +inventory_set(player(), 0, 6) => ['diamond', 64, {id:"minecraft:diamond"}] // changed stack of diamonds in player slot to 6 +inventory_set(player(), 0, 1, 'diamond_axe','{components:{"minecraft:damage":5},id:"minecraft:diamond_axe"}') => null //added slightly damaged diamond axe to first player slot +inventory_set(player(), 0, null, null, '{components:{"minecraft:damage":5},id:"minecraft:diamond_axe"}') => null // same effect as above ++ +### `inventory_find(inventory, item, start_slot?, ), inventory_find(inventory, null, start_slot?)` + +Finds the first slot with a corresponding item in the inventory, or if queried with null: the first empty slot. +Returns slot number if found, or null otherwise. Optional start_slot argument allows to skip all preceeding slots +allowing for efficient (so not slot-by-slot) inventory search for items. + +
+inventory_find(player(), 'stone') => 0 // player has stone in first hotbar slot +inventory_find(player(), null) => null // player's inventory has no empty spot +while( (slot = inventory_find(p, 'diamond', slot)) != null, 41, drop_item(p, slot) ) + // spits all diamonds from player inventory wherever they are +inventory_drop(x,y,z, 0) => 64 // removed and spawned in the world a full stack of items ++ +Throws `unknown_item` if item doesn't exist. + +### `inventory_remove(inventory, item, amount?)` + +Removes amount (defaults to 1) of item from inventory. If the inventory doesn't have the defined amount, nothing +happens, otherwise the given amount of items is removed wherever they are in the inventory. Returns boolean +whether the removal operation was successful. Easiest way to remove a specific item from player inventory +without specifying the slot. + +
+inventory_remove(player(), 'diamond') => 1 // removed diamond from player inventory +inventory_remove(player(), 'diamond', 100) => 0 // player doesn't have 100 diamonds, nothing happened ++ +### `drop_item(inventory, slot, amount?, )` + +Drops the items from indicated inventory slot, like player that Q's an item or villager, that exchanges food. +You can Q items from block inventories as well. default amount is 0 - which is all from the slot. +NOTE: hoppers are quick enough to pick all the queued items from their inventory anyways. +Returns size of the actual dropped items. + +
+inventory_drop(player(), 0, 1) => 1 // Q's one item on the ground +inventory_drop(x,y,z, 0) => 64 // removed and spawned in the world a full stack of items ++ +## Screens + +A screen is a value type used to open screens for a player and interact with them. +For example, this includes the chest inventory gui, the crafting table gui and many more. + +### `create_screen(player, type, name, callback?)` + +Creates and opens a screen for a `player`. + +Available `type`s: + +* `anvil` +* `beacon` +* `blast_furnace` +* `brewing_stand` +* `cartography_table` +* `crafting` +* `enchantment` +* `furnace` +* `generic_3x3` +* `generic_9x1` +* `generic_9x2` +* `generic_9x3` +* `generic_9x4` +* `generic_9x5` +* `generic_9x6` +* `grindstone` +* `hopper` +* `lectern` +* `loom` +* `merchant` +* `shulker_box` +* `smithing` +* `smoker` +* `stonecutter` + +The `name` parameter can be a formatted text and will be displayed at the top of the screen. +Some screens like the lectern or beacon screen don't show it. + +Optionally, a `callback` function can be passed as the fourth argument. +This functions needs to have four parameters: +`_(screen, player, action, data) -> ...` + +The `screen` parameter is the screen value of the screen itself. +`player` is the player who interacted with the screen. +`action` is a string corresponding to the interaction type. +Can be any of the following: + +Slot interactions: + +* `pickup` +* `quick_move` +* `swap` +* `clone` +* `throw` +* `quick_craft` +* `pickup_all` + +The `data` for this interaction is a map, with a `slot` and `button` value. +`slot` is the slot index of the slot that was clicked. +When holding an item in the cursor stack and clicking inside the screen, +but not in a slot, this is -1. +If clicked outside the screen (where it would drop held items), this value is null. +The `button` is the mouse button used to click the slot. + +For the `swap` action, the `button` is the number key 0-8 for a certain hotbar slot. + +For the `quick_craft` action, the `data` also contains the `quick_craft_stage`, +which is either 0 (beginning of quick crafting), 1 (adding item to slot) or 2 (end of quick crafting). + +Other interactions: + +* `button` Pressing a button in certain screens that have button elements (enchantment table, lectern, loom and stonecutter) +The `data` provides a `button`, which is the index of the button that was pressed. +Note that for lecterns, this index can be certain a value above 100, for jumping to a certain page. +This can come from formatted text inside the book, with a `change_page` click event action. + +* `close` Triggers when the screen gets closed. No `data` provided. + +* `select_recipe` When clicking on a recipe in the recipe book. +`data` contains a `recipe`, which is the identifier of the clicked recipe, +as well as `craft_all`, which is a boolean specifying whether +shift was pressed when selecting the recipe. + +* `slot_update` Gets called **after** a slot has changed contents. `data` provides a `slot` and `stack`. + +By returning a string `'cancel'` in the callback function, +the screen interaction can be cancelled. +This doesn't work for the `close` action. + +The `create_screen` function returns a `screen` value, +which can be used in all inventory related functions to access the screens' slots. +The screen inventory covers all slots in the screen and the player inventory. +The last slot is the cursor stack of the screen, +meaning that using `-1` can be used to modify the stack the players' cursor is holding. + +### `close_screen(screen)` + +Closes the screen of the given screen value. +Returns `true` if the screen was closed. +If the screen is already closed, returns `false`. + +### `screen_property(screen, property)` + +### `screen_property(screen, property, value)` + +Queries or modifies a certain `property` of a `screen`. +The `property` is a string with the name of the property. +When called with `screen` and `property` parameter, returns the current value of the property. +When specifying a `value`, +the property will be assigned the new `value` and synced with the client. + +**Options for `property` string:** + +| `property` | Required screen type | Type | Description | +|---|---|---|---| +| `name` | **All** | text | The name of the screen, as specified in the `create_screen()` function. Can only be queried. | +| `open` | **All** | boolean | Returns `true` if the screen is open, `false` otherwise. Can only be queried. | +| `fuel_progress` | furnace/smoker/blast_furnace | number | Current value of the fuel indicator. | +| `max_fuel_progress` | furnace/smoker/blast_furnace | number | Maximum value for the full fuel indicator. | +| `cook_progress` | furnace/smoker/blast_furnace | number | Cooking progress indicator value. | +| `max_cook_progress` | furnace/smoker/blast_furnace | number | Maximum value for the cooking progress indicator. | +| `level_cost` | anvil | number | Displayed level cost for the anvil. | +| `page` | lectern | number | Opened page in the lectern screen. | +| `beacon_level` | beacon | number | The power level of the beacon screen. This affects how many effects under primary power are grayed out. Should be a value between 0-5. | +| `primary_effect` | beacon | number | The effect id of the primary effect. This changes the effect icon on the button on the secondary power side next to the regeneration effect. | +| `secondary_effect` | beacon | number | The effect id of the secondary effect. This seems to change nothing, but it exists. | +| `brew_time` | brewing_stand | number | The brewing time indicator value. This goes from 0 to 400. | +| `brewing_fuel` | brewing_stand | number | The fuel indicator progress. Values range between 0 to 20. | +| `enchantment_power_x` | enchantment | number | The level cost of the shown enchantment. Replace `x` with 1, 2 or 3 (e.g. `enchantment_power_2`) to target the first, second or third enchantment. | +| `enchantment_id_x` | enchantment | number | The id of the enchantment shown (replace `x` with the enchantment slot 1/2/3). | +| `enchantment_level_x` | enchantment | number | The enchantment level of the enchantment. | +| `enchantment_seed` | enchantment | number | The seed of the enchanting screen. This affects the text shown in the standard Galactic alphabet. | +| `banner_pattern` | loom | number | The selected banner pattern inside the loom. | +| `stonecutter_recipe` | stonecutter | number | The selected recipe in the stonecutter. | + +### Screen example scripts + +
+scoreboard_add('counter') +scoreboard_add('lvl','level') ++ +### `scoreboard_remove(objective)` `scoreboard_remove(objective, key)` + +Removes an entire objective, or an entry in the scoreboard associated with the key. +Returns `true` if objective has existed and has been removed, or previous +value of the scoreboard if players score is removed. Returns `null` if objective didn't exist, or a key was missing +for the objective. + +### `scoreboard_display(place, objective)` + +Sets display location for a specified `objective`. If `objective` is `null`, then display is cleared. If objective is invalid, +returns `null`. + +### `scoreboard_property(objective, property)` `scoreboard_property(objective, property, value)` + +Reads a property of an `objective` or sets it to a `value` if specified. Available properties are: + +* `criterion` +* `display_name` (Formatted text supported) +* `display_slot`: When reading, returns a list of slots this objective is displayed in, when modifying, displays the objective in the specified slot +* `render_type`: Either `'integer'` or `'hearts'`, defaults to `'integer'` if invalid value specified + +# Team + +### `team_list()`, `team_list(team)` + +Returns all available teams as a list with no arguments. + +When a `team` is specified, it returns all the players inside that team. If the `team` is invalid, returns `null`. + +### `team_add(team)`, `team_add(team,player)` + +With one argument, creates a new `team` and returns its name if successful, or `null` if team already exists. + + +`team_add('admin')` -> Create a team with the name 'admin' +`team_add('admin','Steve')` -> Joing the player 'Steve' into the team 'admin' + +If a `player` is specified, the player will join the given `team`. Returns `true` if player joined the team, or `false` if nothing changed since the player was already in this team. If the team is invalid, returns `null` + +### `team_remove(team)` + +Removes a `team`. Returns `true` if the team was deleted, or `null` if the team is invalid. + +### `team_leave(player)` + +Removes the `player` from the team he is in. Returns `true` if the player left a team, otherwise `false`. + +`team_leave('Steve')` -> Removes Steve from the team he is currently in +`for(team_list('admin'), team_leave('admin', _))` -> Remove all players from team 'admin' + +### `team_property(team,property,value?)` + +Reads the `property` of the `team` if no `value` is specified. If a `value` is added as a third argument, it sets the `property` to that `value`. + +* `collisionRule` + * Type: String + * Options: always, never, pushOtherTeams, pushOwnTeam + +* `color` + * Type: String + * Options: See [team command](https://minecraft.wiki/w/Commands/team#Arguments) (same strings as `'teamcolor'` [command argument](https://github.com/gnembon/fabric-carpet/blob/master/docs/scarpet/Full.md#command-argument-types) options) + +* `displayName` + * Type: String or FormattedText, when querying returns FormattedText + +* `prefix` + * Type: String or FormattedText, when querying returns FormattedText + +* `suffix` + * Type: String or FormattedText, when querying returns FormattedText + +* `friendlyFire` + * Type: boolean + +* `seeFriendlyInvisibles` + * Type: boolean + +* `nametagVisibility` + * Type: String + * Options: always, never, hideForOtherTeams, hideForOwnTeam + +* `deathMessageVisibility` + * Type: String + * Options: always, never, hideForOtherTeams, hideForOwnTeam + +Examples: + +``` +team_property('admin','color','dark_red') Make the team color for team 'admin' dark red +team_property('admin','prefix',format('r Admin | ')) Set prefix of all players in 'admin' +team_property('admin','display_name','Administrators') Set display name for team 'admin' +team_property('admin','seeFriendlyInvisibles',true) Make all players in 'admin' see other admins even when invisible +team_property('admin','deathMessageVisibility','hideForOtherTeams') Make all players in 'admin' see other admins even when invisible +``` + +## `bossbar()`, `bossbar(id)`, `bossbar(id,property,value?)` + +Manage bossbars just like with the `/bossbar` command. + +Without any arguments, returns a list of all bossbars. + +When an id is specified, creates a bossbar with that `id` and returns the id of the created bossbar. +Bossbar ids need a namespace and a name. If no namespace is specified, it will automatically use `minecraft:`. +In that case you should keep track of the bossbar with the id that `bossbar(id)` returns, because a namespace may be added automatically. +If the id was invalid (for example by having more than one colon), returns `null`. +If the bossbar already exists, returns `false`. + +`bossbar('timer') => 'minecraft:timer'` (Adds the namespace `minecraft:` because none is specified) + +`bossbar('scarpet:test') => 'scarpet:test'` In this case there is already a namespace specified + +`bossbar('foo:bar:baz') => null` Invalid identifier + +`bossbar(id,property)` is used to query the `property` of a bossbar. + +`bossbar(id,property,value)` can modify the `property` of the bossbar to a specified `value`. + +Available properties are: + +* color: can be `'pink'`, `'blue'`, `'red'`, `'green'`, `'yellow'`, `'purple'` or `'white'` + +* style: can be `'progress'`, `'notched_6'`, `'notched_10'`, `'notched_12'` or `'notched_20'` + +* value: value of the bossbar progress + +* max: maximum value of the bossbar progress, by default 100 + +* name: Text to display above the bossbar, supports formatted text + +* visible: whether the bossbar is visible or not + +* players: List of players that can see the bossbar + +* add_player: add a player to the players that can see this bossbar, this can only be used for modifying (`value` must be present) + +* remove: remove this bossbar, no `value` required + +``` +bossbar('script:test','style','notched_12') +bossbar('script:test','value',74) +bossbar('script:test','name',format('rb Test')) -> Change text +bossbar('script:test','visible',false) -> removes visibility, but keeps players +bossbar('script:test','players',player('all')) -> Visible for all players +bossbar('script:test','players',player('Steve')) -> Visible for Steve only +bossbar('script:test','players',null) -> Invalid player, removing all players +bossbar('script:test','add_player',player('Alex')) -> Add Alex to the list of players that can see the bossbar +bossbar('script:test','remove') -> remove bossbar 'script:test' +for(bossbar(),bossbar(_,'remove')) -> remove all bossbars +``` + + + + diff --git a/docs/scarpet/api/ScriptCommand.md b/docs/scarpet/api/ScriptCommand.md new file mode 100644 index 0000000..fb4a8b9 --- /dev/null +++ b/docs/scarpet/api/ScriptCommand.md @@ -0,0 +1,184 @@ +# `/script run` command + +Primary way to input commands. The command executes in the context, position, and dimension of the executing player, +commandblock, etc... The command receives 4 variables, `x`, `y`, `z` and `p` indicating position and +the executing entity of the command. You will receive tab completion suggestions as you type your code suggesting +functions and global variables. It is advisable to use `/execute in ... at ... as ... run script run ...` or similar, +to simulate running commands in a different scope. + +# `/script load / unload
+foo = 1; +//This is a comment +bar = 2; +// This never worked, so I commented it out +// baz = foo() ++ +### `/script load/unload
+/script run example_function(const, phrase, price) -> print(const+' '+phrase+' '+price) +/script invoke example_function pi costs 5 ++ +### `/script invokepoint
+/script outline 0 100 0 -40 60 -40 40 140 40 "x*x+y*y+z*z < 32*32" white_stained_glass replace air +/script outline 0 100 0 -40 60 -40 40 140 40 "x*x+y*y+z*z <= 32*32" white_stained_glass replace air +/script outline 0 100 0 -40 60 -40 40 140 40 "x*x+y*y+z*z < 32.5*32.5" white_stained_glass replace air +/script fill 0 100 0 -40 60 -40 40 140 40 "floor(sqrt(x*x+y*y+z*z)) == 32" white_stained_glass replace air +/script fill 0 100 0 -40 60 -40 40 140 40 "round(sqrt(x*x+y*y+z*z)) == 32" white_stained_glass replace air +/script fill 0 100 0 -40 60 -40 40 140 40 "ceil(sqrt(x*x+y*y+z*z)) == 32" white_stained_glass replace air +/draw sphere 0 100 0 32 white_stained_glass replace air // fluffy ball round(sqrt(x*x+y*y+z*z)-rand(abs(y)))==32 ++ +The last method is the one that world edit is using (part of carpet mod). It turns out that the outline method +with `32.5` radius, fill method with `round` function and draw command are equivalent + +# `script stop/script resume` command + +`/script stop` allows to stop execution of any script currently running that calls the `game_tick()` function which +allows the game loop to regain control of the game and process other commands. This will also make sure that all +current and future programs will stop their execution. Execution of all programs will be prevented +until `/script resume` command is called. + +Lets look at the following example. This is a program computes Fibonacci number in a recursive manner: + +
+fib(n) -> if(n<3, 1, fib(n-1)+fib(n-2) ); fib(8) ++ +That's really bad way of doing it, because the higher number we need to compute the compute requirements will +rise exponentially with `n`. It takes a little over 50 milliseconds to do fib(24), so above one tick, but about +a minute to do fib(40). Calling fib(40) will not only freeze the game, but also you woudn't be able to interrupt +its execution. We can modify the script as follows + +
+fib(n) -> ( game_tick(50); if(n<3, 1, fib(n-1)+fib(n-2) ) ); fib(40) ++ +But this would never finish as such call would finish after `~ 2^40` ticks. To make our computations responsive, +yet able to respond to user interactions, other commands, as well as interrupt execution, we could do the following: + +
+fib(n) -> ( if(n==23, game_tick(50) ); if(n<3, 1, fib(n-1)+fib(n-2) ) ); fib(40) ++ +This would slow down the computation of fib(40) from a minute to two, but allows the game to keep continue running +and be responsive to commands, using about half of each tick to advance the computation. Obviously depending on the +problem, and available hardware, certain things can take more or less time to execute, so portioning of work with +calling `gametick` should be balanced in each case separately + +# `/script download` command + +`/script download
+get([range(10)], 5) => 5 +get([range(10)], -1) => 9 +get([range(10)], 10) => 0 +[range(10)]:93 => 3 +get(player() ~ 'nbt', 'Health') => 20 // inefficient way to get player health, use player() ~ 'health' instead +get({ 'foo' -> 2, 'bar' -> 3, 'baz' -> 4 }, 'bar') => 3 ++ +### `has(container, address, ...), has(lvalue)` + +Similar to `get`, but returns boolean value indicating if the given index / key / path is in the container. +Can be used to determine if `get(...)==null` means the element doesn't exist, or the stored value for this +address is `null`, and is cheaper to run than `get`. + +Like get, it can accept multiple addresses for chains in nested containers. In this case `has(foo:a:b)` is +equivalent to `has(get(foo,a), b)` or `has(foo, a, b)` + +### `delete(container, address, ...), delete(lvalue)` + +Removes specific entry from the container. For the lists - removes the element and shrinks it. For maps, it +removes the key from the map, and for nbt - removes content from a given path. + +Like with the `get` and `has`, `delete` can accept chained addresses, as well as l-value container access, removing +the value from the leaf of the path provided, so `delete(foo, a, b)` is the +same as `delete(get(foo,a),b)` or `delete(foo:a:b)` + +Returns true, if container was changed, false, if it was left unchanged, and null if operation was invalid. + +### `put(container, address, value), put(container, address, value, mode), put(lvalue, value)` + +**Lists** + +Modifies the container by replacing the value under the address with the supplied `value`. For lists, a valid +index is required, but can be negative as well to indicate positions from the end of the list. If `null` is +supplied as the address, it always means - add to the end of the list. + +There are three modes that lists can have items added to them: + +* `replace`(default): Replaces item under given index(address). Doesn't change the size of the array +unless `null` address is used, which is an exception and then it appends to the end +* `insert`: Inserts given element at a specified index, shifting the rest of the array to make space for the item. +Note that index of -1 points to the last element of the list, thus inserting at that position and moving the previous +last element to the new last element position. To insert at the end, use `+=` operator, or `null` address in put +* `extend`: treats the supplied value as an iterable set of values to insert at a given index, extending the list +by this amount of items. Again use `null` address/index to point to the end of the list + +Due to the extra mode parameter, there is no chaining for `put`, but you can still use l-value container access to +indicate container and address, so `put(foo, key, value)` is the same as `put(foo:key, value)` or `foo:key=value` + +Returns true, if container got modified, false otherwise, and null if operation was invalid. + +**Maps** + +For maps there are no modes available (yet, seems there is no reason to). It replaces the value under the supplied +key (address), or sets it if not currently present. + +**NBT Tags** + +The address for nbt values is a valid nbt path that you would use with `/data` command, and tag is any tag that +would be applicable for a given insert operation. Note that to distinguish between proper types (like integer types, +you need to use command notation, i.e. regular ints is `123`, while byte size int would be `123b` and an explicit +string would be `"5"`, so it helps that scarpet uses single quotes in his strings. Unlike for lists and maps, it +returns the number of affected nodes, or 0 if none were affected. + +There are three modes that NBT tags can have items added to them: + +* `replace`(default): Replaces item under given path(address). Removes them first if possible, and then adds given +element to the supplied position. The target path can indicate compound tag keys, lists, or individual elements +of the lists. +* `
+a = [1, 2, 3]; put(a, 1, 4); a => [1, 4, 3] +a = [1, 2, 3]; put(a, null, 4); a => [1, 2, 3, 4] +a = [1, 2, 3]; put(a, 1, 4, 'insert'); a => [1, 4, 2, 3] +a = [1, 2, 3]; put(a, null, [4, 5, 6], 'extend'); a => [1, 2, 3, 4, 5, 6] +a = [1, 2, 3]; put(a, 1, [4, 5, 6], 'extend'); a => [1, 4, 5, 6, 2, 3] +a = [[0,0,0],[0,0,0],[0,0,0]]; put(a:1, 1, 1); a => [[0, 0, 0], [0, 1, 0], [0, 0, 0]] +a = {1,2,3,4}; put(a, 5, null); a => {1: null, 2: null, 3: null, 4: null, 5: null} +tag = nbt('{}'); put(tag, 'BlockData.Properties', '[1,2,3,4]'); tag => {BlockData:{Properties:[1,2,3,4]}} +tag = nbt('{a:[{lvl:3},{lvl:5},{lvl:2}]}'); put(tag, 'a[].lvl', 1); tag => {a:[{lvl:1},{lvl:1},{lvl:1}]} +tag = nbt('{a:[{lvl:[1,2,3]},{lvl:[3,2,1]},{lvl:[4,5,6]}]}'); put(tag, 'a[].lvl', 1, 2); tag + => {a:[{lvl:[1,2,1,3]},{lvl:[3,2,1,1]},{lvl:[4,5,1,6]}]} +tag = nbt('{a:[{lvl:[1,2,3]},{lvl:[3,2,1]},{lvl:[4,5,6]}]}'); put(tag, 'a[].lvl[1]', 1); tag + => {a:[{lvl:[1,1,3]},{lvl:[3,1,1]},{lvl:[4,1,6]}]} ++ +## List operations + +### `[value, ...?]`,`[iterator]`,`l(value, ...?)`, `l(iterator)` + +Creates a list of values of the expressions passed as parameters. It can be used as an L-value and if all +elements are variables, you coujld use it to return multiple results from one function call, if that +function returns a list of results with the same size as the `[]` call uses. In case there is only one +argument and it is an iterator (vanilla expression specification has `range`, but Minecraft API implements +a bunch of them, like `diamond`), it will convert it to a proper list. Iterators can only be used in high order +functions, and are treated as empty lists, unless unrolled with `[]`. + +Internally, `[elem, ...]`(list syntax) and `l(elem, ...)`(function syntax) are equivalent. `[]` is simply translated to +`l()` in the scarpet preprocessing stage. This means that internally the code has always expression syntax despite `[]` +not using different kinds of brackets and not being proper operators. This means that `l(]` and `[)` are also valid +although not recommended as they will make your code far less readable. + +
+l(1,2,'foo') <=> [1, 2, 'foo'] +l() <=> [] (empty list) +[range(10)] => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +[1, 2] = [3, 4] => Error: l is not a variable +[foo, bar] = [3, 4]; foo==3 && bar==4 => 1 +[foo, bar, baz] = [2, 4, 6]; [min(foo, bar), baz] = [3, 5]; [foo, bar, baz] => [3, 4, 5] ++ +In the last example `[min(foo, bar), baz]` creates a valid L-value, as `min(foo, bar)` finds the lower of the +variables (in this case `foo`) creating a valid assignable L-list of `[foo, baz]`, and these values +will be assigned new values + +### `join(delim, list), join(delim, values ...)` + +Returns a string that contains joined elements of the list, iterator, or all values, +concatenated with `delim` delimiter + +
+join('-',range(10)) => 0-1-2-3-4-5-6-7-8-9 +join('-','foo') => foo +join('-', 'foo', 'bar') => foo-bar ++ +### `split(delim?, expr)` + +Splits a string under `expr` by `delim` which can be a regular expression. If no delimiter is specified, it splits +by characters. + +If `expr` is a list, it will split the list into multiple sublists by the element (s) which equal `delim`, or which equal the empty string +in case no delimiter is specified. + +Splitting a `null` value will return an empty list. + +
+split('foo') => [f, o, o] +split('','foo') => [f, o, o] +split('.','foo.bar') => [] +split('\\.','foo.bar') => [foo, bar] +split(1,[2,5,1,2,3,1,5,6]) => [[2,5],[2,3],[5,6]] +split(1,[1,2,3,1,4,5,1] => [[], [2,3], [4,5], []] +split(null) => [] ++ +### `slice(expr, from, to?)` + +extracts a substring, or sublist (based on the type of the result of the expression under expr with +starting index of `from`, and ending at `to` if provided, or the end, if omitted. Can use negative indices to +indicate counting form the back of the list, so `-1 <=> length(_)`. + +Special case is made for iterators (`range`, `rect` etc), which does require non-negative indices (negative `from` is treated as +`0`, and negative `to` as `inf`), but allows retrieving parts of the sequence and ignore +other parts. In that case consecutive calls to `slice` will refer to index `0` the current iteration position since iterators +cannot go back nor track where they are in the sequence (see examples). + +
+slice([0,1,2,3,4,5], 1, 3) => [1, 2] +slice('foobar', 0, 1) => 'f' +slice('foobar', 3) => 'bar' +slice(range(10), 3, 5) => [3, 4] +slice(range(10), 5) => [5, 6, 7, 8, 9] +r = range(100); [slice(r, 5, 7), slice(r, 1, 3)] => [[5, 6], [8, 9]] ++ +### `sort(list), sort(values ...)` + +Sorts in the default sortographical order either all arguments, or a list if its the only argument. +It returns a new sorted list, not affecting the list passed to the argument + +
sort(3,2,1) => [1, 2, 3] +sort('a',3,11,1) => [1, 3, 11, 'a'] +list = [4,3,2,1]; sort(list) => [1, 2, 3, 4] ++ +### `sort_key(list, key_expr)` + +Sorts a copy of the list in the order or keys as defined by the `key_expr` for each element + +
+sort_key([1,3,2],_) => [1, 2, 3] +sort_key([1,3,2],-_) => [3, 2, 1] +sort_key([range(10)],rand(1)) => [1, 0, 9, 6, 8, 2, 4, 5, 7, 3] +sort_key([range(20)],str(_)) => [0, 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9] ++ +### `range(to), range(from, to), range(from, to, step)` + +Creates a range of numbers from `from`, no greater/larger than `to`. The `step` parameter dictates not only the +increment size, but also direction (can be negative). The returned value is not a proper list, just the iterator +but if for whatever reason you need a proper list with all items evaluated, use `[range(to)]`. +Primarily to be used in higher order functions + +
+range(10) => [...] +[range(10)] => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +map(range(10),_*_) => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] +reduce(range(10),_a+_, 0) => 45 +range(5,10) => [5, 6, 7, 8, 9] +range(20, 10, -2) => [20, 18, 16, 14, 12] +range(-0.3, 0.3, 0.1) => [-0.3, -0.2, -0.1, 0, 0.1, 0.2] +range(0.3, -0.3, -0.1) => [0.3, 0.2, 0.1, -0, -0.1, -0.2] ++ +## Map operations + +Scarpet supports map structures, aka hashmaps, dicts etc. Map structure can also be used, with `null` values as sets. +Apart from container access functions, (`. , get, put, has, delete`), the following functions: + +### `{values, ...}`,`{iterator}`,`{key -> value, ...}`,`m(values, ...)`, `m(iterator)`, `m(l(key, value), ...))` + +creates and initializes a map with supplied keys, and values. If the arguments contains a flat list, these are all +treated as keys with no value, same goes with the iterator - creates a map that behaves like a set. If the +arguments is a list of lists, they have to have two elements each, and then first is a key, and second, a value + +In map creation context (directly inside `{}` or `m{}` call), `->` operator acts like a pair constructor for simpler +syntax providing key value pairs, so the invocation to `{foo -> bar, baz -> quux}` is equivalent to +`{[foo, bar], [baz, quux]}`, which is equivalent to somewhat older, but more traditional functional form of +`m(l(foo, bar),l(baz, quuz))`. + +Internally, `{?}`(list syntax) and `m(?)`(function syntax) are equivalent. `{}` is simply translated to +`m()` in the scarpet preprocessing stage. This means that internally the code has always expression syntax despite `{}` +not using different kinds of brackets and not being proper operators. This means that `m(}` and `{)` are also valid +although not recommended as they will make your code far less readable. + +When converting map value to string, `':'` is used as a key-value separator due to tentative compatibility with NBT +notation, meaning in simpler cases maps can be converted to NBT parsable string by calling `str()`. This however +does not guarantee a parsable output. To properly convert to NBT value, use `encode_nbt()`. + +
+{1, 2, 'foo'} => {1: null, 2: null, foo: null} +m() <=> {} (empty map) +{range(10)} => {0: null, 1: null, 2: null, 3: null, 4: null, 5: null, 6: null, 7: null, 8: null, 9: null} +m(l(1, 2), l(3, 4)) <=> {1 -> 2, 3 -> 4} => {1: 2, 3: 4} +reduce(range(10), put(_a, _, _*_); _a, {}) + => {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81} ++ +### `keys(map), values(map), pairs(map)` + +Returns full lists of keys, values and key-value pairs (2-element lists) for all the entries in the map diff --git a/docs/scarpet/language/FunctionsAndControlFlow.md b/docs/scarpet/language/FunctionsAndControlFlow.md new file mode 100644 index 0000000..f42017a --- /dev/null +++ b/docs/scarpet/language/FunctionsAndControlFlow.md @@ -0,0 +1,318 @@ +# User-defined functions and program control flow + +## Writing programs with more than 1 line + +### Operator `;`, `then(...)` + +To effectively write programs that have more than one line, a programmer needs way to specify a sequence of commands +that execute one after another. In `scarpet` this can be achieved with `;`. Its an operator, and by separating +statements with semicolons. And since whitespaces and +`$` (commandline visible newline separator) +sign are all treats as whitespaces, how you layout your code doesn't matter, as long as it is readable to everyone involved. + +
+expr; +expr; +expr; +expr ++ +Notice that the last expression is not followed by a semicolon. Since instruction separation is functional +in `scarpet`, and not barely an instruction delimiter, terminating the code with a dangling operator wouldn't +be valid. Having said that, since many programming languages don't care about the number of op terminators +programmers use, carpet preprocessor will remove all unnecessary semicolons from scripts when compiled. + +In general `expr; expr; expr; expr` is equivalent to `(((expr ; expr) ; expr) ; expr)` or `then(expr, expr, expr, expr)`. + +Result of the evaluated expression is the same as the result of the second expression, but first expression +is also evaluated for side-effects + +
+expr1 ; expr2 => expr2 // with expr1 as a side-effect ++ +## Global variables + +All defined functions are compiled, stored persistently, and available globally within the app. +Functions can only be undefined via call to `undef('fun')`, which would erase global entry for function `fun`. +Since all variables have local scope inside each function, or each command script, + global variables is a way to share the global state. + +Any variable that is used with a name that starts with `'global_'` will be stored and accessible globally, not only +inside the current scope. If used directly in the chat window with the default app, it will persist across calls to `/script` +function. Like functions, which are global, global variables can only be undefined via `undef`. + +For apps running in `'global'` scope - all players will share the same global variables and defined functions, +and with `player` scope, each player hosts its own state for each app, so function and global_variables are distinct. + + +
+/script run a() -> global_list+=1; global_list = [1,2,3]; a(); a(); global_list // => [1, 2, 3, 1, 1] +/script run a(); a(); global_list // => [1, 2, 3, 1, 1, 1, 1] ++ +### `Operator ->` + +`->` operator has two uses - as a function definition operator and key-value initializer for maps. + +To organize code better than a flat sequence of operations, one can define functions. Definition is correct +if has the following form + +
+fun(args, ...) -> expr ++ +Where `fun(args, ...)` is a function signature indicating function name, number of arguments, and their names, +and expr is an expression (can be complex) that is evaluated when `fun` is called. Names in the signature don't +need to be used anywhere else, other occurrences of these names will be masked in this function scope. Function +call creates new scope for variables inside `expr`, so all non-global variables are not visible from the caller +scope. All parameters are passed by value to the new scope, including lists and other containers, however their +copy will be shallow. + +The function returns itself as a first class object, which means it can be used to call it later with the `call` function + +Using `_` as the function name creates anonymous function, so each time `_` function is defined, it will be given +a unique name, which you can pass somewhere else to get this function `call`ed. Anonymous functions can only be called +by their value and `call` method. + +
+a(lst) -> lst+=1; list = [1,2,3]; a(list); a(list); list // => [1,2,3] ++ +In case the inner function wants to operate and modify larger objects, lists from the outer scope, but not global, +it needs to use `outer` function in function signature. + +in map construction context (directly in `m()` or `{}`), the `->` operator has a different function by converting its +arguments to a tuple which is used by map constructor as a key-value pair: + +
+{ 'foo' -> 'bar' } => {l('foo', 'bar')} ++ +This means that it is not possible to define literally a set of inline function, however a set of functions can still +be created by adding elements to an empty set, and building it this way. That's a tradeoff for having a cool map initializer. + +### `outer(arg)` + +`outer` function can only be used in the function signature, and it will cause an error everywhere else. It +saves the value of that variable from the outer scope and allows its use in the inner scope. This is a similar +behaviour to using outer variables in lambda function definitions from Java, except here you have to specify +which variables you want to use, and borrow + +This mechanism can be used to use static mutable objects without the need of using `global_...` variables + +
+list = [1,2,3]; a(outer(list)) -> list+=1; a(); a(); list // => [1,2,3,1,1] ++ +The return value of a function is the value of the last expression. This as the same effect as using outer or +global lists, but is more expensive + +
+a(lst) -> lst+=1; list = [1,2,3]; list=a(list); list=a(list); list // => [1,2,3,1,1] ++ +Ability to combine more statements into one expression, with functions, passing parameters, and global and outer +scoping allow to organize even larger scripts + +### `Operator ...` + +Defines a function argument to represent a variable length argument list of whatever arguments are left +from the argument call list, also known as a varargs. There can be only one defined vararg argument in a function signature. +Technically, it doesn't matter where is it, but it looks best at the butt side of it. + +
+foo(a, b, c) -> ... # fixed argument function, call foo(1, 2, 3) +foo(a, b, ... c) -> ... # c is now representing the variable argument part + foo(1, 2) # a:1, b:2, c:[] + foo(1, 2, 3) # a:1, b:2, c:[3] + foo(1, 2, 3, 4) # a:1, b:2, c:[3, 4] +foo(... x) -> ... # all arguments for foo are included in the list + + ++ +### `import(module_name, ? symbols ...)` + +Imports symbols from other apps and libraries into the current one: global variables or functions, allowing to use +them in the current app. This includes other symbols imported by these modules. Scarpet supports circular dependencies, +but if symbols are used directly in the module body rather than functions, it may not be able to retrieve them. + +Returns full list of available symbols that could be imported from this module, which can be used to debug import +issues, and list contents of libraries. + +You can load and import functions from dependencies in a remote app store's source specified in your config's `libraries` block, but make sure +to place your config _before_ the import in order to allow the remote dependency to be downloaded (currently, app resources are only downloaded +when using the `/carpet download` command). + +### `call(function, ? args ...)` + +calls a user defined function with specified arguments. It is equivalent to calling `function(args...)` directly +except you can use it with function value, or name instead. This means you can pass functions to other user defined +functions as arguments and call them with `call` internally. Since function definitions return the defined +function, they can be defined in place as anonymous functions. + +#### Passing function references to other modules of your application + +In case a function is defined by its name, Scarpet will attempt to resolve its definition in the given module and its imports, +meaning if the call is in a imported library, and not in the main module of your app, and that function is not visible from the +library perspective, but in the app, it won't be call-able. In case you pass a function name to a separate module in your app, +it should import back that method from the main module for visibility. + +Check an example of a problematic code of a library that expects a function value as a passed argument and how it is called in +the parent app: +``` +//app.sc +import('lib', 'callme'); +foo(x) -> x*x; +test() -> callme('foo' , 5); +``` +``` +//lib.scl +callme(fun, arg) -> call(fun, arg); +``` + +In this case `'foo'` will fail to dereference in `lib` as it is not visible by name. In tightly coupled modules, where `lib` is just +a component of your `app` you can use circular import to acknowledge the symbol from the other module (pretty much like +imports in Java classes), and that solves the issue but makes the library dependent on the main app: +``` +//lib.scl +import('app','foo'); +callme(fun, arg) -> call(fun, arg); +``` +You can circumvent that issue by explicitly dereferencing the local function where it is used as a lambda argument created +in the module in which the requested function is visible: +``` +//app.sc +import('lib', 'callme'); +foo(x) -> x*x; +test() -> callme(_(x) -> foo(x), 5); +``` +``` +//lib.scl +callme(fun, arg) -> call(fun, arg); +``` +Or by passing an explicit reference to the function, instead of calling it by name: +``` +//app.sc +import('lib', 'callme'); +global_foohandler = (foo(x) -> x*x); +test() -> callme(global_foohandler, 5); +``` + +Little technical note: the use of `_` in expression passed to built in functions is much more efficient due to not +creating new call stacks for each invoked function, but anonymous functions is the only mechanism available for +programmers with their own lambda arguments + +
+my_map(list, function) -> map(list, call(function, _)); +my_map([1,2,3], _(x) -> x*x); // => [1,4,9] +profile_expr(my_map([1,2,3], _(x) -> x*x)); // => ~32000 +sq(x) -> x*x; profile_expr(my_map([1,2,3], 'sq')); // => ~36000 +sq = (_(x) -> x*x); profile_expr(my_map([1,2,3], sq)); // => ~36000 +profile_expr(map([1,2,3], _*_)); // => ~80000 ++ +## Control flow + +### `return(expr?)` + +Sometimes its convenient to break the organized control flow, or it is not practical to pass the final result value of +a function to the last statement, in this case a return statement can be used + +If no argument is provided - returns null value. + +
+def() -> ( + expr1; + expr2; + return(expr3); // function terminates returning expr3 + expr4; // skipped + expr5 // skipped +) ++ +In general its cheaper to leave the last expression as a return value, rather than calling +returns everywhere, but it would often lead to a messy code. + +### `exit(expr?)` + +It terminates entire program passing `expr` as the result of the program execution, or null if omitted. + +### `try(expr)` `try(expr, user_catch_expr)` `try(expr, type, catch_expr, type?, catch_expr?, ...)` + +`try` evaluates expression, allowing capturing exceptions that would be thrown inside `expr` statement. The exceptions can be +thrown explicitly using `throw()` or internally by scarpet where code is correct but detects illegal state. The 2-argument form +catches only user-thrown exceptions and one argument call `try(expr)` is equivalent to `try(expr, null)`, +or `try(expr, 'user_exception', null)`. If multiple `type-catch` pairs are defined, the execution terminates on the first +applicable type for the exception thrown. Therefore, even if the caught exception matches multiple filters, only +the first matching block will be executed. + +Catch expressions are evaluated with +`_` set to the value associated with the exception and `_trace` set to contain details about point of error (token, and line and +column positions), call stack and local +variables at the time of failure. The `type` will catch any exception of that type and any subtype of this type. + + +You can use `try` mechanism to exit from large portion of a convoluted call stack and continue program execution, although catching +exceptions is typically much more expensive comparing to not throwing them. + +The `try` function allows you to catch some scarpet exceptions for cases covering invalid data, like invalid +blocks, biomes, dimensions and other things, that may have been modified by datapacks, resourcepacks or other mods, +or when an error is outside of the programmers scope, such as problems when reading or decoding files. + +This is the hierarchy of the exceptions that could be thrown/caught in the with the `try` function: +- `exception`: This is the base exception. Catching `'exception'` allows to catch everything that can be caught, +but like everywhere else, doing that sounds like a bad idea. + - `value_exception`: This is the parent for any exception that occurs due to an + incorrect argument value provided to a built-in function + - `unknown_item`, `unknown_block`, `unknown_biome`, `unknown_sound`, `unknown_particle`, + `unknown_poi_type`, `unknown_dimension`, `unknown_structure`, `unknown_criterion`: Specific + errors thrown when a specified internal name does not exist or is invalid. + - `io_exception`: This is the parent for any exception that occurs due to an error handling external data. + - `nbt_error`: Incorrect input/output NBT file. + - `json_error`: Incorrect input/output JSON data. + - `b64_error`: Incorrect input/output b64 (base 64) string + - `user_exception`: Exception thrown by default with `throw` function. + +Synopsis: +
+inner_call() -> +( + aaa = 'booyah'; + try( + for (range(10), item_tags('stick'+_*'k')); + , + print(_trace) // not caught, only catching user_exceptions + ) +); + +outer_call() -> +( + try( + inner_call() + , 'exception', // catching everything + print(_trace) + ) +); ++Producing: +``` +{stack: [[
+ c_for(x=0, x<10, x+=1, + c_for(y=0, y<10, y+=1, + print(str('%d * %d = %d', x, y, x*y)) + ) + ) ++ +### `for(list,expr(_,_i))` + +Evaluates expression over list of items from the `list`. Supplies `_`(value) and `_i`(iteration number) to the `expr`. + +Returns the number of times `expr` was successful. Uses `continue` and `break` argument in place of the returned +value from the `expr`(if supplied), to determine if the iteration was successful. + +
+check_prime(n) -> !first( range(2, sqrt(n)+1), !(n % _) ); +for(range(1000000,1100000),check_prime(_)) => 7216 ++ +From which we can learn that there is 7216 primes between 1M and 1.1M + +### `while(cond, expr)`, `while(cond, limit, expr)` + +Evaluates expression `expr` repeatedly until condition `cond` becomes false, but not more than `limit` times (if limit is specified). +Returns the result of the last `expr` evaluation, or `null` if nothing was successful. Both `expr` and `cond` will +received a bound variable `_` indicating current iteration, so its a number. + +
+while(a<100,a=_*_) => 100 // loop stopped at condition +while(a<100,10,a=_*_) => 81 // loop exhausted via limit +while(a<100,20,a=_*_) => 100 // loop stopped at condition, but a has already been assigned +while(_*_<100,20,a=_*_) => 81 // loop stopped at condition, before a was assigned a value ++ +### `loop(num,expr(_),exit(_)?)` + +Evaluates expression `expr`, `num` number of times. code>expr receives `_` system variable indicating the iteration. + +
+loop(5, game_tick()) => repeat tick 5 times +list = []; loop(5, x = _; loop(5, list += [x, _] ) ); list + // double loop, produces: [[0, 0], [0, 1], [0, 2], [0, 3], [0, 4], [1, 0], [1, 1], ... , [4, 2], [4, 3], [4, 4]] ++ +In this small example we will search for first 10 primes, apparently including 0: + +
+check_prime(n) -> !first( range(2, sqrt(n)+1), !(n % _) ); +primes = []; +loop(10000, if(check_prime(_), primes += _ ; if (length(primes) >= 10, break()))); +primes +// outputs: [0, 1, 2, 3, 5, 7, 11, 13, 17, 19] ++ +## Higher Order Functions + +### `map(list,expr(_,_i))` + +Converts a `list` of values, to another list where each value is result of an expression `v = expr(_, _i)` +where `_` is passed as each element of the list, and `_i` is the index of such element. If `break` is called the +map returns whatever collected thus far. If `continue` and `break` are used with supplied argument, it is used in +place of the resulting map element, otherwise current element is skipped. + +
+map(range(10), _*_) => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] +map(player('*'), _+' is stoopid') [gnembon is stoopid, herobrine is stoopid] ++ +### `filter(list,expr(_,_i))` + +filters `list` elements returning only these that return positive result of the `expr`. With `break` and `continue` +statements, the supplied value can be used as a boolean check instead. + +
+filter(range(100), !(_%5), _*_>1000) => [0, 5, 10, 15, 20, 25, 30] +map(filter(entity_list('*'),_=='Witch'), query(_,'pos') ) => [[1082.5, 57, 1243.5]] ++ +### `first(list,expr(_,_i))` + +Finds and returns the first item in the list that satisfies `expr`. It sets `_` for current element value, +and `_i` for index of that element. `break` can be called inside the iteration code, using its argument value +instead of the current item. `continue` has no sense and cannot be called inside `first` call. + +
+first(range(1000,10000), n=_; !first( range(2, sqrt(n)+1), !(n % _) ) ) => 1009 // first prime after 1000 ++ +Notice in the example above, that we needed to rename the outer `_` to be albe to use in in the inner `first` call + +### `all(list,expr(_,_i))` + +Returns `true` if all elements on the list satisfy the condition. Its roughly equivalent +to `all(list,expr) <=> for(list,expr)==length(list)`. `expr` also receives bound `_` and `_i` variables. `break` +and `continue` have no sense and cannot be used inside of `expr` body. + +
+all([1,2,3], check_prime(_)) => true +all(neighbours(x,y,z), _=='stone') => true // if all neighbours of [x, y, z] are stone +map(filter(rect(0,4,0,1000,0,1000), [x,y,z]=pos(_); all(rect(x,y,z,1,0,1),_=='bedrock') ), pos(_) ) + => [[-298, 4, -703], [-287, 4, -156], [-269, 4, 104], [242, 4, 250], [-159, 4, 335], [-208, 4, 416], [-510, 4, 546], [376, 4, 806]] + // find all 3x3 bedrock structures in the top bedrock layer +map( filter( rect(0,4,0,1000,1,1000,1000,0,1000), [x,y,z]=pos(_); + all(rect(x,y,z,1,0,1),_=='bedrock') && for(rect(x,y-1,z,1,1,1,1,0,1),_=='bedrock')<8), + pos(_) ) => [[343, 3, -642], [153, 3, -285], [674, 3, 167], [-710, 3, 398]] + // ditto, but requiring at most 7 bedrock block in the 18 blocks below them ++ +### `reduce(list,expr(_a,_,_i), initial)` + +Applies `expr` for each element of the list and saves the result in `_a` accumulator. Consecutive calls to `expr` +can access that value to apply more values. You also need to specify the initial value to apply for the +accumulator. `break` can be used to terminate reduction prematurely. If a value is provided to `break` or `continue`, +it will be used from now on as a new value for the accumulator. + +
+reduce([1,2,3,4],_a+_,0) => 10 +reduce([1,2,3,4],_a*_,1) => 24 ++ diff --git a/docs/scarpet/language/Math.md b/docs/scarpet/language/Math.md new file mode 100644 index 0000000..3bb65ee --- /dev/null +++ b/docs/scarpet/language/Math.md @@ -0,0 +1,121 @@ +# Arithmetic operations + +## Basic Arithmetic Functions + +There is bunch of them - they require a number and spit out a number, doing what you would expect them to do. + +### `fact(n)` + +Factorial of a number, a.k.a `n!`, just not in `scarpet`. Gets big... quick... Therefore, values larger +than `fact(20)` will not return the exact value, but a value with 'double-float' precision. + +### `sqrt(n)` + +Square root (not 'a squirt') of a number. For other fancy roots, use `^`, math and yo noggin. Imagine square roots on a tree... + +### `abs(n)` + +Absolut value. + +### `round(n)` + +Closest integer value. Did you know the earth is also round? + +### `floor(n)` + +Highest integer that is still no larger then `n`. Insert a floor pun here. + +### `ceil(n)` + +First lucky integer that is not smaller than `n`. As you would expect, ceiling is typically right above the floor. + +### `ln(n)` + +Natural logarithm of `n`. Naturally. + +### `ln1p(n)` + +Natural logarithm of `n+1`. Very optimistic. + +### `log10(n)` + +Decimal logarithm of `n`. Its ceiling is the length of its floor. + +### `log(n)` + +Binary logarithm of `n`. Finally, a proper one, not like the previous 11. + +### `log1p(n)` + +Binary logarithm of `n+1`. Also always positive. + +### `mandelbrot(a, b, limit)` + +Computes the value of the mandelbrot set, for set `a` and `b` spot. Spot the beetle. Why not. + +### `min(arg, ...), min(list), max(arg, ...), max(list)` + +Compute minimum or maximum of supplied arguments assuming default sorthoraphical order. +In case you are missing `argmax`, just use `a ~ max(a)`, little less efficient, but still fun. + +Interesting bit - `min` and `max` don't remove variable associations from arguments, which means can be used as +LHS of assignments (obvious case), or argument spec in function definitions (far less obvious). + +
+a = 1; b = 2; min(a,b) = 3; [a,b] => [3, 2] +a = 1; b = 2; fun(x, min(a,b)) -> [a,b]; fun(3,5) => [5, 0] ++ +Absolutely no idea, how the latter might be useful in practice. But since it compiles, can ship it. + +### `relu(n)` + +Linear rectifier of `n`. 0 below 0, n above. Why not. `max(0,n)` with less moral repercussions. + +## Trigonometric / Geometric Functions + +### `sin(x)` + +### `cos(x)` + +### `tan(x)` + +### `asin(x)` + +### `acos(x)` + +### `atan(x)` + +### `atan2(x,y)` + +### `sinh(x)` + +### `cosh(x)` + +### `tanh(x)` + +### `sec(x)` + +### `csc(x)` + +### `sech(x)` + +### `csch(x)` + +### `cot(x)` + +### `acot(x)` + +### `coth(x)` + +### `asinh(x)` + +### `acosh(x)` + +### `atanh(x)` + +### `rad(deg)` + +### `deg(rad)` + +Use as you wish diff --git a/docs/scarpet/language/Operators.md b/docs/scarpet/language/Operators.md new file mode 100644 index 0000000..c13921f --- /dev/null +++ b/docs/scarpet/language/Operators.md @@ -0,0 +1,326 @@ +# Operators + +There is a number of operators you can use inside the expressions. Those could be considered generic type operators +that apply to most data types. They also follow standard operator precedence, i.e. `2+2*2` is understood +as `2+(2*2)`, not `(2+2)*2`, otherwise they are applied from left to right, i.e. `2+4-3` is interpreted +as `(2+4)-3`, which in case of numbers doesn't matter, but since `scarpet` allows for mixing all value types +the associativity would matter, and may lead to unintended effects: + +Operators can be unary - with one argument prefixed by the operator (like `-`, `!`, `...`), "practically binary" (that +clearly have left and right operands, like assignment `=` operator), and "technically binary" (all binary operators have left and +right hand, but can be frequently chained together, like `1+2+3`). All "technically binary" operators (where chaining makes sense) +have their functional counterparts, e.g. `1+2+3` is equivalent to `sum(1, 2, 3)`. Functional and operatoral forms are directly +equivalent - they actually will result in the same code as scarpet will optimize long operator chains into their optimized functional forms. + +Important operator is function definition `->` operator. It will be covered +in [User Defined Functions and Program Control Flow](docs/scarpet/language/FunctionsAndControlFlow.md) + +
+'123'+4-2 => ('123'+4)-2 => '1234'-2 => '134' +'123'+(4-2) => '123'+2 => '1232' +3*'foo' => 'foofoofoo' +1357-5 => 1352 +1357-'5' => 137 +3*'foo'-'o' => 'fff' +[1,3,5]+7 => [8,10,12] ++ +As you can see, values can behave differently when mixed with other types in the same expression. +In case values are of the same types, the result tends to be obvious, but `Scarpet` tries to make sense of whatever +it has to deal with + +## Operator Precedence + +Here is the complete list of operators in `scarpet` including control flow operators. Note, that commas and brackets +are not technically operators, but part of the language, even if they look like them: + +* Match, Get `~ :` +* Unary `+ - ! ...` +* Exponent `^` +* Multiplication `* / %` +* Addition `+ -` +* Comparison `> >= <= <` +* Equality `== !=` +* Logical And`&&` +* Logical Or `||` +* Assignment `= += <>` +* Definition `->` +* Next statement`;` +* Comma `,` +* Bracket `( )` + +### `Get, Accessor Operator :` + +Operator version of the `get(...)` function to access elements of lists, maps, and potentially other containers +(i.e. NBTs). It is important to distinguish from `~` operator, which is a matching operator, which is expected to +perform some extra computations to retrieve the result, while `:` should be straightforward and immediate, and +the source object should behave like a container and support full container API, +meaning `get(...)`, `put(...)`, `delete(...)`, and `has(...)` functions + +For certain operators and functions (get, put, delete, has, =, +=) objects can use `:` annotated fields as l-values, +meaning construct like `foo:0 = 5`, would act like `put(foo, 0, 5)`, rather than `get(foo, 0) = 5`, +which would result in an error. + +TODO: add more information about l-value behaviour. + +### `Matching Operator ~` + +This operator should be understood as 'matches', 'contains', 'is_in', or 'find me some stuff about something else. +For strings it matches the right operand as a regular expression to the left, returning: + - `null` if there is no match + - matched phrase if no grouping is applied + - matched element if one group is applied + - list of matches if more than one grouping is applied + +This can be used to extract information from unparsed nbt's in a more convoluted way (use `get(...)` for +more appropriate way of doing it). For lists it checks if an element is in the list, and returns the +index of that element, or `null` if no such element was found, especially that the use of `first(...)` +function will not return the index. Currently it doesn't have any special behaviour for numbers - it checks for +existence of characters in string representation of the left operand with respect of the regular expression on +the right hand side. + +In Minecraft API portion `entity ~ feature` is a shortcode for `query(entity,feature)` for queries that do not take +any extra arguments. + +
+[1,2,3] ~ 2 => 1 +[1,2,3] ~ 4 => null + +'foobar' ~ 'baz' => null +'foobar' ~ '.b' => 'ob' +'foobar' ~ '(.)b' => 'o' +'foobar' ~ '((.)b)' => ['ob', 'o'] +'foobar' ~ '((.)(b))' => ['ob', 'o', 'b'] +'foobar' ~ '(?:(.)(?:b))' => 'o' + +player('*') ~ 'gnembon' // null unless player gnembon is logged in (better to use player('gnembon') instead +p ~ 'sneaking' // if p is an entity returns whether p is sneaking ++ +Or a longer example of an ineffective way to searching for a squid + +
+entities = entities_area('all',x,y,z,100,10,100); +sid = entities ~ 'Squid'; +if(sid != null, run('execute as '+query(get(entities,sid),'id')+' run say I am here '+query(get(entities,sid),'pos') ) ) ++ +Or an example to find if a player has specific enchantment on a held axe (either hand) and get its level +(not using proper NBTs query support via `get(...)`): + +
+global_get_enchantment(p, ench) -> ( +$ for(['mainhand','offhand'], +$ holds = query(p, 'holds', _); +$ if( holds, +$ [what, count, nbt] = holds; +$ if( what ~ '_axe' && nbt ~ ench, +$ lvl = max(lvl, number(nbt ~ '(?<=lvl:)\\d') ) +$ ) +$ ) +$ ); +$ lvl +$); +/script run global_get_enchantment(player(), 'sharpness') ++ +### Basic Arithmetic Operators `+`, `sum(...)`, `-`, `difference(...)`, `*`, `product(...)`, `/`, `quotient(...)` + +Allows to add the results of two expressions. If the operands resolve to numbers, the result is arithmetic operation. +In case of strings, adding or subtracting from a string results in string concatenation and removal of substrings +from that string. Multiplication of strings and numbers results in repeating the string N times and division results +in taking the first k'th part of the string, so that `str*n/n ~ str` + +In case first operand is a list, either it +results in a new list with all elements modified one by one with the other operand, or if the operand is a list +with the same number of items - element-wise addition/subtraction. This prioritize treating lists as value containers +to lists treated as vectors. + +Addition with maps (`{}` or `m()`) results in a new map with keys from both maps added, if both operands are maps, +adding elements of the right argument to the keys, of left map, or just adding the right value as a new key +in the output map. + +Functional forms of `-` and `/` have less intuitive multi-nary interpretation, but they might be useful in some situations. +`x-y-z` resolves to `difference(x, y, z)`. + +`/` always produces a properly accurate result, fully reversible with `*` operator. To obtain a integer 'div' result, use +`floor(x/y)`. + +Examples: + +
+2+3 => 5 +'foo'+3+2 => 'foo32' +'foo'+(3+2) => 'foo5' +3+2+'bar' => '5bar' +'foo'*3 => 'foofoofoo' +'foofoofoo' / 3 => 'foo' +'foofoofoo'-'o' => 'fff' +[1,2,3]+1 => [2,3,4] +b = [100,63,100]; b+[10,0,10] => [110,63,110] +{'a' -> 1} + {'b' -> 2} => {'a' -> 1, 'b' -> 2} ++ +### Just Operators `%`, `^` + +The modulo and exponent (power) operators work only if both operands are numbers. `%` is a proper (and useful) 'modulus' operator, +not a useless 'reminder' operator that you would expect from anything that touches Java. While typically modulus is reserved +to integer numbers, scarpet expands them to floats with as much sense as possible. + +
pi^pi%euler => 1.124.... +-9 % 4 => 3 +9 % -4 => -3 +9.1 % -4.2 => -3.5 +9.1 % 4.2 => 0.7 +-3 ^ 2 => 9 +-3 ^ pi => // Error ++ +### Comparison Operators `==`, `equal()`, `!=`, `unique()`, `<`, `increasing()`, `>`, `decreasing()`, `<=`, `nondecreasing()`, `>=`, `nonincreasing()` + +Allows to compare the results of two expressions. For numbers, it considers arithmetic order of numbers, for +strings - lexicographical, nulls are always 'less' than everything else, and lists check their elements - +if the sizes are different, the size matters, otherwise, pairwise comparisons for each element are performed. +The same order rules than with all these operators are used with the default sortographical order as used by `sort` +function. All of these are true: + +
+null == null +null != false +0 == false +1 == true +null < 0 +null < -1000 +1000 < 'a' +'bar' < 'foo' +3 == 3.0 ++ +Functional variants of these operators allow to assert certain paradigms on multiple arguments at once. This means that +due to formal equivalence `x < y < z` is equivalent to `x < y & y < z` because of direct mapping to `increasing(x, y, z)`. This translates through +the parentheses, so `((x < y) < z)` is the same as `increasing(x, y, z)`. To achieve the same effect as you would see in other + languages (not python), you would need to cast the first pair to boolean value, i.e. `bool(x < y) < z`. + +### Logical Operators `&&`, `and(...)`, `||`, `or(...)` + +These operator compute respective boolean operation on the operands. What it important is that if calculating of the +second operand is not necessary, it won't be evaluated, which means one can use them as conditional statements. In +case of success returns first positive operand (`||`) or last one (`&&`). + +
+true || false => true +null || false => false +false || null => null +null != false || run('kill gnembon') // gnembon survives +null != false && run('kill gnembon') // when cheats not allowed +null != false && run('kill gnembon') // gnembon dies, cheats allowed ++ +### `Assignment Operators = <> +=` + +A set of assignment operators. All require bounded variable on the LHS, `<>` requires bounded arguments on the +right hand side as well (bounded, meaning being variables). Additionally they can also handle list constructors +with all bounded variables, and work then as list assignment operators. When `+=` is used on a list, it extends +that list of that element, and returns the list (old == new). `scarpet` doesn't support currently removal of items. +Removal of items can be obtained via `filter` command, and reassigning it fo the same variable. Both operations would +require rewriting of the array anyways. + +
+a = 5 => a == 5 +[a,b,c] = [3,4,5] => a==3, b==4, c==5 +[minx,maxx] = sort(xi,xj); // minx assumes min(xi, xj) and maxx, max(xi, xj) +[a,b,c,d,e,f] = [range(6)]; [a,b,c] <> [d,e,f]; [a,b,c,d,e,f] => [3,4,5,0,1,2] +a = [1,2,3]; a += 4 => [1,2,3,4] +a = [1,2,3,4]; a = filter(a,_!=2) => [1,3,4] ++ +### `Unary Operators - +` + +Require a number, flips the sign. One way to assert it's a number is by crashing the script. gg. + +
+-4 => -4 ++4 => 4 ++'4' // Error message ++ +### `Negation Operator !` + +flips boolean condition of the expression. Equivalent of `bool(expr)==false` + +
+!true => false +!false => true +!null => true +!5 => false +![] => true +![null] => false ++ +### `Unpacking Operator ...` + +Unpacks elements of a list of an iterator into a sequence of arguments in a function making so that `fun(...[1, 2, 3])` is +identical with `fun(1, 2, 3)`. For maps, it unpacks them to a list of key-value pairs. + +In function signatures it identifies a vararg parameter. + +
+fun(a, b, ... rest) -> [a, b, rest]; fun(1, 2, 3, 4) => [1, 2, [3, 4]] ++ +Effects of `...` can be surprisingly lasting. It is kept through the use of variables and function calls. + +
+fun(a, b, ... rest) -> [a, b, ... rest]; fun(1, 2, 3, 4) => [1, 2, 3, 4] +args() -> ... [1, 2, 3]; sum(a, b, c) -> a+b+c; sum(args()) => 6 +a = ... [1, 2, 3]; sum(a, b, c) -> a+b+c; sum(a) => 6 ++ +Unpacking mechanics can be used for list and map construction, not just for function calls. + +
+[...range(5), pi, ...range(5,-1,-1)] => [0, 1, 2, 3, 4, 3.14159265359, 5, 4, 3, 2, 1, 0] +{ ... map(range(5), _ -> _*_ )} => {0: 0, 1: 1, 2: 4, 3: 9, 4: 16} +{...{1 -> 2, 3 -> 4}, ...{5 -> 6, 7 -> 8}} => {1: 2, 3: 4, 5: 6, 7: 8} ++ +Fine print: unpacking of argument lists happens just before functions are evaluated. +This means that in some situations, for instance +when an expression is expected (`map(list, expr)`), or a function should not evaluate some (most!) of its arguments (`if(...)`), +unpacking cannot be used, and will be ignored, leaving `... list` identical to `list`. +Functions that don't honor unpacking mechanics, should have no use for it at the first place + (i.e. have one, or very well-defined, and very specific parameters), +so some caution (prior testing) is advised. Some of these multi-argument built-in functions are + `if`, `try`, `sort_key`, `system_variable_get`, `synchronize`, `sleep`, `in_dimension`, +all container functions (`get`, `has`, `put`, `delete`), +and all loop functions (`while`, `loop`, `map`, `filter`, `first`, `all`, `c_for`, `for` and`reduce`). + +### `Binary (bitwise) operations` + +These are a bunch of operators that work exclusively on numbers, more specifically their binary representations. Some of these +work on multiple numbers, some on only 2, and others on only 1. Note that most of these functions (all but `double_to_long_bits`) +only take integer values, so if the input has a decimal part, it will be discarded. + + - `bitwise_and(...)` -> Does the bitwise AND operation on each number in order. Note that with larger ranges of numbers this will + tend to 0. + - `bitwise_xor(...)` -> Does the bitwise XOR operation on each number in order. + - `bitwise_or(...)` -> Does the bitwise AND operation on each number in order. Note that with larger ranges of numbers this will + tend to -1. + - `bitwise_shift_left(num, amount)` -> Shifts all the bits of the first number `amount` spots to the left. Note that only the 6 + lowest-order bits of the amount are considered. + - `bitwise_shift_right(num, amount)` -> Shifts all the bits of the first number `amount` spots to the right logically. That is, the + `amount` most significant bits will always be set to 0. Like with the above, only the 6 lowest-order bits of the amount are considered. + - `bitwise_arithmetic_shift_right(num, amount)` -> Shifts all the bits of the first number `amount` spots to the right arithmetically. + That is, if the most significant (sign) bit is a 1, it'll propagate the one to the `amount` most significant bits. Like with the above, + only the 6 lowest-order bits of the amount are considered. + - `bitwise_roll_left(num, amount)` -> Rolls the bits of the first number `amount` bits to the left. This is basically where you + shift out the first `amount` bits and then add them on at the back, essentially 'rolling' the number. Note that unlike with + shifting, you can roll more than 63 bits at a time, as it just makes the number roll over more times, which isn't an issue + - `bitwise_roll_right(num, amount)` -> Same as above, just rolling in the other direction + - `bitwise_not(num)` -> Flips all the bits of the number. This is simply done by performing xor operation with -1, which in binary is + all ones. + - `bitwise_popcount(num)` -> Returns the number of ones in the binary representation of the number. For the number of zeroes, just + do 64 minus this number. + - `double_to_long_bits(num)` -> Returns a representation of the specified floating-point value according to the IEEE 754 floating-point + "double format" bit layout. + - `long_to_double_bits(num)` -> Returns the double value corresponding to a given bit representation. diff --git a/docs/scarpet/language/Overview.md b/docs/scarpet/language/Overview.md new file mode 100644 index 0000000..80da2ba --- /dev/null +++ b/docs/scarpet/language/Overview.md @@ -0,0 +1,293 @@ +# Fundamental components of `scarpet` programming language. + +Scarpet (a.k.a. Carpet Script, or Script for Carpet) is a programming language +designed to provide the ability to write custom programs to run within Minecraft +and interact with the world. + +This specification is divided into two sections: this one is agnostic to any +Minecraft related features and could function on its own, and CarpetExpression +for Minecraft specific routines and world manipulation functions. + +# Synopsis + +
+script run print('Hello World!') ++ +or an OVERLY complex example: + +
+/script run + block_check(x1, y1, z1, x2, y2, z2, block_to_check) -> + ( + [minx, maxx] = sort([x1, x2]); + [miny, maxy] = sort([y1, y2]); + [minz, maxz] = sort([z1, z2]); + 'Need to compute the size of the area of course'; + 'Cause this language doesn\'t support comments in the command mode'; + xsize = maxx - minx + 1; + ysize = maxy - miny + 1; + zsize = maxz - minz + 1; + total_count = 0; + loop(xsize, + xx = minx + _ ; + loop(ysize, + yy = miny + _ ; + loop(zsize, + zz = minz + _ ; + if ( block(xx,yy,zz) == block_to_check, + total_count += ceil(rand(1)) + ) + ) + ) + ); + total_count + ); + check_area_around_closest_player_for_block(block_to_check) -> + ( + closest_player = player(); + [posx, posy, posz] = query(closest_player, 'pos'); + total_count = block_check( posx-8,1,posz-8, posx+8,17,posz+8, block_to_check); + print('There is '+total_count+' of '+block_to_check+' around you') + ) +/script invoke check_area_around_closest_player_for_block 'diamond_ore' ++ +or simply + +
+/script run print('There is '+for(rect(x,9,z,8,8,8), _ == 'diamond_ore')+' diamond ore around you') ++ +It definitely pays to check what higher level `scarpet` functions have to offer. + +# Programs + +You can think of an program like a mathematical expression, like `"2.4*sin(45)/(2-4)"` or `"sin(y)>0 & max(z, 3)>3"`. +Writing a program, is like writing a `2+3`, just a bit longer. + +## Basic language components + +Programs consist of constants, like `2`, `3.14`, `pi`, or `'foo'`, operators like `+`, `/`, `->`, variables which you +can define, like `foo` or special ones that will be defined for you, like `_x`, or `_` , which are specific to each +built in function, and functions with name, and arguments in the form of `f(a,b,c)`, where `f` is the function name, +and `a, b, c` are the arguments which can be any other expression. And that's all the parts of the language, so all +in all - sounds quite simple. + +## Code flow + +Like any other proper programming language, `scarpet` needs brackets, basically to identify where stuff begins and +where it ends. In the languages that uses much more complicated constructs, like Java, they tend to use all sort of +them, round ones to indicate function calls, curly to indicate section of code, square to access lists, pointy for +generic types etc... I mean - there is no etc, cause they have exhausted all the bracket options... + +`Scarpet` is different, since it runs everything based on functions (although its not per se a functional +language like lisp) only needs the round brackets for everything, and it is up to the programmer to organize +its code so its readable, as adding more brackets does not have any effect on the performance of the programs +as they are compiled before they are executed. Look at the following example usage of `if()` function: + +
+if(x<y+6,set(x,8+y,z,'air');plop(x,top('surface',x,z),z,'birch'),sin(query(player(),'yaw'))>0.5,plop(0,0,0,'boulder'),particle('fire',x,y,z)) ++ +Would you prefer to read + +
+if( x<y+6, + set(x,8+y,z,'air'); + plop(x,top('surface',x,z),z,'birch'), + sin(query(player(),'yaw'))>0.5, + plop(0,0,0,'boulder'), + particle('fire',x,y,z) +) ++ +Or rather: + +
+if +( x<y+6, + ( + set(x,8+y,z,'air'); + plop(x,top('surface',x,z),z,'birch') + ), + // else if + sin(query(player(),'yaw'))>0.5, + ( + plop(0,0,0,'boulder') + ), + // else + particle('fire',x,y,z) +) ++ +Whichever style you prefer it doesn't matter. It typically depends on the situation and the complexity of the +subcomponents. No matter how many whitespaces and extra brackets you add - the code will evaluate to exactly the +same expression, and will run exactly the same, so make sure your programs are nice and clean so others don't +have problems with them + +## Functions and scoping + +Users can define functions in the form `fun(args....) -> expression` and they are compiled and saved for further +execution in this, but also subsequent calls of /script command, added to events, etc. Functions can also be + assigned to variables, +passed as arguments, called with `call('fun', args...)` function, but in most cases you would want to +call them directly by +name, in the form of `fun(args...)`. This means that once defined functions are saved with the world for +further use. For variables, there are two types of them, global - which are shared anywhere in the code, +and those are all which name starts with 'global_', and local variables which is everything else and those +are only visible inside each function. This also means that all the parameters in functions are +passed 'by value', not 'by reference'. + +## Outer variables + +Functions can still 'borrow' variables from the outer scope, by adding them to the function signature wrapped +around built-in function `outer`. It adds the specified value to the function call stack so they behave exactly +like capturing lambdas in Java, but unlike java captured variables don't need to be final. Scarpet will just +attach their new values at the time of the function definition, even if they change later. Most value will be +copied, but mutable values, like maps or lists, allow to keep the 'state' with the function, allowing them to +have memory and act like objects so to speak. Check `outer(var)` for details. + +## Code delivery, line indicators + +Note that this should only apply to pasting your code to execute with commandblock. Scarpet recommends placing +your code in apps (files with `.sc` extension that can be placed inside `/scripts` folder in the world files +or as a globally available app in singleplayer in the `.minecraft/config/carpet/scripts` folder and loaded +as a Scarpet app with the command `/script load [app_name]`. Scarpet apps loaded from disk should only +contain code, no need to start with `/script run` prefix. + +The following is the code that could be provided in a `foo.sc` app file located in world `/scripts` folder + +
+run_program() -> ( + loop( 10, + // looping 10 times + // comments are allowed in scripts located in world files + // since we can tell where that line ends + foo = floor(rand(10)); + check_not_zero(foo); + print(_+' - foo: '+foo); + print(' reciprocal: '+ _/foo ) + ) +); +check_not_zero(foo) -> ( + if (foo==0, foo = 1) +) ++ +Which we then call in-game with: + +
+/script load foo +/script in foo invoke run_program ++ +However the following code can also be input as a command, or in a command block. + +Since the maximum command that can be input to the chat is limited in length, you will be probably inserting your +programs by pasting them to command blocks or reading from world files, however pasting to command blocks will +remove some whitespaces and squish your newlines making the code not readable. If you are pasting a program that +is perfect and will never cause an error, I salute you, but for the most part it is quite likely that your program +might break, either at compile time, when its initially analyzed, or at execute time, when you suddenly attempt to +divide something by zero. In these cases you would want to get a meaningful error message, but for that you would +need to indicate for the compiler where did you put these new lines, since command block would squish them. For that, +place at the beginning of the line to let the compiler know where are you. This makes so that `$` is the only +character that is illegal in programs, since it will be replaced with new lines. As far as I know, `$` is not +used anywhere inside Minecraft identifiers, so this shouldn't hinder the abilities of your programs. + +Consider the following program executed as command block command: + +
+/script run +run_program() -> ( + loop( 10, + foo = floor(rand(_)); + check_not_zero(foo); + print(_+' - foo: '+foo); + print(' reciprocal: '+ _/foo ) + ) +); +check_not_zero(foo) -> ( + if (foo==0, foo = 1) +) ++ +Lets say that the intention was to check if the bar is zero and prevent division by zero in print, but because +the `foo` is passed as a variable, it never changes the original foo value. Because of the inevitable division +by zero, we get the following message: + +
+Your math is wrong, Incorrect number format for NaN at pos 98 +run_program() -> ( loop( 10, foo = floor(rand(_)); check_not_zero(foo); print(_+' - foo: '+foo); +HERE>> print(' reciprocal: '+ _/foo ) ));check_not_zero(foo) -> ( if (foo==0, foo = 1)) ++ +As we can see, we got our problem where the result of the mathematical operation was not a number +(infinity, so not a number), however by pasting our program into the command made it squish the newlines so +while it is clear where the error happened and we still can track the error down, the position of the error (98) +is not very helpful and wouldn't be useful if the program gets significantly longer. To combat this issue we can +precede every line of the script with dollar signs `$`: + +
+/script run +$run_program() -> ( +$ loop( 10, +$ foo = floor(rand(_)); +$ check_not_zero(foo); +$ print(_+' - foo: '+foo); +$ print(' reciprocal: '+ _/foo ) +$ ) +$); +$check_not_zero(foo) -> ( +$ if (foo==0, foo = 1) +$) ++ +Then we get the following error message + +
+Your math is wrong, Incorrect number format for NaN at line 7, pos 2 + print(_+' - foo: '+foo); + HERE>> print(' reciprocal: '+ _/foo ) + ) ++ +As we can note not only we get much more concise snippet, but also information about the line number and position, +so means its way easier to locate the potential problems problem + +Obviously that's not the way we intended this program to work. To get it `foo` modified via a function call, +we would either return it as a result and assign it to the new variable: + +
+foo = check_not_zero(foo); +... +check_not_zero(foo) -> if(foo == 0, 1, foo) ++ +.. or convert it to a global variable, which in this case passing as an argument is not required + +
+global_foo = floor(rand(10)); +check_foo_not_zero(); +... +check_foo_not_zero() -> if(global_foo == 0, global_foo = 1) ++ +## Scarpet preprocessor + +There are several preprocessing operations applied to the source of your program to clean it up and prepare for +execution. Some of them will affect your code as it is reported via stack traces and function definition, and some +are applied only on the surface. + - stripping `//` comments (in file mode) + - replacing `$` with newlines (in command mode, modifies submitted code) + - removing extra semicolons that don't follow `;` use as a binary operator, allowing for lenient use of semicolons + - translating `{` into `m(`, `[` into `l(`, and `]` and `}` into `)` + +No further optimizations are currently applied to your code. + +## Mentions + +LR1 parser, tokenizer, and several built-in functions are built based on the EvalEx project. +EvalEx is a handy expression evaluator for Java, that allows to evaluate +simple mathematical and boolean expressions. EvalEx is distributed under MIT licence. +For more information, see: [EvalEx GitHub repository](https://github.com/uklimaschewski/EvalEx) diff --git a/docs/scarpet/language/SystemFunctions.md b/docs/scarpet/language/SystemFunctions.md new file mode 100644 index 0000000..924dbce --- /dev/null +++ b/docs/scarpet/language/SystemFunctions.md @@ -0,0 +1,430 @@ +# System functions + +## Type conversion functions + +### `copy(expr)` + +Returns the deep copy of the expression. Can be used to copy mutable objects, like maps and lists + +### `type(expr)` + +Returns the string value indicating type of the expression. Possible outcomes +are `null`, `number`, `string`, `list`, `map`, `iterator`, `function`, `task`, +as well as minecraft related concepts like `block`, `entity`, `nbt`, `text`. + +### `bool(expr)` + +Returns a boolean context of the expression. +Bool is also interpreting string values as boolean, which is different from other +places where boolean context can be used. This can be used in places where API functions return string values to +represent binary values. + +
+bool(pi) => true +bool(false) => false +bool('') => false +bool([]) => false +bool(['']) => true +bool('foo') => true +bool('false') => false +bool('nulL') => false +if('false',1,0) => true ++ +### `number(expr)` + +Returns a numeric context of the expression. Can be used to read numbers from strings, or other types + +
+number(null) => 0 +number(false) => 0 +number(true) => 1 +number('') => null +number('3.14') => 3.14 +number([]) => 0 +number(['']) => 1 +number('foo') => null +number('3bar') => null +number('2')+number('2') => 4 ++ +### `str(expr)`,`str(expr, params? ... )`, `str(expr, param_list)` + +If called with one argument, returns string representation of such value. + +Otherwise, returns a formatted string representing the expression. Arguments for formatting can either be provided as + each consecutive parameter, or as a list which then would be the only extra parameter. To format one list argument + , you can use `str(list)`, or `str('foo %s', [list])`. + +Accepts formatting style accepted by `String.format`. +Supported types (with `"%>"` syntax): + +* `d`, `o`, `x`: integers, octal, hex +* `a`, `e`, `f`, `g`: floats +* `b`: booleans +* `s`: strings +* `%%`: '%' character + +
+str(null) => 'null' +str(false) => 'false' +str('') => '' +str('3.14') => '3.14' +str([]) => '[]' +str(['']) => '[]' +str('foo') => 'foo' +str('3bar') => '3bar' +str(2)+str(2) => '22' +str('pi: %.2f',pi) => 'pi: 3.14' +str('player at: %d, %d, %d',pos(player())) => 'player at: 567, -2423, 124' ++ +* * * +## Threading and Parallel Execution + +Scarpet allows to run threads of execution in parallel to the main script execution thread. In Minecraft, apps +are executed on the main server thread. Since Minecraft is inherently NOT thread safe, it is not that +beneficial to parallel execution in order to access world resources faster. Both `getBlockState` and `setBlockState` +are not thread safe and require the execution to park on the server thread, where these requests can be executed in +the off-tick time in between ticks that didn't take all 50ms. There are however benefits of running things in parallel, +like fine time control not relying on the tick clock, or running things independent on each other. You can still run +your actions on tick-by-tick basis, either taking control of the execution using `game_tick()` API function +(nasty solution), or scheduling tick using `schedule()` function (preferred solution), but threading gives much more control +on the timings without impacting the main game and is the only solution to solve problems in parallel +(see [scarpet camera](/src/main/resources/assets/carpet/scripts/camera.sc)). + +Due to limitations with the game, there are some limits to the threading as well. You cannot for +instance `join_task()` at all from the main script and server thread, because any use of Minecraft specific +function that require any world access, will require to park and join on the main thread to get world access, +meaning that calling join on that task would inevitably lead to a typical deadlock. You can still join tasks +from other threads, just because the only possibility of a deadlock in this case would come explicitly from your +bad code, not the internal world access behaviour. Some things tough like players or entities manipulation, can be +effectively parallelized. + +If the app is shutting down, creating new tasks via `task` will not succeed. Instead the new task will do nothing and return +`null`, so most threaded application should handle closing apps naturally. Keep in mind in case you rely on task return values, +that they will return `null` regardless of anything in these situations. When app handles `__on_close()` event, new tasks cannot +be submitted at this point, but current tasks are not terminated. Apps can use that opportunity to gracefully shutdown their tasks. +Regardless if the app handles `__on_close()` event, or does anything with their tasks in it, all tasks will be terminated exceptionally +within the next 1.5 seconds. + +### `task(function, ... args)`, `task_thread(executor, function, ... args)` + +Creates and runs a parallel task, returning the handle to the task object. Task will return the return value of the +function when its completed, or will return `null` immediately if task is still in progress, so grabbing a value of +a task object is non-blocking. Function can be either function value, or function lambda, or a name of an existing +defined function. In case function needs arguments to be called with, they should be supplied after the function +name, or value. `executor` identifier in `task_thread`, places the task in a specific queue identified by this value. +The default thread value is the `null` thread. There are no limits on number of parallel tasks for any executor, +so using different queues is solely for synchronization purposes. + +
+task( _() -> print('Hello Other World') ) => Runs print command on a separate thread +foo(a, b) -> print(a+b); task('foo',2,2) => Uses existing function definition to start a task +task_thread('temp', 'foo',3,5); => runs function foo with a different thread executor, identified as 'temp' +a = 3; task_thread('temp', _(outer(a), b) -> foo(a,b), 5) + => Another example of running the same thing passing arguments using closure over anonymous function as well as passing a parameter. ++ +In case you want to create a task based on a function that is not defined in your module, please read the tips on + "Passing function references to other modules of your application" section in the `call(...)` section. + +### `sleep()` `sleep(timeout)`, `sleep(timeout, close_expr)` + + +Halts the execution of the thread (or the game itself, if run not as a part of a task) for `expr` milliseconds. +It checks for interrupted execution, in that case exits the thread (or the entire program, if not run on a thread) in case the app +is being stopped/removed. If the closing expression is specified, executes the expression when a shutdown signal is triggered. +If run on the main thread (i.e. not as a task) the close expression may only be invoked when the entire game shuts down, so close call only +makes sense for threads. For regular programs, use `__on_close()` handler. + +Since `close_expr` is executed after app shutdown is initiated, you won't be able to create new tasks in that block. Threads +should periodically call `sleep` to ensure all app tasks will finish when the app is closing or right after, but the app engine +will not forcefully remove your running tasks, so the tasks themselves need to properly react to the closing request. + +
+sleep(50) # wait for 50 milliseconds +sleep(1000, print('Interrupted')) # waits for 1 second, outputs a message when thread is shut down. ++ +### `task_count(executor?)` + +If no argument provided, returns total number of tasks being executed in parallel at this moment using scarpet +threading system. If the executor is provided, returns number of active tasks for that provider. Use `task_count(null)` +to get the task count of the default executor only. + +### `task_value(task)` + +Returns the task return value, or `null` if task hasn't finished yet. Its a non-blocking operation. Unlike `join_task`, +can be called on any task at any point + +### `task_join(task)` + +Waits for the task completion and returns its computed value. If the task has already finished returns it immediately. +Unless taking the task value directly, i.e. via `task_value`, this operation is blocking. Since Minecraft has a +limitation that all world access operations have to be performed on the main game thread in the off-tick time, +joining any tasks that use Minecraft API from the main thread would mean automatic lock, so joining from the main +thread is not allowed. Join tasks from other threads, if you really need to, or communicate asynchronously with +the task via globals or function data / arguments to monitor its progress, communicate, get partial results, +or signal termination. + +### `task_completed(task)` + +Returns true if task has completed, or false otherwise. + +### `synchronize(lock, expression)` + +Evaluates `expression` synchronized with respect to the lock `lock`. Returns the value of the expression. + +### `task_dock(expr)` + +In a not-task (running regular code on the main game thread) it is a pass-through command. In tasks - it docks +the current thread on the main server thread and executes expression as one server offline server task. +This is especially helpful in case a task has several docking operations to perform, such as setting a block, and +it would be much more efficient to do them all at once rather then packing each block access in each own call. + +Be mindful, that docking the task means that the tick execution will be delayed until the expression is evaluated. +This will synchronize your task with other tasks using `task_dock`, but if you should be using `synchronize` to +synchronize tasks without locking the main thread. + + +* * * + +## Auxiliary functions + +### `lower(expr), upper(expr), title(expr)` + +Returns lowercase, uppercase or titlecase representation of a string representation of the passed expression + +
+lower('aBc') => 'abc' +upper('aBc') => 'ABC' +title('aBc') => 'Abc' ++ +### `replace(string, regex, repl?); replace_first(string, regex, repl?)` + +Replaces all, or first occurrence of a regular expression in the string with `repl` expression, +or nothing, if not specified. To use escape characters (`\(`,`\+`,...), metacharacters (`\d`,`\w`,...), or position anchors (`\b`,`\z`,...) in your regular expression, use two backslashes. + +
+replace('abbccddebfg','b+','z') // => azccddezfg +replace('abbccddebfg','\\w$','z') // => abbccddebfz +replace_first('abbccddebfg','b+','z') // => azccddebfg ++ +### `length(expr)` + +Returns length of the expression, the length of the string, the length of the integer part of the number, +or length of the list + +
+length(pi) => 1 +length(pi*pi) => 1 +length(pi^pi) => 2 +length([]) => 0 +length([1,2,3]) => 3 +length('') => 0 +length('foo') => 3 ++ +### `rand(expr), rand(expr, seed)` + +returns a random number from `0.0` (inclusive) to `expr` (exclusive). In boolean context (in conditions, +boolean functions, or `bool`), returns false if the randomly selected value is less than 1. This means +that `bool(rand(2))` returns true half of the time and `!rand(5)` returns true for 20% (1/5) of the time. If seed is not +provided, uses a random seed that's shared across all scarpet apps. +If seed is provided, each consecutive call to rand() will act like 'next' call to the +same random object. Scarpet keeps track of up to 65536 custom random number generators (custom seeds, per app), +so if you exceed this number, your random sequences will revert to the beginning and start over. + +
+map(range(10), floor(rand(10))) => [5, 8, 0, 6, 9, 3, 9, 9, 1, 8] +map(range(10), bool(rand(2))) => [false, false, true, false, false, false, true, false, true, false] +map(range(10), str('%.1f',rand(_))) => [0.0, 0.4, 0.6, 1.9, 2.8, 3.8, 5.3, 2.2, 1.6, 5.6] ++ +## `reset_seed(seed)` + +Resets the sequence of the randomizer used by `rand` for this seed to its initial state. Returns a boolean value +indicating if the given seed has been used or not. + +### `perlin(x), perlin(x, y), perlin(x, y, z), perlin(x, y, z, seed)` + +returns a noise value from `0.0` to `1.0` (roughly) for 1, 2 or 3 dimensional coordinate. The default seed it samples +from is `0`, but seed can be specified as a 4th argument as well. In case you need 1D or 2D noise values with custom +seed, use `null` for `y` and `z`, or `z` arguments respectively. + +Perlin noise is based on a square grid and generates rougher maps comparing to Simplex, which is creamier. +Querying for lower-dimensional result, rather than affixing unused dimensions to constants has a speed benefit, + +Thou shall not sample from noise changing seed frequently. Scarpet will keep track of the last 256 perlin seeds +used for sampling providing similar speed comparing to the default seed of `0`. In case the app engine uses more +than 256 seeds at the same time, switching between them can get much more expensive. + +### `simplex(x, y), simplex(x, y, z), simplex(x, y, z, seed)` + +returns a noise value from `0.0` to `1.0` (roughly) for 2 or 3 dimensional coordinate. The default seed it samples +from is `0`, but seed can be specified as a 4th argument as well. In case you need 2D noise values with custom seed, +use `null` for `z` argument. + +Simplex noise is based on a triangular grid and generates smoother maps comparing to Perlin. To sample 1D simplex +noise, affix other coordinate to a constant. + +Thou shall not sample from noise changing seed frequently. Scarpet will keep track of the last 256 simplex seeds +used for sampling providing similar speed comparing to the default seed of `0`. In case the app engine uses more +than 256 seeds at the same time, switching between them can get much more expensive. + +### `print(expr)`, `print(player, expr)` + +prints the value of the expression to chat. Passes the result of the argument to the output unchanged, +so `print`-statements can be weaved in code to debug programming issues. By default it uses the same communication +channels that most vanilla commands are using. + +In case player is directly specified, it only sends the message to that player, like `tell` command. + +
+print('foo') => results in foo, prints: foo +a = 1; print(a = 5) => results in 5, prints: 5 +a = 1; print(a) = 5 => results in 5, prints: 1 +print('pi = '+pi) => prints: pi = 3.141592653589793 +print(str('pi = %.2f',pi)) => prints: pi = 3.14 ++ +### `time()` + +Returns the number of milliseconds since 'some point', like Java's `System.nanoTime()`, which varies from system to +system and from Java to Java. This measure should NOT be used to determine the current (date)time, but to measure +durations of things. +it returns a float with time in milliseconds (ms) for convenience and microsecond (μs) resolution for sanity. + + +
+start_time = time(); +flip_my_world_upside_down(); +print(str('this took %d milliseconds',time()-start_time)) ++ +### `unix_time()` + +Returns standard POSIX time as a number of milliseconds since the start of the epoch +(00:00 am and 0 seconds, 1 Jan 1970). +Unlike the previous function, this can be used to get exact time, but it varies from time zone to time zone. + +### `convert_date(milliseconds)` +### `convert_date(year, month, date, hours?, mins?, secs?)` +### `convert_date([year, month, date, hours?, mins?, secs?])` + +If called with a single argument, converts standard POSIX time to a list in the format: + +`[year, month, date, hours, mins, secs, day_of_week, day_of_year, week_of_year]` + +eg: `convert_date(1592401346960) -> [2020, 6, 17, 10, 42, 26, 3, 169, 25]` + +Where the `6` stands for June, but `17` stands for 17th, `10` stands for 10am, +`42` stands for 42 minutes past the hour, and `26` stands for 26 seconds past the minute, +and `3` stands for Wednesday, `169` is the day of year, and `25` is a week of year. + +Run `convert_date(unix_time())` to get current time as list. + + +When called with a list, or with 3 or 6 arguments, returns standard POSIX time as a number of milliseconds since the + start of the epoch (1 Jan 1970), +using the time inputted into the function as opposed to the system time. + +Example editing: +
+date = convert_date(unix_time()); + +months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; + +days = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun']; + +print( + str('Its %s, %d %s %d, %02d:%02d:%02d', + days:(date:6-1), date:2, months:(date:1-1), date:0, date:3, date:4, date:5 + ) +) ++ +This will give you a date: + +It is currently `hrs`:`mins` and `secs` seconds on the `date`th of `month`, `year` + +### `encode_b64(string)`, `decode_b64(string)` + +Encode or decode a string from b64, throwing a `b64_error` exception if it's invalid + +### `encode_json(value)`, `decode_json(string)` + +Encodes a value as a json string, and decodes a json string as a valid value, throwing a `json_error` exception if it +doesn't parse properly + +### `profile_expr(expression)` + +Returns number of times given expression can be run in 50ms time. Useful to profile and optimize your code. +Note that, even if its only a number, it WILL run these commands, so if they are destructive, you need to be careful. + +* * * + +## Access to variables and stored functions (use with caution) + +### `var(expr)` + +Returns the variable under the name of the string value of the expression. Allows to manipulate variables in more +programmatic manner, which allows to use local variable set with a hash map type key-value access, +can also be used with global variables + +
+a = 1; var('a') = 'foo'; a => a == 'foo' ++ +### `undef(expr)` + +Removes all bindings of a variable with a name of `expr`. Removes also all function definitions with that name. +It can affect global variable pool, and local variable set for a particular function. + +
+inc(i) -> i+1; foo = 5; inc(foo) => 6 +inc(i) -> i+1; foo = 5; undef('foo'); inc(foo) => 1 +inc(i) -> i+1; foo = 5; undef('inc'); undef('foo'); inc(foo) => Error: Function inc is not defined yet at pos 53 ++ +### `vars(prefix)` + +It returns all names of variables from local scope (if prefix does not start with 'global') or global variables +(otherwise). Here is a larger example that uses combination of `vars` and `var` functions to be +used for object counting + +
+/script run +$ count_blocks(ent) -> ( +$ [cx, cy, cz] = query(ent, 'pos'); +$ scan(cx, cy, cz, 16, 16, 16, var('count_'+_) += 1); +$ for ( sort_key( vars('count_'), -var(_)), +$ print(str( '%s: %d', slice(_,6), var(_) )) +$ ) +$ ) +/script run count_blocks(player()) ++ +* * * + +## System key-value storage + +Scarpet runs apps in isolation. The can share code via use of shared libraries, but each library that is imported to +each app is specific to that app. Apps can store and fetch state from disk, but its restricted to specific locations +meaning apps cannot interact via disk either. To facilitate communication for interappperability, scarpet hosts its +own key-value storage that is shared between all apps currently running on the host, providing methods for getting an +associated value with optional setting it if not present, and an operation of modifying a content of a system +global value. + +### `system_variable_get(key, default_value ?)` + +Returns the variable from the system shared key-value storage keyed with a `key` value, optionally if value is +not present, and default expression is provided, sets a new value to be associated with that key + +### `system_variable_set(key, new_value)` + +Returns the variable from the system shared key-value storage keyed with a `key` value, and sets a new +mapping for the key diff --git a/docs/scarpet/language/VariablesAndConstants.md b/docs/scarpet/language/VariablesAndConstants.md new file mode 100644 index 0000000..a7657f4 --- /dev/null +++ b/docs/scarpet/language/VariablesAndConstants.md @@ -0,0 +1,34 @@ +# Variables and Constants + +`scarpet` provides a number of constants that can be used literally in scripts + +* `null`: nothing, zilch, not even false +* `true`: pure true, can act as `1` +* `false`: false truth, or true falsth, equals to `0` +* `pi`: for the fans of perimeters, its a perimeter of an apple pi of diameter 1\. About 3.14 +* `euler`: clever guy. Derivative of its exponent is goto 1\. About 2.72 + +Apart from that, there is a bunch of system variables, that start with `_` that are set by `scarpet` built-ins, +like `_`, typically each consecutive value in loops, `_i` indicating iteration, or `_a` like an accumulator +for `reduce` function. Certain calls to Minecraft specific calls would also set `_x`, `_y`, `_z`, indicating +block positions. All variables starting with `_` are read-only, and cannot be declared and modified in client code. + +## Literals + +`scarpet` accepts numeric and string liters constants. Numbers look like `1, 2.5, -3e-7, 0xff,` and are internally +represented primarily as Java's `double` but `scarpet` will try to trim trailing zeros as much as possible so if you +need to use them as integers or even longs - you can. Long values will also not loose their long precision in addition, +subtraction, negation and multiplication, however any other operation that is not guaranteed to return a long value +(like division) on a number even if it can be properly +represented as long, will make them convert to doubles. + +Strings use single quoting, for multiple reasons, but primarily to allow for +easier use of strings inside doubly quoted command arguments (when passing a script as a parameter of `/script fill` +for example), or when typing in jsons inside scarpet (to feed back into a `/data merge` command for example). +Strings also use backslashes `\` for quoting special characters, in both plain strings and regular expressions + +
+'foo' +print('This doesn\'t work') +nbt ~ '\\.foo' // matching '.' as a '.', not 'any character match' +diff --git a/docs/scarpet/resources/editors/idea/1.txt b/docs/scarpet/resources/editors/idea/1.txt new file mode 100644 index 0000000..5f9a7dc --- /dev/null +++ b/docs/scarpet/resources/editors/idea/1.txt @@ -0,0 +1,127 @@ +abs +acos +acosh +acot +all +and +asin +asinh +atan +atan2 +atanh +bitwise_and +bitwise_or +bitwise_xor +bitwise_shift_left +bitwise_shift_right +bitwise_roll_left +bitwise_roll_right +bitwise_not +bitwise_popcount +bool +break +call +ceil +c_for +continue +copy +cos +cosh +cot +coth +csc +csch +decreasing +deg +delete +difference +double_to_long_bits +equal +exit +fact +filter +first +floor +for +get +has +hash_code +if +import +increasing +join +keys +l +length +log +log10 +log1p +long_bits_to_double +loop +lower +m +mandelbrot +map +max +min +nondecreasing +nonincreasing +not +number +or +outer +pairs +perlin +print +product +profile_expr +put +quotient +rad +rand +range +reduce +relu +replace +replace_first +reset_seed +return +round +sec +sech +simplex +sin +sinh +sleep +slice +sort +sort_key +sum +synchronize +system_variable_get +system_variable_set +split +sqrt +str +tan +tanh +task +task_completed +task_count +task_dock +task_join +task_thread +task_value +then +throw +time +title +try +type +undef +unique +upper +values +var +vars +while diff --git a/docs/scarpet/resources/editors/idea/2.txt b/docs/scarpet/resources/editors/idea/2.txt new file mode 100644 index 0000000..d90729f --- /dev/null +++ b/docs/scarpet/resources/editors/idea/2.txt @@ -0,0 +1,142 @@ +air +add_chunk_ticket +biome +blast_resistance +block +block_data +block_light +block_list +block_sound +block_state +block_tick +block_tags +blocks_daylight +blocks_movement +bossbar +brightness +chunk_tickets +close_screen +convert_date +crafting_remaining_item +create_datapack +create_explosion +create_marker +create_screen +current_dimension +day_time +delete_file +destroy +diamond +display_title +draw_shape +drop_item +effective_light +emitted_light +encode_nbt +entity_area +entity_event +entity_id +entity_list +entity_load_handler +entity_selector +entity_types +escape_nbt +flammable +format +game_tick +generation_status +handle_event +hardness +harvest +in_dimension +in_slime_chunk +inhabited_time +inventory_find +inventory_get +inventory_remove +inventory_set +inventory_size +item_display_name +item_list +item_tags +last_tick_times +light +liquid +list_files +load_app_data +loaded +loaded_status +logger +map_colour +material +modify +nbt +nbt_storage +neighbours +opacity +parse_nbt +particle +particle_line +particle_rect +place_item +player +plop +poi +pos +pos_offset +power +query +random_tick +recipe_data +rect +reload_chunk +remove_all_markers +reset_chunk +run +sample_noise +save +scan +schedule +scoreboard +scoreboard_add +scoreboard_remove +scoreboard_display +scoreboard_property +screen_property +tag_matches +team_add +team_leave +team_list +team_property +team_remove +read_file +see_sky +set +set_biome +set_poi +set_structure +signal_event +sky_light +solid +sound +spawn +spawn_potential +stack_limit +statistic +store_app_data +structures +structure_eligibility +structure_references +suffocates +system_info +tick_time +ticks_randomly +top +transparent +update +unix_time +view_distance +volume +without_updates +world_time +write_file diff --git a/docs/scarpet/resources/editors/idea/3.txt b/docs/scarpet/resources/editors/idea/3.txt new file mode 100644 index 0000000..fc9e16f --- /dev/null +++ b/docs/scarpet/resources/editors/idea/3.txt @@ -0,0 +1,12 @@ +_ +_a +_i +_x +_y +_z +_trace +euler +false +null +pi +true \ No newline at end of file diff --git a/docs/scarpet/resources/editors/idea/4.txt b/docs/scarpet/resources/editors/idea/4.txt new file mode 100644 index 0000000..29bcdb3 --- /dev/null +++ b/docs/scarpet/resources/editors/idea/4.txt @@ -0,0 +1,51 @@ +__command() -> +__config() -> +__on_chunk_generated(x, z) -> +__on_chunk_loaded(x, z) -> +__on_close() -> +__on_player_attacks_entity(player, entity) -> +__on_player_breaks_block(player, block) -> +__on_player_clicks_block(player, block, face) -> +__on_player_changes_dimension(player, from_pos, from_dimension, to_pos, to_dimension) -> +__on_player_chooses_recipe(player, recipe, full_stack) -> +__on_player_collides_with_entity(player, entity) -> +__on_player_deploys_elytra(player) -> +__on_player_drops_item(player) -> +__on_player_drops_stack(player) -> +__on_player_finishes_using_item(player, item_tuple, hand) -> +__on_player_interacts_with_block(player, hand, block, face, hitvec) -> +__on_player_interacts_with_entity(player, entity, hand) -> +__on_player_jumps(player) -> +__on_player_picks_up_item(player, item) -> +__on_player_placing_block(player, item_tuple, hand, block) -> +__on_player_places_block(player, item_tuple, hand, block) -> +__on_player_releases_item(player, item_tuple, hand) -> +__on_player_rides(player, forward, strafe, jumping, sneaking) -> +__on_player_right_clicks_block(player, item_tuple, hand, block, face, hitvec) -> +__on_player_starts_sneaking(player) -> +__on_player_starts_sprinting(player) -> +__on_player_stops_sneaking(player) -> +__on_player_stops_sprinting(player) -> +__on_player_swings_hand(player, hand) -> +__on_player_switches_slot(player, from, to) -> +__on_player_swaps_hands(player) -> +__on_player_takes_damage(player, amount, source, entity) -> +__on_player_trades(player, entity, buy_left, buy_right, sell) -> +__on_player_uses_item(player, item_tuple, hand) -> +__on_player_wakes_up(player) -> +__on_player_escapes_sleep(player) -> +__on_statistic(player, category, event, value) -> +__on_start() -> +__on_tick() -> +__on_tick_ender() -> +__on_tick_nether() -> +__on_player_takes_damage(player, amount, source, source_entity) -> +__on_player_deals_damage(player, amount, entity) -> +__on_player_dies(player) -> +__on_player_respawns(player) -> +__on_player_disconnects(player, reason) -> +__on_player_connects(player) -> +__on_player_message(player, message) -> +__on_player_command(player, command) -> +__on_explosion(pos, power, source, causer, mode, fire) -> +__on_explosion_outcome(pos, power, source, causer, mode, fire, blocks, entities) -> diff --git a/docs/scarpet/resources/editors/idea/Idea.md b/docs/scarpet/resources/editors/idea/Idea.md new file mode 100644 index 0000000..36218ca --- /dev/null +++ b/docs/scarpet/resources/editors/idea/Idea.md @@ -0,0 +1,16 @@ +# Custom Language Support for Intellij + +### Add scarpet to registered file types (`.sc` and `.scl`) + +![Settings -> File Types](/docs/scarpet/resources/media/idea_settings.png) + +### When adding use the following settings + +![Customize your settings](/docs/scarpet/resources/media/idea_customize.png) + +### Grab lists of keywords (1,2,3 and 4) from the following files: + +[1](/docs/scarpet/resources/editors/idea/1.txt) +[2](/docs/scarpet/resources/editors/idea/2.txt) +[3](/docs/scarpet/resources/editors/idea/3.txt) +[4](/docs/scarpet/resources/editors/idea/4.txt) diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml new file mode 100644 index 0000000..8d84d45 --- /dev/null +++ b/docs/scarpet/resources/editors/npp/scarpet.xml @@ -0,0 +1,186 @@ +
A Carpet rule, that can return its required properties and stores a value.
+ * + *Name and description translations are picked up from the translation system.
+ * + * @paramReturns this rule's name
+ * + *This is also the rule's id, used as its key in {@link SettingsManager}, for changing rules in-game and for creation + * of translation keys for the rule.
+ * + *This method must always return the same value for the same {@link CarpetRule}.
+ */ + String name(); + + /** + *Returns a {@link List} of {@link BaseComponent} with extra information about this rule, that is, + * the lines after the rule's description.
+ * + *Handling of translation of the result of this method is responsibility of the rule implementation.
+ */ + ListReturns a {@link Collection} of categories this rule is on.
+ */ + CollectionReturns a {@link Collection} of suggestions for values that this rule will be able + * to accept as {@link String strings}. The rule must be able to accept all of the suggestions in the returned {@link Collection} as a value, + * though it may have requirements for those to be applicable.
+ * + *The returned collection must contain the rule's default value.
+ */ + CollectionReturns the {@link SettingsManager} this rule is in.
+ * + *This method may be removed or changed in the future, and is not part of the api contract, but it's needed to sync + * the rule with clients and other workings. Overriding this method should be safe.
+ */ + SettingsManager settingsManager(); + + /** + *Returns this rule's value
+ */ + T value(); + + /** + *Returns whether this rule can be toggled in the client-side when not connected to a + * Carpet server.
+ * + *In the default implementation, this is the case when {@link #categories()} contains {@link RuleCategory#CLIENT}
+ */ + boolean canBeToggledClientSide(); + + /** + *Returns the type of this rule's value.
+ * + *If this rule's type is primitive, it returns a wrapped version of it (such as the result of running + * {@link ClassUtils#primitiveToWrapper(Class)} on it)
+ */ + ClassReturns this rule's default value.
+ * + *This value will never be {@code null}, and will always be a valid value for {@link #set(CommandSourceStack, Object)}.
+ */ + T defaultValue(); + + /** + *Returns whether this rule is strict.
+ * + *A rule being strict means that it will only accept the suggestions returned by {@link #suggestions()} as valid values.
+ * + *Note that a rule implementation may return {@code false} in this method but still not accept options other than those + * returned in {@link #suggestions()}, only the opposite is guaranteed.
+ */ + default boolean strict() { + return false; + } + + /** + *Sets this rule's value to the provided {@link String}, after first converting the {@link String} into a suitable type.
+ * + *This methods run any required validation on the value first, and throws {@link InvalidRuleValueException} if the value is not suitable for + * this rule, regardless of whether it was impossible to convert the value to the required type, the rule doesn't accept the value, or the rule is + * immutable.
+ * + *Implementations of this method must notify their {@link SettingsManager} by calling + * {@link SettingsManager#notifyRuleChanged(CommandSourceStack, CarpetRule, String)}, who is responsible for notifying the + * {@link ServerNetworkHandler} (if the rule isn't restricted from being synchronized with clients) and other rule observers like the Scarpet + * event, in case the value of the rule was changed because of the invocation.
+ * + *This method must not throw any exception other than the documented {@link InvalidRuleValueException}.
+ * + * @param source The {@link CommandSourceStack} to notify about the result of this rule change or {@code null} in order to not notify + * @param value The new value for this rule as a {@link String} + * @throws InvalidRuleValueException if the value passed to the method was not valid as a value to this rule, either because of incompatible type, + * because the rule can't accept that value or because there was some requirement missing for that value to be allowed + */ + void set(CommandSourceStack source, String value) throws InvalidRuleValueException; + + /** + *This method follows the same contract as {@link #set(CommandSourceStack, String)}, but accepts a value already parsed (though not verified).
+ * @see #set(CommandSourceStack, String) + */ + void set(CommandSourceStack source, T value) throws InvalidRuleValueException; +} diff --git a/src/main/java/carpet/api/settings/InvalidRuleValueException.java b/src/main/java/carpet/api/settings/InvalidRuleValueException.java new file mode 100644 index 0000000..82e04b3 --- /dev/null +++ b/src/main/java/carpet/api/settings/InvalidRuleValueException.java @@ -0,0 +1,36 @@ +package carpet.api.settings; + +import carpet.utils.Messenger; +import net.minecraft.commands.CommandSourceStack; + +/** + *An {@link Exception} thrown when the value given for a {@link CarpetRule} is invalid.
+ * + *It can hold a message to be sent to the executing source.
+ */ +public class InvalidRuleValueException extends Exception { + + /** + *Constructs a new {@link InvalidRuleValueException} with a message that will be passed to the executing source
+ * @param cause The cause of the exception + */ + public InvalidRuleValueException(String cause) { + super(cause); + } + + /** + *Constructs a new {@link InvalidRuleValueException} with no detail message, that therefore should not notify the source
+ */ + public InvalidRuleValueException() { + super(); + } + + /** + *Notifies the given source with the exception's message if it exists, does nothing if it doesn't exist or it is {@code null}
+ * @param source The source to notify + */ + public void notifySource(String ruleName, CommandSourceStack source) { + if (getMessage() != null) + Messenger.m(source, "r Couldn't set value for rule " + ruleName + ": "+ getMessage()); + } +} diff --git a/src/main/java/carpet/api/settings/Rule.java b/src/main/java/carpet/api/settings/Rule.java new file mode 100644 index 0000000..f899140 --- /dev/null +++ b/src/main/java/carpet/api/settings/Rule.java @@ -0,0 +1,82 @@ +package carpet.api.settings; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Any field in this class annotated with this class is interpreted as a carpet rule. + * The field must be static and have a type of one of: + * - boolean + * - int + * - double + * - String + * - long + * - float + * - a subclass of Enum + * The default value of the rule will be the initial value of the field. + */ +@Documented +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Rule +{ + /** + *An array of categories the rule is in, as {@link String strings}.
+ * + *Those must have a corresponding translation key. Categories provided in Carpet's {@link RuleCategory} already + * have translation key.
+ */ + String[] categories(); + + /** + * Options to select in menu. + * Inferred for booleans and enums. Otherwise, must be present. + */ + String[] options() default {}; + + /** + * if a rule is not strict - can take any value, otherwise it needs to match + * any of the options + * For enums, its always strict, same for booleans - no need to set that for them. + */ + boolean strict() default true; + + /** + * If specified, the rule will automatically enable or disable + * a builtin Scarpet Rule App with this name. + */ + String appSource() default ""; + + /** + * The class of the validator checked right before the rule is changed, using the returned value as the new value to set, or cancel the change if null is returned. + */ + @SuppressWarnings("rawtypes") + Class extends Validator>[] validators() default {}; + + /** + *A class or list of classes implementing {@link Condition} that will have their {@link Condition#shouldRegister()} method + * executed while the rule is being parsed in {@link SettingsManager#parseSettingsClass(Class)}, that will cause the rule to be skipped + * if it evaluates to false
+ * + * @see Condition + */ + Class extends Condition>[] conditions() default {}; + + /** + *Represents a condition that must be met for a rule to be registered in a {@link SettingsManager} via + * {@link SettingsManager#parseSettingsClass(Class)}
+ * + * @see Rule#conditions() + * @see #shouldRegister() + */ + interface Condition { + /** + *Returns whether the rule that has had this {@link Condition} added should register.
+ * @return {@code true} to register the rule, {@code false} otherwise + */ + boolean shouldRegister(); + } +} diff --git a/src/main/java/carpet/api/settings/RuleCategory.java b/src/main/java/carpet/api/settings/RuleCategory.java new file mode 100644 index 0000000..0f785ca --- /dev/null +++ b/src/main/java/carpet/api/settings/RuleCategory.java @@ -0,0 +1,29 @@ +package carpet.api.settings; + +/** + *Multiple categories for rules that Carpet uses and can be used to group other rules under the same + * categories.
+ * + *These are not the only categories that can be used, you can create your own ones for your rules + * and they will be added to the SettingsManager.
+ * + */ +public class RuleCategory { + public static final String BUGFIX = "bugfix"; + public static final String SURVIVAL = "survival"; + public static final String CREATIVE = "creative"; + public static final String EXPERIMENTAL = "experimental"; + public static final String OPTIMIZATION = "optimization"; + public static final String FEATURE = "feature"; + public static final String COMMAND = "command"; + public static final String TNT = "tnt"; + public static final String DISPENSER = "dispenser"; + public static final String SCARPET = "scarpet"; + /** + * Rules with this {@link RuleCategory} will have a client-side + * counterpart, so they can be set independently without the server + * running Carpet + */ + public static final String CLIENT = "client"; + +} diff --git a/src/main/java/carpet/api/settings/RuleHelper.java b/src/main/java/carpet/api/settings/RuleHelper.java new file mode 100644 index 0000000..8966df3 --- /dev/null +++ b/src/main/java/carpet/api/settings/RuleHelper.java @@ -0,0 +1,107 @@ +package carpet.api.settings; + +import java.util.Locale; + +import carpet.utils.TranslationKeys; +import carpet.utils.Translations; +import net.minecraft.commands.CommandSourceStack; + +/** + *A helper class for operating with {@link CarpetRule} instances and values.
+ * + *If a method is visible and has javadocs it's probably API
+ */ +public final class RuleHelper { + private RuleHelper() {} + + /** + *Gets a {@code boolean} value for a given {@link CarpetRule}.
+ * + *The current implementation is as follows:
+ *Converts a rule's value into its similar {@link String} representation.
+ * + *If the value is an {@link Enum}, this method returns the name of the enum constant lowercased, else + * it returns the result of running {@link #toString()} on the value.
+ * @param value A rule's value + * @return A {@link String} representation of the given value + */ + public static String toRuleString(Object value) { + if (value instanceof Enum) return ((Enum>) value).name().toLowerCase(Locale.ROOT); + return value.toString(); + } + + /** + *Checks whether the given {@code CarpetRule rule} is in its default value
+ * + * @param rule The rule to check + * @return {@code true} if the rule's default value equals its current value + */ + public static boolean isInDefaultValue(CarpetRule> rule) { + return rule.defaultValue().equals(rule.value()); + } + + /** + *Resets the given {@link CarpetRule rule} to its default value, and notifies the given source, if provided.
+ * @param rule The {@link CarpetRule} to reset to its default value + * @param source A {@link ServerCommandSource} to notify about this change, or {@code null} + * + * @paramA {@link SettingsManager} is a class that manages {@link CarpetRule carpet rules} in a {@link MinecraftServer}, + * including a command to manage it, and allowing other code to hook into rule changes by using {@link RuleObserver rule observers}.
+ * + *You can create your own {@link SettingsManager} if you want to have your own command that will be handled the same way as Carpet's + * by using {@link #SettingsManager(String, String, String)} and returning it in your {@link CarpetExtension}'s + * {@link CarpetExtension#extensionSettingsManager()} method.
+ */ +@SuppressWarnings({"deprecation", "removal"}) // remove after removing old system +public class SettingsManager { + private final MapDefines a class that can be notified about a {@link CarpetRule} changing.
+ * + * @see #ruleChanged(CommandSourceStack, CarpetRule, String) + * @see SettingsManager#registerRuleObserver(RuleObserver) + * @see SettingsManager#registerGlobalRuleObserver(RuleObserver) + */ + @FunctionalInterface + public static interface RuleObserver { + /** + *Notifies this {@link RuleObserver} about the change of a {@link CarpetRule}.
+ * + *When this is called, the {@link CarpetRule} value has already been set.
+ * + * @param source The {@link CommandSourceStack} that likely originated this change, and should be the notified source for further + * messages. Can be {@code null} if there was none and the operation shouldn't send feedback. + * @param changedRule The {@link CarpetRule} that changed. Use {@link CarpetRule#value() changedRule.value()} to get the rule's value, + * and pass it to {@link RuleHelper#toRuleString(Object)} to get the {@link String} version of it + * @param userInput The {@link String} that the user entered when changing the rule, or a best-effort representation of it in case that is + * is not available at the time (such as loading from disk or a rule being changed programmatically). Note that this value + * may not represent the same string as converting the current value to a {@link String} via {@link RuleHelper#toRuleString(Object)}, + * given the rule implementation may have adapted the input into a different value, for example with the use of a {@link Validator} + */ + void ruleChanged(CommandSourceStack source, CarpetRule> changedRule, String userInput); + } + + /** + * Creates a new {@link SettingsManager} with the given version, identifier and fancy name + * + * @param version A {@link String} with the mod's version + * @param identifier A {@link String} with the mod's id, will be the command name + * @param fancyName A {@link String} being the mod's fancy name. + */ + public SettingsManager(String version, String identifier, String fancyName) + { + this.version = version; + this.identifier = identifier; + this.fancyName = fancyName; + } + + /** + *Registers a {@link RuleObserver} to changes in rules from + * this {@link SettingsManager} instance.
+ * + * @see SettingsManager#registerGlobalRuleObserver(RuleObserver) + * + * @param observer A {@link RuleObserver} that will be called with + * the used {@link CommandSourceStack} and the changed + * {@link CarpetRule}. + */ + public void registerRuleObserver(RuleObserver observer) + { + observers.add(observer); + } + + /** + * Registers a {@link RuleObserver} to changes in rules from + * any {@link SettingsManager} instance (unless their implementation disallows it). + * @see SettingsManager#registerRuleObserver(RuleObserver) + * + * @param observer A {@link RuleObserver} that will be called with + * the used {@link CommandSourceStack}, and the changed + * {@link CarpetRule}. + */ + public static void registerGlobalRuleObserver(RuleObserver observer) + { + staticObservers.add(observer); + } + + /** + * @return A {@link String} being this {@link SettingsManager}'s + * identifier, which is also the command name + */ + public String identifier() { + return identifier; + } + + /** + *Returns whether this {@link SettingsManager} is locked, and any rules in it should therefore not be + * toggleable and its management command should not be available.
+ * @return {@code true} if this {@link SettingsManager} is locked + */ + public boolean locked() { + return locked; + } + + /** + * Adds all annotated fields with the {@link Rule} annotation + * to this {@link SettingsManager} in order to handle them. + * @param settingsClass The class that will be analyzed + */ + public void parseSettingsClass(Class> settingsClass) + { + // In the current translation system languages are not loaded this early. Ensure they are loaded + Translations.updateLanguage(); + boolean warned = settingsClass == CarpetSettings.class; // don't warn for ourselves + + nextRule: for (Field field : settingsClass.getDeclaredFields()) + { + Class extends Rule.Condition>[] conditions; + Rule newAnnotation = field.getAnnotation(Rule.class); + carpet.settings.Rule oldAnnotation = field.getAnnotation(carpet.settings.Rule.class); + if (newAnnotation != null) { + conditions = newAnnotation.conditions(); + } else if (oldAnnotation != null) { + conditions = oldAnnotation.condition(); + if (!warned) { + CarpetSettings.LOG.warn(""" + Registering outdated rules for settings class '%s'! + This won't be supported in the future and rules won't be registered!""".formatted(settingsClass.getName())); + warned = true; + } + } else { + continue; + } + for (Class extends Rule.Condition> condition : conditions) { //Should this be moved to ParsedRule.of? + try + { + Constructor extends Condition> constr = condition.getDeclaredConstructor(); + constr.setAccessible(true); + if (!(constr.newInstance()).shouldRegister()) + continue nextRule; + } + catch (ReflectiveOperationException e) + { + throw new IllegalArgumentException(e); + } + } + CarpetRule> parsed = ParsedRule.of(field, this); + rules.put(parsed.name(), parsed); + } + } + + /** + * @return A String {@link Iterable} with all categories + * that the rules in this {@link SettingsManager} have. + * @implNote This method doesn't cache the result, so each call loops through all rules and finds all present categories + */ + public IterableGets a registered rule in this {@link SettingsManager}.
+ * + * @param name The name of the rule to get + * @return A {@link CarpetRule} with the provided name or {@code null} if none in this {@link SettingsManager} matches + */ + public CarpetRule> getCarpetRule(String name) + { + return rules.get(name); + } + + /** + * @return An unmodifiable {@link Collection} of the registered rules in this {@link SettingsManager}. + */ + public CollectionAdds a {@link CarpetRule} to this {@link SettingsManager}.
+ * + *Useful when having different {@link CarpetRule} implementations instead of a class of {@code static}, + * annotated fields.
+ * + * @param rule The {@link CarpetRule} to add + * @throws UnsupportedOperationException If a rule with that name is already present in this {@link SettingsManager} + */ + public void addCarpetRule(CarpetRule> rule) { + if (rules.containsKey(rule.name())) + throw new UnsupportedOperationException(fancyName + " settings manager already contains a rule with that name!"); + rules.put(rule.name(), rule); + } + + public void notifyRuleChanged(CommandSourceStack source, CarpetRule> rule, String userInput) + { + observers.forEach(observer -> observer.ruleChanged(source, rule, userInput)); + staticObservers.forEach(observer -> observer.ruleChanged(source, rule, userInput)); + ServerNetworkHandler.updateRuleWithConnectedClients(rule); + switchScarpetRuleIfNeeded(source, rule); //TODO move into rule + } + + /** + * Attaches a {@link MinecraftServer} to this {@link SettingsManager}.Initializes Scarpet rules in this {@link SettingsManager}, if any.
+ *This is handled automatically by Carpet and calling it is not supported.
+ */ + public void initializeScarpetRules() { //TODO try remove + for (CarpetRule> rule : rules.values()) + { + if (rule instanceof ParsedRule> pr && !pr.scarpetApp.isEmpty()) { + switchScarpetRuleIfNeeded(server.createCommandSourceStack(), pr); + } + } + } + + /** + * Calling this method is not supported. + */ + public void inspectClientsideCommand(CommandSourceStack source, String string) + { + if (string.startsWith("/" + identifier + " ")) + { + String[] res = string.split("\\s+", 3); + if (res.length == 3) + { + String rule = res[1]; + String strOption = res[2]; + if (rules.containsKey(rule) && rules.get(rule).canBeToggledClientSide()) + { + try { + rules.get(rule).set(source, strOption); + } catch (InvalidRuleValueException e) { + e.notifySource(rule, source); + } + } + } + } + } + + private void switchScarpetRuleIfNeeded(CommandSourceStack source, CarpetRule> carpetRule) //TODO remove. This should be handled by the rule + { + if (carpetRule instanceof ParsedRule> rule && !rule.scarpetApp.isEmpty() && CarpetServer.scriptServer != null) // null check because we may be in server init + { + if (RuleHelper.getBooleanValue(rule) || (rule.type() == String.class && !rule.value().equals("false"))) + { + CarpetServer.scriptServer.addScriptHost(source, rule.scarpetApp, s -> CommandHelper.canUseCommand(s, rule.value()), false, false, true, null); + } else { + CarpetServer.scriptServer.removeScriptHost(source, rule.scarpetApp, false, true); + } + } + } + + private Path getFile() + { + return server.getWorldPath(LevelResource.ROOT).resolve(identifier + ".conf"); + } + + private Collection