diff --git a/build.gradle b/build.gradle index 9fcfdc6b..5795603e 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 archivesBaseName = project.archives_base_name -version = project.minecraft_version+'-'+project.mod_version +version = project.minecraft_version+'-'+project.mod_version+'+v'+new Date().format('yyMMdd') group = project.maven_group minecraft { @@ -15,7 +15,7 @@ minecraft { } dependencies { - //to change the versions see the gradle.properties file + // To change the versions see the gradle.properties file minecraft "com.mojang:minecraft:${project.minecraft_version}" mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" modCompile "net.fabricmc:fabric-loader:${project.loader_version}" @@ -32,7 +32,7 @@ processResources { from(sourceSets.main.resources.srcDirs) { include "fabric.mod.json" - expand "version": project.version + expand "version": project.mod_version } from(sourceSets.main.resources.srcDirs) { @@ -40,23 +40,34 @@ processResources { } } -// 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 -tasks.withType(JavaCompile) { - options.encoding = "UTF-8" + +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" + + // The Minecraft launcher currently installs Java 8 for users, so your mod probably wants to target Java 8 too + // JDK 9 introduced a new way of specifying this that will make sure no newer classes or methods are used. + // We'll use that if it's available, but otherwise we'll use the older option. + def targetVersion = 8 + if (JavaVersion.current().isJava9Compatible()) { + it.options.release = targetVersion + } } -// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task -// if it is present. -// If you remove this task, sources will not be generated. -task sourcesJar(type: Jar, dependsOn: classes) { - classifier = "sources" - from sourceSets.main.allSource +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" + from("LICENSE") { + rename { "${it}_${project.archivesBaseName}"} + } } // configure the maven publication @@ -73,10 +84,10 @@ publishing { } } - // select the repositories you want to publish to + // Select the repositories you want to publish to + // To publish to maven local, no extra repositories are necessary. Just use the task `publishToMavenLocal`. repositories { - // uncomment to publish to the local maven - // mavenLocal() + // 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/Full.md b/docs/scarpet/Full.md index b5aec1b9..0c427544 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -1665,14 +1665,21 @@ split('\\.','foo.bar') => [foo, bar] ### `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 +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(l(0,1,2,3,4,5), 1, 3)  => [1, 2, 3]
+slice([0,1,2,3,4,5], 1, 3)  => [1, 2, 3]
 slice('foobar', 0, 1)  => 'f'
 slice('foobar', 3)  => 'bar'
 slice(range(10), 3, 5)  => [3, 4, 5]
 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 ...)` @@ -2893,7 +2900,10 @@ In this case - entities would need to be re-fetched, or the code should account ### `player(), player(type), player(name)` -With no arguments, it returns the calling player or the player closest to the caller. Note that the main context +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 @@ -4109,8 +4119,10 @@ about players death and applying external effects (like mob anger etc). ### `__on_player_respawns(player)` Triggered when a player respawns. This includes spawning after death, or landing in the overworld after leaving the end. -When the event is handled, a player is still in its previous location and dimension - will be repositioned right after. - +When the event is handled, a player is still in its previous location and dimension - will be repositioned right after. In +case player died, its previous inventory as already been scattered, and its current inventory will not be copied to the respawned +entity, so any manipulation to player data is +best to be scheduled at the end of the tick, but you can still use its current reference to query its status as of the respawn event. ### `__on_player_changes_dimension(player, from_pos, from_dimension, to_pos, to_dimension)` Called when a player moves from one dimension to another. Event is handled still when the player is in its previous @@ -4299,8 +4311,9 @@ With specified `objective` and ### `scoreboard_add(objective, criterion?)` -Adds a new objective to scoreboard. If `criterion` is not specified, assumes `'dummy'`. Returns `false` if objective -already existed, `true` otherwise. +Adds a new objective to scoreboard. If `criterion` is not specified, assumes `'dummy'`. +If the objective already exists, changes the criterion of that objective and returns `false`. If the criterion was not specified but the objective already exists, returns the current criterion. +If the objective was added, returns `true`. If nothing is affected, returns `null`
 scoreboard_add('counter')
@@ -4451,7 +4464,6 @@ for(bossbar(),bossbar(_,'remove'))  -> remove all bossbars
 
 
 
-
 # Auxiliary aspects
 
 Collection of other methods that control smaller, yet still important aspects of the game
@@ -4759,12 +4771,15 @@ 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 - indicting folder listing instead of files
- * `shared_nbt`, `shared_text`, `shared_raw`, `shared_folder` - shared versions of the above
+ * `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. Text files have `.txt` extension, 
+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
@@ -4942,7 +4957,7 @@ it could either mean your input is wrong, or statistic effectively has a value o
 ### `system_info()`, `system_info(property)`
 Fetches the value of a system property or returns all inforation as a map when called without any arguments. It can be used to 
 fetch various information, mostly not changing, or only available via low level
-system calls. In all cirumstances, these are only provided as read-only.
+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
@@ -4960,7 +4975,7 @@ Available options in the scarpet app space:
 
 
  Relevant gameplay related properties
-  * `game_difficulty` - current difficulty of the game: `'peacefu'`, `'easy'`, `'normal'`, or `'hard'`
+  * `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
@@ -4968,6 +4983,7 @@ Available options in the scarpet app space:
   * `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_data_version` - data version of the game. Returns an integer, so it can be compared.
   
  Server related properties
  * `server_motd` - the motd of the server visible when joining
diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md
index 3db7a3df..399d5fc1 100644
--- a/docs/scarpet/api/Auxiliary.md
+++ b/docs/scarpet/api/Auxiliary.md
@@ -305,12 +305,15 @@ 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 - indicting folder listing instead of files
- * `shared_nbt`, `shared_text`, `shared_raw`, `shared_folder` - shared versions of the above
+ * `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. Text files have `.txt` extension, 
+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
@@ -488,7 +491,7 @@ it could either mean your input is wrong, or statistic effectively has a value o
 ### `system_info()`, `system_info(property)`
 Fetches the value of a system property or returns all inforation as a map when called without any arguments. It can be used to 
 fetch various information, mostly not changing, or only available via low level
-system calls. In all cirumstances, these are only provided as read-only.
+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
@@ -506,7 +509,7 @@ Available options in the scarpet app space:
 
 
  Relevant gameplay related properties
-  * `game_difficulty` - current difficulty of the game: `'peacefu'`, `'easy'`, `'normal'`, or `'hard'`
+  * `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
@@ -514,6 +517,7 @@ Available options in the scarpet app space:
   * `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_data_version` - data version of the game. Returns an integer, so it can be compared.
   
  Server related properties
  * `server_motd` - the motd of the server visible when joining
diff --git a/docs/scarpet/api/Entities.md b/docs/scarpet/api/Entities.md
index 34584e8b..bbd2872e 100644
--- a/docs/scarpet/api/Entities.md
+++ b/docs/scarpet/api/Entities.md
@@ -8,7 +8,10 @@ In this case - entities would need to be re-fetched, or the code should account
 
 ### `player(), player(type), player(name)`
 
-With no arguments, it returns the calling player or the player closest to the caller. Note that the main context 
+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 
@@ -243,6 +246,10 @@ Boolean, true if the entity is silent.
 
 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.
@@ -723,6 +730,10 @@ Silences or unsilences the entity.
 
 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.
diff --git a/docs/scarpet/api/Events.md b/docs/scarpet/api/Events.md
index ac3da637..e3888543 100644
--- a/docs/scarpet/api/Events.md
+++ b/docs/scarpet/api/Events.md
@@ -160,8 +160,10 @@ about players death and applying external effects (like mob anger etc).
 
 ### `__on_player_respawns(player)`
 Triggered when a player respawns. This includes spawning after death, or landing in the overworld after leaving the end. 
-When the event is handled, a player is still in its previous location and dimension - will be repositioned right after.
-
+When the event is handled, a player is still in its previous location and dimension - will be repositioned right after. In 
+case player died, its previous inventory as already been scattered, and its current inventory will not be copied to the respawned
+entity, so any manipulation to player data is
+best to be scheduled at the end of the tick, but you can still use its current reference to query its status as of the respawn event.
 
 ### `__on_player_changes_dimension(player, from_pos, from_dimension, to_pos, to_dimension)`
 Called when a player moves from one dimension to another. Event is handled still when the player is in its previous
diff --git a/docs/scarpet/api/Scoreboard.md b/docs/scarpet/api/Scoreboard.md
index 36903292..826c5328 100644
--- a/docs/scarpet/api/Scoreboard.md
+++ b/docs/scarpet/api/Scoreboard.md
@@ -10,8 +10,9 @@ With specified `objective` and
  
 ### `scoreboard_add(objective, criterion?)`
 
-Adds a new objective to scoreboard. If `criterion` is not specified, assumes `'dummy'`. Returns `false` if objective 
-already existed, `true` otherwise.
+Adds a new objective to scoreboard. If `criterion` is not specified, assumes `'dummy'`.
+If the objective already exists, changes the criterion of that objective and returns `false`. If the criterion was not specified but the objective already exists, returns the current criterion.
+If the objective was added, returns `true`. If nothing is affected, returns `null`
 
 
 scoreboard_add('counter')
@@ -162,4 +163,3 @@ for(bossbar(),bossbar(_,'remove'))  -> remove all bossbars
 
 
 
-
diff --git a/docs/scarpet/language/Containers.md b/docs/scarpet/language/Containers.md
index 8b07497c..f017adaf 100644
--- a/docs/scarpet/language/Containers.md
+++ b/docs/scarpet/language/Containers.md
@@ -170,14 +170,21 @@ split('\\.','foo.bar')  => [foo, bar]
 ### `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
+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(l(0,1,2,3,4,5), 1, 3)  => [1, 2, 3]
+slice([0,1,2,3,4,5], 1, 3)  => [1, 2, 3]
 slice('foobar', 0, 1)  => 'f'
 slice('foobar', 3)  => 'bar'
 slice(range(10), 3, 5)  => [3, 4, 5]
 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 ...)` diff --git a/gradle.properties b/gradle.properties index ed329661..3d8df639 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ org.gradle.jvmargs=-Xmx1G loader_version=0.10.8 # Mod Properties - mod_version = 1.4.21+v201216 + mod_version = 1.4.21 maven_group = carpet archives_base_name = fabric-carpet diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf0..e708b1c0 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4b7e1f3d..be52383e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 8e25e6c1..cdc5fd53 100755 --- a/gradlew +++ b/gradlew @@ -125,10 +125,11 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath @@ -154,19 +155,19 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -175,14 +176,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 9618d8d9..107acd32 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @@ -37,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -51,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -61,28 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/src/main/java/carpet/CarpetServer.java b/src/main/java/carpet/CarpetServer.java index 773b61e3..c8cae167 100644 --- a/src/main/java/carpet/CarpetServer.java +++ b/src/main/java/carpet/CarpetServer.java @@ -109,7 +109,7 @@ public class CarpetServer implements ClientModInitializer,DedicatedServerModInit public static void tick(MinecraftServer server) { - TickSpeed.tick(server); + TickSpeed.tick(); HUDController.update_hud(server); scriptServer.tick(); diff --git a/src/main/java/carpet/commands/TickCommand.java b/src/main/java/carpet/commands/TickCommand.java index 27c6ef27..8b6b3826 100644 --- a/src/main/java/carpet/commands/TickCommand.java +++ b/src/main/java/carpet/commands/TickCommand.java @@ -2,6 +2,7 @@ package carpet.commands; import carpet.CarpetSettings; import carpet.helpers.TickSpeed; +import carpet.network.ServerNetworkHandler; import carpet.settings.SettingsManager; import carpet.utils.CarpetProfiler; import carpet.utils.Messenger; @@ -95,16 +96,13 @@ public class TickCommand private static int toggleFreeze(ServerCommandSource source, boolean isDeep) { - TickSpeed.is_paused = !TickSpeed.is_paused; - if (TickSpeed.is_paused) + TickSpeed.setFrozenState(!TickSpeed.isPaused(), isDeep); + if (TickSpeed.isPaused()) { - TickSpeed.deepFreeze = isDeep; Messenger.m(source, "gi Game is "+(isDeep?"deeply ":"")+"frozen"); - } else { - TickSpeed.deepFreeze = false; Messenger.m(source, "gi Game runs normally"); } return 1; @@ -119,9 +117,10 @@ public class TickCommand private static int toggleSuperHot(ServerCommandSource source) { TickSpeed.is_superHot = !TickSpeed.is_superHot; + ServerNetworkHandler.updateSuperHotStateToConnectedPlayers(); if (TickSpeed.is_superHot) { - Messenger.m(source,"gi Superhot enabled"); + Messenger.m(source, "gi Superhot enabled"); } else { diff --git a/src/main/java/carpet/fakes/ServerPlayerEntityInterface.java b/src/main/java/carpet/fakes/ServerPlayerEntityInterface.java index 1d7aa428..9fd69a30 100644 --- a/src/main/java/carpet/fakes/ServerPlayerEntityInterface.java +++ b/src/main/java/carpet/fakes/ServerPlayerEntityInterface.java @@ -5,4 +5,7 @@ import carpet.helpers.EntityPlayerActionPack; public interface ServerPlayerEntityInterface { EntityPlayerActionPack getActionPack(); + void invalidateEntityObjectReference(); + boolean isInvalidEntityObject(); + } diff --git a/src/main/java/carpet/helpers/TickSpeed.java b/src/main/java/carpet/helpers/TickSpeed.java index 575867eb..a71301fb 100644 --- a/src/main/java/carpet/helpers/TickSpeed.java +++ b/src/main/java/carpet/helpers/TickSpeed.java @@ -7,7 +7,6 @@ import java.util.function.BiConsumer; import carpet.CarpetServer; import carpet.network.ServerNetworkHandler; import carpet.utils.Messenger; -import net.minecraft.server.MinecraftServer; import net.minecraft.server.command.CommandManager; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; @@ -26,10 +25,43 @@ public class TickSpeed public static ServerCommandSource tick_warp_sender = null; public static int player_active_timeout = 0; public static boolean process_entities = true; - public static boolean deepFreeze = false; - public static boolean is_paused = false; + private static boolean deepFreeze = false; + private static boolean is_paused = false; public static boolean is_superHot = false; + /** + * @return Whether or not the game is in a frozen state. + * You should normally use {@link #process_entities} instead, + * since that one accounts for tick steps and superhot + */ + public static boolean isPaused() { + return is_paused; + } + + /** + * Whether or not the game is deeply frozen. + * This can be used for things that you may not normally want + * to freeze, but may need to in some situations. + * This should be checked with {@link #process_entities} to make sure the + * current tick is actually frozen, not only the game + * @return Whether or not the game is deeply frozen. + */ + public static boolean deeplyFrozen() { + return deepFreeze; + } + + /** + * Used to update the frozen state of the game. + * Handles connected clients as well. + * @param isPaused Whether or not the game is paused + * @param isDeepFreeze Whether or not the game is deeply frozen + */ + public static void setFrozenState(boolean isPaused, boolean isDeepFreeze) { + is_paused = isPaused; + deepFreeze = isPaused ? isDeepFreeze : false; + ServerNetworkHandler.updateFrozenStateToConnectedPlayers(); + } + /** * Functional interface that listens for tickrate changes. This is * implemented to allow tickrate compatibility with other mods etc. @@ -66,6 +98,7 @@ public class TickSpeed public static void add_ticks_to_run_in_pause(int ticks) { player_active_timeout = PLAYER_GRACE+ticks; + ServerNetworkHandler.updateTickPlayerActiveTimeoutToConnectedPlayers(); } public static BaseText tickrate_advance(ServerPlayerEntity player, int advance, String callback, ServerCommandSource source) @@ -155,7 +188,7 @@ public class TickSpeed } } - public static void tick(MinecraftServer server) + public static void tick() { process_entities = true; if (player_active_timeout > 0) diff --git a/src/main/java/carpet/mixins/ChunkTicketManager_tickMixin.java b/src/main/java/carpet/mixins/ChunkTicketManager_tickMixin.java index 9b70b707..bbaeea46 100644 --- a/src/main/java/carpet/mixins/ChunkTicketManager_tickMixin.java +++ b/src/main/java/carpet/mixins/ChunkTicketManager_tickMixin.java @@ -18,6 +18,6 @@ public class ChunkTicketManager_tickMixin { // pausing expiry of tickets // that will prevent also chunks from unloading, so require a deep frozen state - if (!TickSpeed.process_entities && TickSpeed.deepFreeze) age--; + if (!TickSpeed.process_entities && TickSpeed.deeplyFrozen()) age--; } } diff --git a/src/main/java/carpet/mixins/CommandFunctionManager_tickMixin.java b/src/main/java/carpet/mixins/CommandFunctionManager_tickMixin.java index 301a05d5..034f06db 100644 --- a/src/main/java/carpet/mixins/CommandFunctionManager_tickMixin.java +++ b/src/main/java/carpet/mixins/CommandFunctionManager_tickMixin.java @@ -1,5 +1,6 @@ package carpet.mixins; +import carpet.helpers.TickSpeed; import carpet.utils.CarpetProfiler; import net.minecraft.server.function.CommandFunctionManager; import org.spongepowered.asm.mixin.Mixin; @@ -12,9 +13,11 @@ public class CommandFunctionManager_tickMixin { CarpetProfiler.ProfilerToken currentSection; - @Inject(method = "tick", at = @At("HEAD")) + @Inject(method = "tick", at = @At("HEAD"), cancellable = true) private void beforeDatapacks(CallbackInfo ci) { + if (!TickSpeed.process_entities) ci.cancel(); + else currentSection = CarpetProfiler.start_section(null, "Datapacks", CarpetProfiler.TYPE.GENERAL); } diff --git a/src/main/java/carpet/mixins/MinecraftClientMixin.java b/src/main/java/carpet/mixins/MinecraftClientMixin.java index 7e94395e..d0a340a5 100644 --- a/src/main/java/carpet/mixins/MinecraftClientMixin.java +++ b/src/main/java/carpet/mixins/MinecraftClientMixin.java @@ -1,9 +1,13 @@ package carpet.mixins; +import carpet.CarpetServer; +import carpet.helpers.TickSpeed; import carpet.network.CarpetClient; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.world.ClientWorld; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -11,9 +15,21 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(MinecraftClient.class) public class MinecraftClientMixin { + @Shadow public ClientWorld world; + @Inject(method = "disconnect(Lnet/minecraft/client/gui/screen/Screen;)V", at = @At("HEAD")) private void onCloseGame(Screen screen, CallbackInfo ci) { CarpetClient.disconnect(); } + + @Inject(at = @At("HEAD"), method = "tick") + private void onClientTick(CallbackInfo info) { + if (this.world != null) { + if (CarpetServer.minecraft_server == null) + TickSpeed.tick(); + if (!TickSpeed.process_entities) + CarpetClient.shapes.renewShapes(); + } + } } diff --git a/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java b/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java index 705a3e1e..a4934a7f 100644 --- a/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java +++ b/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java @@ -1,6 +1,7 @@ package carpet.mixins; import carpet.fakes.MinecraftServerInterface; +import carpet.helpers.TickSpeed; import net.minecraft.server.MinecraftServer; import net.minecraft.server.ServerTask; import net.minecraft.server.world.ServerWorld; @@ -69,6 +70,8 @@ public abstract class MinecraftServer_scarpetMixin extends ReentrantThreadExecut )) public void tickTasks(BooleanSupplier booleanSupplier_1, CallbackInfo ci) { + if (!TickSpeed.process_entities) + return; TICK.onTick(); NETHER_TICK.onTick(); ENDER_TICK.onTick(); diff --git a/src/main/java/carpet/mixins/PlayerManager_scarpetEventsMixin.java b/src/main/java/carpet/mixins/PlayerManager_scarpetEventsMixin.java index 6911f8f1..0d2d1c96 100644 --- a/src/main/java/carpet/mixins/PlayerManager_scarpetEventsMixin.java +++ b/src/main/java/carpet/mixins/PlayerManager_scarpetEventsMixin.java @@ -1,5 +1,6 @@ package carpet.mixins; +import carpet.fakes.ServerPlayerEntityInterface; import net.minecraft.server.PlayerManager; import net.minecraft.server.network.ServerPlayerEntity; import org.spongepowered.asm.mixin.Mixin; @@ -17,4 +18,11 @@ public class PlayerManager_scarpetEventsMixin { PLAYER_RESPAWNS.onPlayerEvent(player); } + + @Inject(method = "respawnPlayer", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerPlayerEntity;onSpawn()V")) + private void invalidatePreviousInstance(ServerPlayerEntity player, boolean alive, CallbackInfoReturnable cir) + { + ((ServerPlayerEntityInterface)player).invalidateEntityObjectReference(); + } + } diff --git a/src/main/java/carpet/mixins/ScoreboardObjective_scarpetMixin.java b/src/main/java/carpet/mixins/ScoreboardObjective_scarpetMixin.java new file mode 100644 index 00000000..a7506dde --- /dev/null +++ b/src/main/java/carpet/mixins/ScoreboardObjective_scarpetMixin.java @@ -0,0 +1,12 @@ +package carpet.mixins; + +import net.minecraft.scoreboard.ScoreboardCriterion; +import net.minecraft.scoreboard.ScoreboardObjective; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(ScoreboardObjective.class) +public interface ScoreboardObjective_scarpetMixin { + @Accessor("criterion") + void setCriterion(ScoreboardCriterion criterion); +} diff --git a/src/main/java/carpet/mixins/Scoreboard_scarpetMixin.java b/src/main/java/carpet/mixins/Scoreboard_scarpetMixin.java new file mode 100644 index 00000000..646e2ee7 --- /dev/null +++ b/src/main/java/carpet/mixins/Scoreboard_scarpetMixin.java @@ -0,0 +1,16 @@ +package carpet.mixins; + +import net.minecraft.scoreboard.Scoreboard; +import net.minecraft.scoreboard.ScoreboardCriterion; +import net.minecraft.scoreboard.ScoreboardObjective; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.List; +import java.util.Map; + +@Mixin(Scoreboard.class) +public interface Scoreboard_scarpetMixin { + @Accessor("objectivesByCriterion") + Map> getObjectivesByCriterion(); +} diff --git a/src/main/java/carpet/mixins/ServerPlayerEntity_scarpetEventMixin.java b/src/main/java/carpet/mixins/ServerPlayerEntity_scarpetEventMixin.java index f53ad308..511d56dc 100644 --- a/src/main/java/carpet/mixins/ServerPlayerEntity_scarpetEventMixin.java +++ b/src/main/java/carpet/mixins/ServerPlayerEntity_scarpetEventMixin.java @@ -1,6 +1,7 @@ package carpet.mixins; import carpet.fakes.EntityInterface; +import carpet.fakes.ServerPlayerEntityInterface; import carpet.script.EntityEventsGroup; import com.mojang.authlib.GameProfile; import net.minecraft.entity.Entity; @@ -28,8 +29,11 @@ import static carpet.script.CarpetEventServer.Event.PLAYER_FINISHED_USING_ITEM; import static carpet.script.CarpetEventServer.Event.STATISTICS; @Mixin(ServerPlayerEntity.class) -public abstract class ServerPlayerEntity_scarpetEventMixin extends PlayerEntity +public abstract class ServerPlayerEntity_scarpetEventMixin extends PlayerEntity implements ServerPlayerEntityInterface { + // to denote if the player reference is valid + + private boolean isInvalidReference = false; public ServerPlayerEntity_scarpetEventMixin(World world, BlockPos blockPos, float f, GameProfile gameProfile) { @@ -109,6 +113,17 @@ public abstract class ServerPlayerEntity_scarpetEventMixin extends PlayerEntity } PLAYER_CHANGES_DIMENSION.onDimensionChange(player, previousLocation, to, previousDimension, destination.getRegistryKey()); } + }; + + @Override + public void invalidateEntityObjectReference() + { + isInvalidReference = true; } + @Override + public boolean isInvalidEntityObject() + { + return isInvalidReference; + } } diff --git a/src/main/java/carpet/network/ClientNetworkHandler.java b/src/main/java/carpet/network/ClientNetworkHandler.java index 4d3cc1ad..0d60acc0 100644 --- a/src/main/java/carpet/network/ClientNetworkHandler.java +++ b/src/main/java/carpet/network/ClientNetworkHandler.java @@ -9,6 +9,7 @@ import carpet.settings.SettingsManager; import io.netty.buffer.Unpooled; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.nbt.AbstractNumberTag; +import net.minecraft.nbt.ByteTag; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.Tag; @@ -65,6 +66,14 @@ public class ClientNetworkHandler } }); dataHandlers.put("TickRate", (p, t) -> TickSpeed.tickrate(((AbstractNumberTag)t).getFloat(), false)); + dataHandlers.put("TickingState", (p, t) -> { + CompoundTag tickingState = (CompoundTag)t; + TickSpeed.setFrozenState(tickingState.getBoolean("is_paused"), tickingState.getBoolean("deepFreeze")); + }); + dataHandlers.put("SuperHotState", (p, t) -> { + TickSpeed.is_superHot = ((ByteTag) t).equals(ByteTag.ONE); + }); + dataHandlers.put("TickPlayerActiveTimeout", (p, t) -> TickSpeed.player_active_timeout = ((AbstractNumberTag)t).getInt()); dataHandlers.put("scShape", (p, t) -> { // deprecated if (CarpetClient.shapes != null) CarpetClient.shapes.addShape((CompoundTag)t); diff --git a/src/main/java/carpet/network/ServerNetworkHandler.java b/src/main/java/carpet/network/ServerNetworkHandler.java index 72ffc14f..f5959561 100644 --- a/src/main/java/carpet/network/ServerNetworkHandler.java +++ b/src/main/java/carpet/network/ServerNetworkHandler.java @@ -77,7 +77,7 @@ public class ServerNetworkHandler CarpetSettings.LOG.info("Player "+playerEntity.getName().getString()+" joined with a matching carpet client"); else CarpetSettings.LOG.warn("Player "+playerEntity.getName().getString()+" joined with another carpet version: "+clientVersion); - DataBuilder data = DataBuilder.create().withTickRate(); + DataBuilder data = DataBuilder.create().withTickRate().withFrozenState().withTickPlayerActiveTimeout(); // .withSuperHotState() CarpetServer.settingsManager.getRules().forEach(data::withRule); CarpetServer.extensions.forEach(e -> { SettingsManager eManager = e.customSettingsManager(); @@ -173,6 +173,42 @@ public class ServerNetworkHandler } } + public static void updateFrozenStateToConnectedPlayers() + { + if (CarpetSettings.superSecretSetting) return; + for (ServerPlayerEntity player : remoteCarpetPlayers.keySet()) + { + player.networkHandler.sendPacket(new CustomPayloadS2CPacket( + CarpetClient.CARPET_CHANNEL, + DataBuilder.create().withFrozenState().build() + )); + } + } + + public static void updateSuperHotStateToConnectedPlayers() + { + if(CarpetSettings.superSecretSetting) return; + for (ServerPlayerEntity player : remoteCarpetPlayers.keySet()) + { + player.networkHandler.sendPacket(new CustomPayloadS2CPacket( + CarpetClient.CARPET_CHANNEL, + DataBuilder.create().withSuperHotState().build() + )); + } + } + + public static void updateTickPlayerActiveTimeoutToConnectedPlayers() + { + if (CarpetSettings.superSecretSetting) return; + for (ServerPlayerEntity player : remoteCarpetPlayers.keySet()) + { + player.networkHandler.sendPacket(new CustomPayloadS2CPacket( + CarpetClient.CARPET_CHANNEL, + DataBuilder.create().withTickPlayerActiveTimeout().build() + )); + } + } + public static void broadcastCustomCommand(String command, Tag data) { if (CarpetSettings.superSecretSetting) return; @@ -240,6 +276,24 @@ public class ServerNetworkHandler tag.putFloat("TickRate", TickSpeed.tickrate); return this; } + private DataBuilder withFrozenState() + { + CompoundTag tickingState = new CompoundTag(); + tickingState.putBoolean("is_paused", TickSpeed.isPaused()); + tickingState.putBoolean("deepFreeze", TickSpeed.deeplyFrozen()); + tag.put("TickingState", tickingState); + return this; + } + private DataBuilder withSuperHotState() + { + tag.putBoolean("SuperHotState", TickSpeed.is_superHot); + return this; + } + private DataBuilder withTickPlayerActiveTimeout() + { + tag.putInt("TickPlayerActiveTimeout", TickSpeed.player_active_timeout); + return this; + } private DataBuilder withRule(ParsedRule rule) { CompoundTag rules = (CompoundTag) tag.get("Rules"); diff --git a/src/main/java/carpet/script/CarpetEventServer.java b/src/main/java/carpet/script/CarpetEventServer.java index fe3a8c3a..7bcab974 100644 --- a/src/main/java/carpet/script/CarpetEventServer.java +++ b/src/main/java/carpet/script/CarpetEventServer.java @@ -2,6 +2,7 @@ package carpet.script; import carpet.CarpetServer; import carpet.CarpetSettings; +import carpet.helpers.TickSpeed; import carpet.script.exception.InternalExpressionException; import carpet.script.value.BlockValue; import carpet.script.value.EntityValue; @@ -973,6 +974,8 @@ public class CarpetEventServer public void tick() { + if (!TickSpeed.process_entities) + return; Iterator eventIterator = scheduledCalls.iterator(); List currentCalls = new ArrayList<>(); while(eventIterator.hasNext()) diff --git a/src/main/java/carpet/script/CarpetScriptHost.java b/src/main/java/carpet/script/CarpetScriptHost.java index 986f591a..05f3ce3e 100644 --- a/src/main/java/carpet/script/CarpetScriptHost.java +++ b/src/main/java/carpet/script/CarpetScriptHost.java @@ -18,6 +18,8 @@ import carpet.script.value.NumericValue; import carpet.script.value.StringValue; import carpet.script.value.Value; import carpet.utils.Messenger; + +import com.google.gson.JsonElement; import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; @@ -705,7 +707,7 @@ public class CarpetScriptHost extends ScriptHost public boolean removeResourceFile(String resource, boolean isShared, String type) { if (getName() == null && !isShared) return false; // - return Module.dropExistingFile(main, resource, type.equals("nbt")?"nbt":"txt", isShared); + return Module.dropExistingFile(main, resource, type, isShared); } public boolean appendLogFile(String resource, boolean isShared, String type, List data) @@ -714,16 +716,22 @@ public class CarpetScriptHost extends ScriptHost return Module.appendToTextFile(main, resource, type, isShared, data); } - public List readTextResource(String resource, boolean isShared) + public List readTextResource(String resource, String type, boolean isShared) { - if (getName() == null && !isShared) return null; // - return Module.listFile(main, resource, "txt", isShared); + if (getName() == null && !isShared) return null; + return Module.listFile(main, resource, type, isShared); + } + + public JsonElement readJsonFile(String resource, String type, boolean isShared) + { + if (getName() == null && !isShared) return null; + return Module.readJsonFile(main, resource, type, isShared); } - public Stream listFolder(String resource, String ext, boolean isShared) + public Stream listFolder(String resource, String type, boolean isShared) { if (getName() == null && !isShared) return null; // - return Module.listFolder(main, resource, ext, isShared); + return Module.listFolder(main, resource, type, isShared); } diff --git a/src/main/java/carpet/script/ScriptHost.java b/src/main/java/carpet/script/ScriptHost.java index d984673f..a59f62aa 100644 --- a/src/main/java/carpet/script/ScriptHost.java +++ b/src/main/java/carpet/script/ScriptHost.java @@ -91,7 +91,7 @@ public abstract class ScriptHost protected ScriptHost parent; protected boolean perUser; - protected String user; + public String user; public String getName() {return main ==null?null: main.getName();} diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java index 4be49ac0..cd1c145d 100644 --- a/src/main/java/carpet/script/api/Auxiliary.java +++ b/src/main/java/carpet/script/api/Auxiliary.java @@ -6,6 +6,7 @@ import carpet.fakes.MinecraftServerInterface; import carpet.fakes.StatTypeInterface; import carpet.fakes.ThreadedAnvilChunkStorageInterface; import carpet.helpers.FeatureGenerator; +import carpet.script.bundled.Module; import carpet.script.CarpetContext; import carpet.script.CarpetEventServer; import carpet.script.CarpetScriptHost; @@ -19,6 +20,7 @@ import carpet.script.argument.Vector3Argument; import carpet.script.exception.ExitStatement; import carpet.script.exception.InternalExpressionException; import carpet.script.utils.FixedCommandSource; +import carpet.script.utils.ScarpetJsonDeserializer; import carpet.script.utils.ShapeDispatcher; import carpet.script.utils.WorldTools; import carpet.script.value.EntityValue; @@ -33,7 +35,6 @@ import carpet.script.value.StringValue; import carpet.script.value.Value; import carpet.script.value.ValueConversions; import carpet.utils.Messenger; -import com.google.common.collect.ImmutableMap; import net.minecraft.block.BlockState; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; @@ -69,6 +70,11 @@ import net.minecraft.world.World; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -86,12 +92,7 @@ import static java.lang.Math.min; public class Auxiliary { public static final String MARKER_STRING = "__scarpet_marker"; private static final Map mixerMap = Arrays.stream(SoundCategory.values()).collect(Collectors.toMap(SoundCategory::getName, k -> k)); - private static final Map supportedTypes = ImmutableMap.of( - "raw", ".txt", - "text", ".txt", - "nbt", ".nbt", - "folder", "folder" - ); + public static final Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().registerTypeAdapter(Value.class, new ScarpetJsonDeserializer()).create(); public static String recognizeResource(Value value, boolean isFloder) { @@ -112,7 +113,7 @@ public class Auxiliary { String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT); boolean shared = origtype.startsWith("shared_"); String type = shared ? origtype.substring(7) : origtype; //len(shared_) - if (!supportedTypes.containsKey(type)) + if (!Module.supportedTypes.containsKey(type)) throw new InternalExpressionException("Unsupported file type: "+origtype); if (type.equals("folder") && !isFloder) throw new InternalExpressionException("Folder types are no supported for this IO function"); @@ -526,7 +527,7 @@ public class Auxiliary { Text title = null; if (lv.size() > 2) { - pVal = lv.get(2).evalValue(c); + pVal = lv.get(2).evalValue(c); if (pVal instanceof FormattedTextValue) title = ((FormattedTextValue) pVal).getText(); else @@ -803,7 +804,7 @@ public class Auxiliary { expression.addLazyFunction("list_files", 2, (c, t, lv) -> { Triple fdesc = getFileDescriptor(lv, c, true); - Stream files = ((CarpetScriptHost) c.host).listFolder(fdesc.getLeft(), supportedTypes.get(fdesc.getMiddle()), fdesc.getRight()); + Stream files = ((CarpetScriptHost) c.host).listFolder(fdesc.getLeft(), fdesc.getMiddle(), fdesc.getRight()); if (files == null) return LazyValue.NULL; Value ret = ListValue.wrap(files.map(StringValue::of).collect(Collectors.toList())); return (cc, tt) -> ret; @@ -820,9 +821,29 @@ public class Auxiliary { if (state == null) return LazyValue.NULL; retVal = new NBTSerializableValue(state); } + else if (fdesc.getMiddle().equals("json")) + { + JsonElement json; + try + { + json = ((CarpetScriptHost) c.host).readJsonFile(fdesc.getLeft(), fdesc.getMiddle(), fdesc.getRight()); + } + catch (JsonParseException e) + { + Throwable exception = e; + if(e.getCause() != null) + exception = e.getCause(); + throw new InternalExpressionException("Failed to read JSON file: "+exception.getMessage()); + } + Value parsedJson = gson.fromJson(json, Value.class); + if (parsedJson == null) + retVal = Value.NULL; + else + retVal = parsedJson; + } else { - List content = ((CarpetScriptHost) c.host).readTextResource(fdesc.getLeft(), fdesc.getRight()); + List content = ((CarpetScriptHost) c.host).readTextResource(fdesc.getLeft(), fdesc.getMiddle(), fdesc.getRight()); if (content == null) return LazyValue.NULL; retVal = ListValue.wrap(content.stream().map(StringValue::new).collect(Collectors.toList())); } @@ -851,6 +872,12 @@ public class Auxiliary { Tag tag = tagValue.getTag(); success = ((CarpetScriptHost) c.host).writeTagFile(tag, fdesc.getLeft(), fdesc.getRight()); } + else if (fdesc.getMiddle().equals("json")) + { + List data = Collections.singletonList(gson.toJson(lv.get(2).evalValue(c).toJson())); + ((CarpetScriptHost) c.host).removeResourceFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle()); + success = ((CarpetScriptHost) c.host).appendLogFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle(), data); + } else { List data = new ArrayList<>(); diff --git a/src/main/java/carpet/script/api/Entities.java b/src/main/java/carpet/script/api/Entities.java index 0c413e3a..3c025803 100644 --- a/src/main/java/carpet/script/api/Entities.java +++ b/src/main/java/carpet/script/api/Entities.java @@ -45,10 +45,17 @@ public class Entities { { expression.addLazyFunction("player", -1, (c, t, lv) -> { - if (lv.size() ==0) + if (lv.size() == 0) { - - Entity callingEntity = ((CarpetContext)c).s.getEntity(); + CarpetContext cc = (CarpetContext)c; + if (cc.host.user != null) + { + ServerPlayerEntity player = cc.s.getMinecraftServer().getPlayerManager().getPlayer(cc.host.user); + if (player == null) return LazyValue.NULL; + Value ret = new EntityValue(player); + return (_c, _t) -> ret; + } + Entity callingEntity = cc.s.getEntity(); if (callingEntity instanceof PlayerEntity) { Value retval = new EntityValue(callingEntity); @@ -108,7 +115,7 @@ public class Entities { retval = new EntityValue(player); } Value finalVar = retval; - return (cc, tt) -> finalVar; + return (_c, _t) -> finalVar; }); expression.addLazyFunction("spawn", -1, (c, t, lv) -> diff --git a/src/main/java/carpet/script/api/Scoreboards.java b/src/main/java/carpet/script/api/Scoreboards.java index 77b5e1c8..ac115283 100644 --- a/src/main/java/carpet/script/api/Scoreboards.java +++ b/src/main/java/carpet/script/api/Scoreboards.java @@ -1,5 +1,7 @@ package carpet.script.api; +import carpet.mixins.ScoreboardObjective_scarpetMixin; +import carpet.mixins.Scoreboard_scarpetMixin; import carpet.script.CarpetContext; import carpet.script.Expression; import carpet.script.LazyValue; @@ -11,6 +13,7 @@ import carpet.script.value.NumericValue; import carpet.script.value.StringValue; import carpet.script.value.FormattedTextValue; import carpet.script.value.Value; +import com.google.common.collect.Lists; import net.minecraft.entity.Entity; import net.minecraft.entity.boss.BossBar; import net.minecraft.entity.boss.BossBarManager; @@ -133,8 +136,15 @@ public class Scoreboards { } ScoreboardObjective objective = scoreboard.getObjective(objectiveName); - if (objective != null) + if (objective != null) { + if(lv.size() == 1) return (_c, _t) -> StringValue.of(objective.getCriterion().getName()); + if(objective.getCriterion().equals(criterion) || lv.size() == 1) return LazyValue.NULL; + ((Scoreboard_scarpetMixin)scoreboard).getObjectivesByCriterion().get(objective.getCriterion()).remove(objective); + ((ScoreboardObjective_scarpetMixin) objective).setCriterion(criterion); + (((Scoreboard_scarpetMixin)scoreboard).getObjectivesByCriterion().computeIfAbsent(criterion, (criterion1) -> Lists.newArrayList())).add(objective); + scoreboard.updateObjective(objective); return LazyValue.FALSE; + } scoreboard.addObjective(objectiveName, criterion, new LiteralText(objectiveName), criterion.getDefaultRenderType()); return LazyValue.TRUE; diff --git a/src/main/java/carpet/script/bundled/FileModule.java b/src/main/java/carpet/script/bundled/FileModule.java index b710b376..8c32034e 100644 --- a/src/main/java/carpet/script/bundled/FileModule.java +++ b/src/main/java/carpet/script/bundled/FileModule.java @@ -25,6 +25,10 @@ import java.util.List; import java.util.Locale; import java.util.stream.Stream; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; + public class FileModule extends Module { private String name; @@ -137,11 +141,11 @@ public class FileModule extends Module return true; } - public static boolean appendText(File filePath, boolean addNewLines, List data) + public static boolean appendText(Path filePath, boolean addNewLines, List data) { try { - OutputStream out = Files.newOutputStream(filePath.toPath(), StandardOpenOption.APPEND, StandardOpenOption.CREATE); + OutputStream out = Files.newOutputStream(filePath, StandardOpenOption.APPEND, StandardOpenOption.CREATE); try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))) { for (String line: data) @@ -159,9 +163,9 @@ public class FileModule extends Module } - public static List listFileContent(File filePath) + public static List listFileContent(Path filePath) { - try (BufferedReader reader = Files.newBufferedReader(filePath.toPath(), StandardCharsets.UTF_8)) { + try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) { List result = new ArrayList<>(); for (;;) { String line = reader.readLine(); @@ -176,6 +180,18 @@ public class FileModule extends Module return null; } } + + public static JsonElement readJsonContent(Path filePath) + { + try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) + { + return new JsonParser().parse(new JsonReader(reader)); + } + catch (IOException e) + { + return null; + } + } public static Stream listFiles(Path dir, String ext) { diff --git a/src/main/java/carpet/script/bundled/Module.java b/src/main/java/carpet/script/bundled/Module.java index 3bf5f8ce..4a352e8e 100644 --- a/src/main/java/carpet/script/bundled/Module.java +++ b/src/main/java/carpet/script/bundled/Module.java @@ -5,9 +5,13 @@ import net.minecraft.nbt.Tag; import net.minecraft.util.WorldSavePath; import org.apache.commons.io.FilenameUtils; +import com.google.common.collect.ImmutableMap; +import com.google.gson.JsonElement; + import java.nio.file.Files; import java.nio.file.Path; import java.util.List; +import java.util.Map; import java.util.stream.Stream; public abstract class Module @@ -16,6 +20,13 @@ public abstract class Module public abstract String getCode(); public abstract boolean isLibrary(); public final static Object writeIOSync = new Object(); + public final static Map supportedTypes = ImmutableMap.of( + "raw", ".txt", + "text", ".txt", + "nbt", ".nbt", + "json", ".json", + "folder", "folder" + ); private static String getDescriptor(Module module, String file, boolean isShared) { @@ -32,7 +43,7 @@ public abstract class Module public static Tag getData(Module module, String file, boolean isShared) { - Path dataFile = resolveResource(module, file, "nbt", isShared); + Path dataFile = resolveResource(module, file, ".nbt", isShared); if (dataFile == null) return null; if (!Files.exists(dataFile) || !(dataFile.toFile().isFile())) return null; synchronized (writeIOSync) { return FileModule.read(dataFile.toFile()); } @@ -40,7 +51,7 @@ public abstract class Module public static boolean saveData(Module module, String file, Tag globalState, boolean isShared) { - Path dataFile = resolveResource(module, file, "nbt", isShared); + Path dataFile = resolveResource(module, file, ".nbt", isShared); if (dataFile == null) return false; if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false; synchronized (writeIOSync) { return FileModule.write(globalState, dataFile.toFile()); } @@ -48,30 +59,38 @@ public abstract class Module public static boolean appendToTextFile(Module module, String resourceName, String type, boolean isShared, List message) { - Path dataFile = resolveResource(module, resourceName, "txt", isShared); + Path dataFile = resolveResource(module, resourceName, supportedTypes.get(type), isShared); if (dataFile == null) return false; if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false; - synchronized (writeIOSync) { return FileModule.appendText(dataFile.toFile(), type.equals("text"), message); } + synchronized (writeIOSync) { return FileModule.appendText(dataFile, type.equals("text"), message); } } - public static boolean dropExistingFile(Module module, String resourceName, String ext, boolean isShared) + public static boolean dropExistingFile(Module module, String resourceName, String type, boolean isShared) { - Path dataFile = resolveResource(module, resourceName, ext, isShared); + Path dataFile = resolveResource(module, resourceName, supportedTypes.get(type), isShared); if (dataFile == null) return false; synchronized (writeIOSync) { return dataFile.toFile().delete(); } } - public static List listFile(Module module, String resourceName, String ext, boolean isShared) + public static List listFile(Module module, String resourceName, String type, boolean isShared) { - Path dataFile = resolveResource(module, resourceName, ext, isShared); + Path dataFile = resolveResource(module, resourceName, supportedTypes.get(type), isShared); if (dataFile == null) return null; if (!dataFile.toFile().exists()) return null; - synchronized (writeIOSync) { return FileModule.listFileContent(dataFile.toFile()); } + synchronized (writeIOSync) { return FileModule.listFileContent(dataFile); } + } + + public static JsonElement readJsonFile(Module module, String resourceName, String type, boolean isShared) { + Path dataFile = resolveResource(module, resourceName, supportedTypes.get(type), isShared); + if (dataFile == null) return null; + if (!dataFile.toFile().exists()) return null; + synchronized (writeIOSync) { return FileModule.readJsonContent(dataFile); } } - public static Stream listFolder(Module module, String resourceName, String ext, boolean isShared) + public static Stream listFolder(Module module, String resourceName, String type, boolean isShared) { Path dir = resolveResource(module, resourceName, null, isShared); + String ext = supportedTypes.get(type); if (dir == null) return null; if (!Files.exists(dir)) return null; Stream result; @@ -87,7 +106,7 @@ public abstract class Module private static Path resolveResource(Module module, String resourceName, String ext, boolean isShared) { if (!isShared && (module == null || module.getName() == null)) return null; - return CarpetServer.minecraft_server.getSavePath(WorldSavePath.ROOT).resolve("scripts/"+getDescriptor(module, resourceName, isShared)+(ext==null?"":"."+ext)); + return CarpetServer.minecraft_server.getSavePath(WorldSavePath.ROOT).resolve("scripts/"+getDescriptor(module, resourceName, isShared)+(ext==null?"":ext)); } @Override diff --git a/src/main/java/carpet/script/command/CommandArgument.java b/src/main/java/carpet/script/command/CommandArgument.java index 029a6008..cae45ed2 100644 --- a/src/main/java/carpet/script/command/CommandArgument.java +++ b/src/main/java/carpet/script/command/CommandArgument.java @@ -315,13 +315,13 @@ public abstract class CommandArgument public static CommandArgument buildFromConfig(String suffix, Map config, CarpetScriptHost host) { if (!config.containsKey("type")) - throw new InternalExpressionException("Custom types should at least specify the type"); + throw new InternalExpressionException("Custom type "+suffix+" should at least specify the type"); String baseType = config.get("type").getString(); if (!builtIns.containsKey(baseType)) - throw new InternalExpressionException("Unknown base type: "+baseType); + throw new InternalExpressionException("Unknown base type "+baseType+" for custom type "+suffix); CommandArgument variant = builtIns.get(baseType).factory().get(); - variant.configure(config, host); variant.suffix = suffix; + variant.configure(config, host); return variant; } @@ -334,9 +334,9 @@ public abstract class CommandArgument } if (config.containsKey("suggest")) { - if (config.containsKey("suggester")) throw new InternalExpressionException("Attempted to provide 'suggest' list while 'suggester' is present"); + if (config.containsKey("suggester")) throw new InternalExpressionException("Attempted to provide 'suggest' list while 'suggester' is present"+" for custom type "+suffix); Value suggestionValue = config.get("suggest"); - if (!(suggestionValue instanceof ListValue)) throw new InternalExpressionException("Argument suggestions needs to be a list"); + if (!(suggestionValue instanceof ListValue)) throw new InternalExpressionException("Argument suggestions needs to be a list"+" for custom type "+suffix); examples = ((ListValue) suggestionValue).getItems().stream() .map(Value::getString) .collect(Collectors.toSet()); @@ -378,7 +378,7 @@ public abstract class CommandArgument args.add(MapValue.wrap(params)); args.addAll(customSuggester.args); Value response = host.handleCommand(context.getSource(), customSuggester.function, args); - if (!(response instanceof ListValue)) throw error("Custom suggester should return a list of options"); + if (!(response instanceof ListValue)) throw error("Custom suggester should return a list of options"+" for custom type "+suffix); return ((ListValue) response).getItems().stream().map(Value::getString).collect(Collectors.toList()); } if (needsMatching) return examples; @@ -426,7 +426,7 @@ public abstract class CommandArgument if (!caseSensitive) choseValue = choseValue.toLowerCase(Locale.ROOT); if (!validOptions.isEmpty() && !validOptions.contains(choseValue)) { - throw new SimpleCommandExceptionType(new LiteralText("Incorrect value for "+param+": "+choseValue)).create(); + throw new SimpleCommandExceptionType(new LiteralText("Incorrect value for "+param+": "+choseValue+" for custom type "+suffix)).create(); } return StringValue.of(choseValue); } @@ -438,7 +438,7 @@ public abstract class CommandArgument if (config.containsKey("options")) { Value optionsValue = config.get("options"); - if (!(optionsValue instanceof ListValue)) throw new InternalExpressionException("Custom string type requires options passed as a list"); + if (!(optionsValue instanceof ListValue)) throw new InternalExpressionException("Custom string type requires options passed as a list"+" for custom type "+suffix); validOptions = ((ListValue) optionsValue).getItems().stream() .map(v -> caseSensitive?v.getString():(v.getString().toLowerCase(Locale.ROOT))) .collect(Collectors.toSet()); @@ -577,7 +577,7 @@ public abstract class CommandArgument if (!single) return ListValue.wrap(founds.stream().map(EntityValue::new).collect(Collectors.toList())); if (founds.size() == 0) return Value.NULL; if (founds.size() == 1) return new EntityValue(founds.iterator().next()); - throw new SimpleCommandExceptionType(new LiteralText("Multiple entities returned while only one was requested")).create(); + throw new SimpleCommandExceptionType(new LiteralText("Multiple entities returned while only one was requested"+" for custom type "+suffix)).create(); } @Override @@ -618,7 +618,7 @@ public abstract class CommandArgument int size = profiles.size(); if (size == 0) return Value.NULL; if (size == 1) return StringValue.of(profiles.iterator().next().getName()); - throw new SimpleCommandExceptionType(new LiteralText("Multiple game profiles returned while only one was requested")).create(); + throw new SimpleCommandExceptionType(new LiteralText("Multiple game profiles returned while only one was requested"+" for custom type "+suffix)).create(); } @Override @@ -659,7 +659,7 @@ public abstract class CommandArgument int size = holders.size(); if (size == 0) return Value.NULL; if (size == 1) return StringValue.of(holders.iterator().next()); - throw new SimpleCommandExceptionType(new LiteralText("Multiple score holders returned while only one was requested")).create(); + throw new SimpleCommandExceptionType(new LiteralText("Multiple score holders returned while only one was requested"+" for custom type "+suffix)).create(); } @Override @@ -734,7 +734,7 @@ public abstract class CommandArgument Identifier choseValue = IdentifierArgumentType.getIdentifier(context, param); if (!validOptions.isEmpty() && !validOptions.contains(choseValue)) { - throw new SimpleCommandExceptionType(new LiteralText("Incorrect value for "+param+": "+choseValue)).create(); + throw new SimpleCommandExceptionType(new LiteralText("Incorrect value for "+param+": "+choseValue+" for custom type "+suffix)).create(); } return ValueConversions.of(choseValue); } @@ -752,7 +752,7 @@ public abstract class CommandArgument if (config.containsKey("options")) { Value optionsValue = config.get("options"); - if (!(optionsValue instanceof ListValue)) throw new InternalExpressionException("Custom sting type requires options passed as a list"); + if (!(optionsValue instanceof ListValue)) throw new InternalExpressionException("Custom sting type requires options passed as a list"+" for custom type "+suffix); validOptions = ((ListValue) optionsValue).getItems().stream().map(v -> new Identifier(v.getString())).collect(Collectors.toSet()); } } @@ -799,7 +799,7 @@ public abstract class CommandArgument { max = NumericValue.asNumber(config.get("max"), "max").getDouble(); } - if (max != null && min == null) throw new InternalExpressionException("Double types cannot be only upper-bounded"); + if (max != null && min == null) throw new InternalExpressionException("Double types cannot be only upper-bounded"+" for custom type "+suffix); } @Override @@ -850,7 +850,7 @@ public abstract class CommandArgument { max = NumericValue.asNumber(config.get("max"), "max").getLong(); } - if (max != null && min == null) throw new InternalExpressionException("Double types cannot be only upper-bounded"); + if (max != null && min == null) throw new InternalExpressionException("Double types cannot be only upper-bounded"+" for custom type "+suffix); } @Override @@ -923,7 +923,7 @@ public abstract class CommandArgument int slot = ItemSlotArgumentType.getItemSlot(context, param); if (restrict != null && !RESTRICTED_CONTAINERS.get(restrict).getLeft().contains(slot)) { - throw new SimpleCommandExceptionType(new LiteralText("Incorrect slot restricted to "+restrict)).create(); + throw new SimpleCommandExceptionType(new LiteralText("Incorrect slot restricted to "+restrict+" for custom type "+suffix)).create(); } return ValueConversions.ofVanillaSlotResult(slot); } @@ -937,7 +937,7 @@ public abstract class CommandArgument restrict = config.get("restrict").getString().toLowerCase(Locale.ROOT); needsMatching = true; if (!RESTRICTED_CONTAINERS.containsKey(restrict)) - throw new InternalExpressionException("Incorrect slot restriction: "+restrict); + throw new InternalExpressionException("Incorrect slot restriction "+restrict+" for custom type "+suffix); } } diff --git a/src/main/java/carpet/script/language/DataStructures.java b/src/main/java/carpet/script/language/DataStructures.java index 10c21c94..95b18749 100644 --- a/src/main/java/carpet/script/language/DataStructures.java +++ b/src/main/java/carpet/script/language/DataStructures.java @@ -87,7 +87,7 @@ public class DataStructures { throw new InternalExpressionException("'slice' takes 2 or 3 arguments"); Value hwat = lv.get(0); long from = NumericValue.asNumber(lv.get(1)).getLong(); - long to = -1; + Long to = null; if (lv.size()== 3) to = NumericValue.asNumber(lv.get(2)).getLong(); return hwat.slice(from, to); diff --git a/src/main/java/carpet/script/utils/ScarpetJsonDeserializer.java b/src/main/java/carpet/script/utils/ScarpetJsonDeserializer.java new file mode 100644 index 00000000..9b13ddc5 --- /dev/null +++ b/src/main/java/carpet/script/utils/ScarpetJsonDeserializer.java @@ -0,0 +1,78 @@ +package carpet.script.utils; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; + +import carpet.script.value.ListValue; +import carpet.script.value.MapValue; +import carpet.script.value.NumericValue; +import carpet.script.value.StringValue; +import carpet.script.value.Value; + +public class ScarpetJsonDeserializer implements JsonDeserializer{ + + @Override + public Value deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + return parseElement(json); + } + + private Value parseElement(JsonElement element) throws JsonParseException { + if (element.isJsonObject()) + { + return parseMap(element.getAsJsonObject()); + } + else if (element.isJsonArray()) + { + return parseList(element.getAsJsonArray()); + } + else if (element.isJsonPrimitive()) + { + return parsePrimitive(element.getAsJsonPrimitive()); + } + return Value.NULL; + } + + private Value parseMap(JsonObject jsonMap) throws JsonParseException { + Map map = new HashMap(); + jsonMap.entrySet().forEach(entry -> { + map.put(new StringValue(entry.getKey()), parseElement(entry.getValue())); + }); + return MapValue.wrap(map); + } + + private Value parseList(JsonArray jsonList) throws JsonParseException { + List list = new ArrayList(); + jsonList.forEach(elem -> { + list.add(parseElement(elem)); + }); + return new ListValue(list); + } + + private Value parsePrimitive(JsonPrimitive primitive) throws JsonParseException { + if (primitive.isString()) + { + return new StringValue(primitive.getAsString()); + } + else if (primitive.isBoolean()) + { + return primitive.getAsBoolean() ? Value.TRUE : Value.FALSE; + } + else if (primitive.isNumber()) + { + return NumericValue.of(primitive.getAsNumber()); + } + return Value.NULL; + } +} diff --git a/src/main/java/carpet/script/utils/ShapesRenderer.java b/src/main/java/carpet/script/utils/ShapesRenderer.java index 7a8f24bc..03bc54aa 100644 --- a/src/main/java/carpet/script/utils/ShapesRenderer.java +++ b/src/main/java/carpet/script/utils/ShapesRenderer.java @@ -168,6 +168,15 @@ public class ShapesRenderer } } + public void renewShapes() + { + synchronized (shapes) + { + shapes.values().forEach(el -> el.values().forEach(shape -> { + shape.expiryTick++; + })); + } + } public abstract static class RenderedShape { diff --git a/src/main/java/carpet/script/utils/SystemInfo.java b/src/main/java/carpet/script/utils/SystemInfo.java index 8d1ac7c2..230fbac5 100644 --- a/src/main/java/carpet/script/utils/SystemInfo.java +++ b/src/main/java/carpet/script/utils/SystemInfo.java @@ -13,6 +13,7 @@ import carpet.settings.ParsedRule; import carpet.settings.SettingsManager; import com.sun.management.OperatingSystemMXBean; import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.SharedConstants; import net.minecraft.util.WorldSavePath; import net.minecraft.world.GameRules; @@ -56,6 +57,7 @@ public class SystemInfo { put("game_view_distance", c -> new NumericValue(c.s.getMinecraftServer().getPlayerManager().getViewDistance())); put("game_mod_name", c -> StringValue.of(c.s.getMinecraftServer().getServerModName())); put("game_version", c -> StringValue.of(c.s.getMinecraftServer().getVersion())); + put("game_data_version", c->NumericValue.of(SharedConstants.getGameVersion().getWorldVersion())); put("server_ip", c -> StringValue.of(c.s.getMinecraftServer().getServerIp())); put("server_whitelisted", c -> new NumericValue(c.s.getMinecraftServer().isEnforceWhitelist())); diff --git a/src/main/java/carpet/script/value/AbstractListValue.java b/src/main/java/carpet/script/value/AbstractListValue.java index cba8a000..fcb1f237 100644 --- a/src/main/java/carpet/script/value/AbstractListValue.java +++ b/src/main/java/carpet/script/value/AbstractListValue.java @@ -4,7 +4,7 @@ import carpet.script.exception.InternalExpressionException; import java.util.Iterator; -public abstract class AbstractListValue extends Value +public abstract class AbstractListValue extends Value implements Iterable { public abstract Iterator iterator(); public void fatality() { } diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java index fc41657e..3c958902 100644 --- a/src/main/java/carpet/script/value/EntityValue.java +++ b/src/main/java/carpet/script/value/EntityValue.java @@ -7,6 +7,7 @@ import carpet.fakes.LivingEntityInterface; import carpet.fakes.MemoryInterface; import carpet.fakes.MobEntityInterface; import carpet.fakes.HungerManagerInterface; +import carpet.fakes.ServerPlayerEntityInterface; import carpet.fakes.ServerPlayerInteractionManagerInterface; import carpet.helpers.Tracer; import carpet.network.ServerNetworkHandler; @@ -91,7 +92,7 @@ import static carpet.utils.MobAI.genericJump; // TODO: decide whether copy(entity) should duplicate entity in the world. public class EntityValue extends Value { - private final Entity entity; + private Entity entity; public EntityValue(Entity e) { @@ -120,6 +121,11 @@ public class EntityValue extends Value public Entity getEntity() { + if (entity instanceof ServerPlayerEntity && ((ServerPlayerEntityInterface)entity).isInvalidEntityObject()) + { + ServerPlayerEntity newPlayer = entity.getServer().getPlayerManager().getPlayer(entity.getUuid()); + if (newPlayer != null) entity = newPlayer; + } return entity; } @@ -171,7 +177,7 @@ public class EntityValue extends Value @Override public String getString() { - return entity.getName().getString(); + return getEntity().getName().getString(); } @Override @@ -185,7 +191,7 @@ public class EntityValue extends Value { if (v instanceof EntityValue) { - return entity.getEntityId()==((EntityValue) v).entity.getEntityId(); + return getEntity().getEntityId()==((EntityValue) v).getEntity().getEntityId(); } return super.equals((Value)v); } @@ -221,7 +227,7 @@ public class EntityValue extends Value @Override public int hashCode() { - return entity.hashCode(); + return getEntity().hashCode(); } public static EntityClassDescriptor getEntityDescriptor(String who, MinecraftServer server) @@ -389,7 +395,7 @@ public class EntityValue extends Value throw new InternalExpressionException("Unknown entity feature: "+what); try { - return featureAccessors.get(what).apply(entity, arg); + return featureAccessors.get(what).apply(getEntity(), arg); } catch (NullPointerException npe) { @@ -850,7 +856,7 @@ public class EntityValue extends Value throw new InternalExpressionException("Unknown entity action: " + what); try { - featureModifiers.get(what).accept(entity, toWhat); + featureModifiers.get(what).accept(getEntity(), toWhat); } catch (NullPointerException npe) { @@ -1483,7 +1489,7 @@ public class EntityValue extends Value EntityEventsGroup.Event event = EntityEventsGroup.Event.byName.get(eventName); if (event == null) throw new InternalExpressionException("Unknown entity event: " + eventName); - ((EntityInterface)entity).getEventContainer().addEvent(event, cc.host, fun, args); + ((EntityInterface)getEntity()).getEventContainer().addEvent(event, cc.host, fun, args); } @Override @@ -1492,7 +1498,7 @@ public class EntityValue extends Value if (!force) throw new NBTSerializableValue.IncompatibleTypeException(this); CompoundTag tag = new CompoundTag(); tag.put("Data", getEntity().toTag( new CompoundTag())); - tag.put("Name", StringTag.of(Registry.ENTITY_TYPE.getId(entity.getType()).toString())); + tag.put("Name", StringTag.of(Registry.ENTITY_TYPE.getId(getEntity().getType()).toString())); return tag; } } diff --git a/src/main/java/carpet/script/value/FunctionValue.java b/src/main/java/carpet/script/value/FunctionValue.java index c9370369..6517dd66 100644 --- a/src/main/java/carpet/script/value/FunctionValue.java +++ b/src/main/java/carpet/script/value/FunctionValue.java @@ -133,7 +133,7 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction } @Override - public Value slice(long from, long to) + public Value slice(long from, Long to) { throw new InternalExpressionException("Cannot slice a function"); } diff --git a/src/main/java/carpet/script/value/LazyListValue.java b/src/main/java/carpet/script/value/LazyListValue.java index 5c68a75a..5584e628 100644 --- a/src/main/java/carpet/script/value/LazyListValue.java +++ b/src/main/java/carpet/script/value/LazyListValue.java @@ -129,9 +129,9 @@ public abstract class LazyListValue extends AbstractListValue implements Iterato } @Override - public Value slice(long from, long to) + public Value slice(long from, Long to) { - if (to < 0) to = Integer.MAX_VALUE; + if (to == null || to < 0) to = (long) Integer.MAX_VALUE; if (from < 0) from = 0; if (from > to) return ListValue.of(); diff --git a/src/main/java/carpet/script/value/ListValue.java b/src/main/java/carpet/script/value/ListValue.java index c7a7a409..a036af56 100644 --- a/src/main/java/carpet/script/value/ListValue.java +++ b/src/main/java/carpet/script/value/ListValue.java @@ -289,27 +289,22 @@ public class ListValue extends AbstractListValue implements ContainerValueInterf items.add(v); } - public void addAtIndex(int index, List subList) + /** + * Finds a proper list index >=0 and < len that correspont to the rolling index value of idx + * + * @param idx + * @param len + * @return + */ + public static int normalizeIndex(long idx, int len) { - int numitems = items.size(); - long range = abs(index)/numitems; - index += (range+2)*numitems; - index = index % numitems; - for (Value v: subList) - { - if (index < numitems) - { - items.set(index, v); - } - else - { - items.add(v); - } - index++; - } + if (idx >=0 && idx < len) return (int)idx; + long range = abs(idx)/len; + idx += (range+2)*len; + idx = idx % len; + return (int)idx; } - public static class ListConstructorValue extends ListValue { public ListConstructorValue(Collection list) @@ -339,15 +334,17 @@ public class ListValue extends AbstractListValue implements ContainerValueInterf } @Override - public Value slice(long from, long to) + public Value slice(long fromDesc, Long toDesc) { List items = getItems(); int size = items.size(); - if (to < 0 || to > size) to = size; - if (from < 0 || from > size) from = size; + int from = normalizeIndex(fromDesc, size); + if (toDesc == null) + return new ListValue((Collection) getItems().subList(from, size)); + int to = normalizeIndex(toDesc, size); if (from > to) return ListValue.of(); - return new ListValue((Collection) getItems().subList((int)from, (int) to)); + return new ListValue((Collection) getItems().subList(from, to)); } @Override @@ -395,10 +392,8 @@ public class ListValue extends AbstractListValue implements ContainerValueInterf return false; int index = (int)((NumericValue) ind).getLong(); if (index < 0) - { - long range = abs(index) / numitems; - index += (range + 2) * numitems; - index = index % numitems; + {// only for values < 0 + index = normalizeIndex(index, numitems); } if (replace) { @@ -424,11 +419,7 @@ public class ListValue extends AbstractListValue implements ContainerValueInterf public Value get(Value value) { long index = NumericValue.asNumber(value, "'address' to a list index").getLong(); - int numitems = items.size(); - long range = abs(index)/numitems; - index += (range+2)*numitems; - index = index % numitems; - return items.get((int)index); + return items.get(normalizeIndex(index, items.size())); } @Override @@ -443,11 +434,7 @@ public class ListValue extends AbstractListValue implements ContainerValueInterf { if (!(where instanceof NumericValue) || items.isEmpty()) return false; long index = ((NumericValue) where).getLong(); - int numitems = items.size(); - long range = abs(index)/numitems; - index += (range+2)*numitems; - index = index % numitems; - items.remove((int)index); + items.remove(normalizeIndex(index, items.size())); return true; } diff --git a/src/main/java/carpet/script/value/MapValue.java b/src/main/java/carpet/script/value/MapValue.java index 2cb3da44..abefe087 100644 --- a/src/main/java/carpet/script/value/MapValue.java +++ b/src/main/java/carpet/script/value/MapValue.java @@ -198,7 +198,7 @@ public class MapValue extends AbstractListValue implements ContainerValueInterfa } @Override - public Value slice(long from, long to) + public Value slice(long from, Long to) { throw new InternalExpressionException("Cannot slice a map value"); } diff --git a/src/main/java/carpet/script/value/StringValue.java b/src/main/java/carpet/script/value/StringValue.java index 61868c60..48fea7c1 100644 --- a/src/main/java/carpet/script/value/StringValue.java +++ b/src/main/java/carpet/script/value/StringValue.java @@ -5,6 +5,8 @@ import net.minecraft.nbt.Tag; public class StringValue extends Value { + public static Value EMPTY = StringValue.of(""); + private String str; @Override diff --git a/src/main/java/carpet/script/value/Value.java b/src/main/java/carpet/script/value/Value.java index c2f188e7..8f79f462 100644 --- a/src/main/java/carpet/script/value/Value.java +++ b/src/main/java/carpet/script/value/Value.java @@ -178,16 +178,15 @@ public abstract class Value implements Comparable, Cloneable return getString().length(); } - public Value slice(long from, long to) + public Value slice(long fromDesc, Long toDesc) { String value = this.getString(); int size = value.length(); - if (to > size) to = -1; - if (from < 0) from = 0; - if (from > size) from = size; - if (to>=0) - return new StringValue(value.substring((int)from, (int)to)); - return new StringValue(value.substring((int)from)); + int from = ListValue.normalizeIndex(fromDesc, size); + if (toDesc == null) return new StringValue(value.substring(from)); + int to = ListValue.normalizeIndex(toDesc, size); + if (from > to) return StringValue.EMPTY; + return new StringValue(value.substring(from, to)); } public double readDoubleNumber() { diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json index 76e41cd2..d231060c 100644 --- a/src/main/resources/carpet.mixins.json +++ b/src/main/resources/carpet.mixins.json @@ -166,6 +166,8 @@ "BlockItem_scarpetEventMixin", "PlayerManager_scarpetEventsMixin", "PlayerInventory_scarpetEventMixin", + "ScoreboardObjective_scarpetMixin", + "Scoreboard_scarpetMixin", "TradeOutputSlot_scarpetEventMixin", "ChunkTicketManager_scarpetMixin", diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 38b4c42b..c98709eb 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -1,7 +1,7 @@ { "schemaVersion": 1, "id": "carpet", - "version": "1.4.21", + "version": "${version}", "name": "Carpet Mod in Fabric", "description": "Carpet made out of fabric",