Merge branch 'master' into 1.17

This commit is contained in:
gnembon committed 2021-01-07 00:41:15 -04:00
commit ed4afa15ff
49 files changed
+593 -211

No files matched your search

+29 -18
View File
@@ -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"
}
+29 -13
View File
@@ -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).
<pre>
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]]
</pre>
### `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`
<pre>
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
+9 -5
View File
@@ -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
+12 -1
View File
@@ -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.
+4 -2
View File
@@ -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
+3 -3
View File
@@ -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`
<pre>
scoreboard_add('counter')
@@ -162,4 +163,3 @@ for(bossbar(),bossbar(_,'remove')) -> remove all bossbars
+9 -2
View File
@@ -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).
<pre>
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]]
</pre>
### `sort(list), sort(values ...)`
+1 -1
View File
@@ -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
Binary file not shown.
+1 -1
View File
@@ -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
Vendored
+15 -19
View File
@@ -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" "$@"
Vendored
+7 -18
View File
@@ -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
+1 -1
View File
@@ -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();
@@ -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
{
@@ -5,4 +5,7 @@ import carpet.helpers.EntityPlayerActionPack;
public interface ServerPlayerEntityInterface
{
EntityPlayerActionPack getActionPack();
void invalidateEntityObjectReference();
boolean isInvalidEntityObject();
}
+37 -4
View File
@@ -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)
@@ -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--;
}
}
@@ -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);
}
@@ -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();
}
}
}
@@ -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();
@@ -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<ServerPlayerEntity> cir)
{
((ServerPlayerEntityInterface)player).invalidateEntityObjectReference();
}
}
@@ -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);
}
@@ -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<ScoreboardCriterion, List<ScoreboardObjective>> getObjectivesByCriterion();
}
@@ -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;
}
}
@@ -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);
@@ -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");
@@ -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<ScheduledCall> eventIterator = scheduledCalls.iterator();
List<ScheduledCall> currentCalls = new ArrayList<>();
while(eventIterator.hasNext())
@@ -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<String> data)
@@ -714,16 +716,22 @@ public class CarpetScriptHost extends ScriptHost
return Module.appendToTextFile(main, resource, type, isShared, data);
}
public List<String> readTextResource(String resource, boolean isShared)
public List<String> 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 Stream<String> listFolder(String resource, String ext, boolean 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<String> 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);
}
+1 -1
View File
@@ -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();}
+38 -11
View File
@@ -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<String, SoundCategory> mixerMap = Arrays.stream(SoundCategory.values()).collect(Collectors.toMap(SoundCategory::getName, k -> k));
private static final Map<String, String> 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<String, String, Boolean> fdesc = getFileDescriptor(lv, c, true);
Stream<String> files = ((CarpetScriptHost) c.host).listFolder(fdesc.getLeft(), supportedTypes.get(fdesc.getMiddle()), fdesc.getRight());
Stream<String> 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<String> content = ((CarpetScriptHost) c.host).readTextResource(fdesc.getLeft(), fdesc.getRight());
List<String> 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<String> 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<String> data = new ArrayList<>();
+11 -4
View File
@@ -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) ->
@@ -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;
@@ -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<String> data)
public static boolean appendText(Path filePath, boolean addNewLines, List<String> 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<String> listFileContent(File filePath)
public static List<String> listFileContent(Path filePath)
{
try (BufferedReader reader = Files.newBufferedReader(filePath.toPath(), StandardCharsets.UTF_8)) {
try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) {
List<String> result = new ArrayList<>();
for (;;) {
String line = reader.readLine();
@@ -177,6 +181,18 @@ public class FileModule extends Module
}
}
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<Path> listFiles(Path dir, String ext)
{
boolean folderListing = ext.equals("folder");
+30 -11
View File
@@ -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<String, String> 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<String> 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<String> listFile(Module module, String resourceName, String ext, boolean isShared)
public static List<String> 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 Stream<String> listFolder(Module module, String resourceName, String ext, boolean isShared)
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<String> 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<Path> 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
@@ -315,13 +315,13 @@ public abstract class CommandArgument
public static CommandArgument buildFromConfig(String suffix, Map<String, Value> 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);
}
}
@@ -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);
@@ -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<Value>{
@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<Value, Value> map = new HashMap<Value, Value>();
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<Value> list = new ArrayList<Value>();
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;
}
}
@@ -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<T extends ShapeDispatcher.ExpiringShape>
{
@@ -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()));
@@ -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<Value>
{
public abstract Iterator<Value> iterator();
public void fatality() { }
@@ -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;
}
}
@@ -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");
}
@@ -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();
@@ -289,27 +289,22 @@ public class ListValue extends AbstractListValue implements ContainerValueInterf
items.add(v);
}
public void addAtIndex(int index, List<Value> 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<? extends Value> 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<Value> 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<? extends Value>) getItems().subList(from, size));
int to = normalizeIndex(toDesc, size);
if (from > to)
return ListValue.of();
return new ListValue((Collection<? extends Value>) getItems().subList((int)from, (int) to));
return new ListValue((Collection<? extends Value>) 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;
}
@@ -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");
}
@@ -5,6 +5,8 @@ import net.minecraft.nbt.Tag;
public class StringValue extends Value
{
public static Value EMPTY = StringValue.of("");
private String str;
@Override
+6 -7
View File
@@ -178,16 +178,15 @@ public abstract class Value implements Comparable<Value>, 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()
{
+2
View File
@@ -166,6 +166,8 @@
"BlockItem_scarpetEventMixin",
"PlayerManager_scarpetEventsMixin",
"PlayerInventory_scarpetEventMixin",
"ScoreboardObjective_scarpetMixin",
"Scoreboard_scarpetMixin",
"TradeOutputSlot_scarpetEventMixin",
"ChunkTicketManager_scarpetMixin",
+1 -1
View File
@@ -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",