From 055c26d44a9cc8203751f6a983bf894c836021de Mon Sep 17 00:00:00 2001 From: replaceitem Date: Wed, 16 Dec 2020 20:35:28 +0100 Subject: [PATCH 01/27] added changing criterion to scoreboard_add #624 --- docs/scarpet/api/Scoreboard.md | 5 +++-- .../mixins/ScoreboardObjective_scarpetMixin.java | 12 ++++++++++++ .../carpet/mixins/Scoreboard_scarpetMixin.java | 16 ++++++++++++++++ src/main/java/carpet/script/api/Scoreboards.java | 10 +++++++++- src/main/resources/carpet.mixins.json | 2 ++ 5 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 src/main/java/carpet/mixins/ScoreboardObjective_scarpetMixin.java create mode 100644 src/main/java/carpet/mixins/Scoreboard_scarpetMixin.java diff --git a/docs/scarpet/api/Scoreboard.md b/docs/scarpet/api/Scoreboard.md index 98e9c9b7..51a3fd2b 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 objective was added, returns true.
 scoreboard_add('counter')
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/script/api/Scoreboards.java b/src/main/java/carpet/script/api/Scoreboards.java
index 0ba8d9e5..89a27a1d 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.player.PlayerEntity;
 import net.minecraft.scoreboard.Scoreboard;
@@ -127,8 +130,13 @@ public class Scoreboards {
             }
 
             ScoreboardObjective objective = scoreboard.getObjective(objectiveName);
-            if (objective != null)
+            if (objective != 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.getCriterionType());
             return LazyValue.TRUE;
diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json
index 8a90de0a..24a94b8f 100644
--- a/src/main/resources/carpet.mixins.json
+++ b/src/main/resources/carpet.mixins.json
@@ -164,6 +164,8 @@
     "BlockItem_scarpetEventMixin",
     "PlayerManager_scarpetEventsMixin",
     "PlayerInventory_scarpetEventMixin",
+    "ScoreboardObjective_scarpetMixin",
+    "Scoreboard_scarpetMixin",
 
     "ChunkTicketManager_scarpetMixin",
     "StatType_scarpetMixin",

From 33347baaf73d890018d9aba96ce6898cb741724b Mon Sep 17 00:00:00 2001
From: altrisi 
Date: Sun, 20 Dec 2020 23:42:52 +0100
Subject: [PATCH 02/27] Freezing Scarpet

And the client
---
 src/main/java/carpet/CarpetServer.java        |  2 +-
 .../java/carpet/commands/TickCommand.java     |  3 ++
 src/main/java/carpet/helpers/TickSpeed.java   |  4 +-
 .../CommandFunctionManager_tickMixin.java     |  5 ++-
 .../carpet/mixins/MinecraftClientMixin.java   | 16 ++++++++
 .../mixins/MinecraftServer_scarpetMixin.java  |  3 ++
 .../carpet/network/ClientNetworkHandler.java  |  7 ++++
 .../carpet/network/ServerNetworkHandler.java  | 40 ++++++++++++++++++-
 .../java/carpet/script/CarpetEventServer.java |  3 ++
 .../carpet/script/utils/ShapesRenderer.java   |  9 +++++
 10 files changed, 87 insertions(+), 5 deletions(-)

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..f4f04268 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;
@@ -107,6 +108,7 @@ public class TickCommand
             TickSpeed.deepFreeze = false;
             Messenger.m(source, "gi Game runs normally");
         }
+        ServerNetworkHandler.updateTickingStateToConnectedPlayers();
         return 1;
     }
 
@@ -119,6 +121,7 @@ public class TickCommand
     private static int toggleSuperHot(ServerCommandSource source)
     {
         TickSpeed.is_superHot = !TickSpeed.is_superHot;
+        ServerNetworkHandler.updateTickingStateToConnectedPlayers();
         if (TickSpeed.is_superHot)
         {
             Messenger.m(source,"gi Superhot enabled");
diff --git a/src/main/java/carpet/helpers/TickSpeed.java b/src/main/java/carpet/helpers/TickSpeed.java
index 575867eb..756bbbb9 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;
@@ -66,6 +65,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 +155,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/CommandFunctionManager_tickMixin.java b/src/main/java/carpet/mixins/CommandFunctionManager_tickMixin.java
index 301a05d5..118642b1 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 && TickSpeed.deepFreeze) 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..6b519175 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 && TickSpeed.deepFreeze)
+                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..599e3796 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 && TickSpeed.deepFreeze)
+            return;
         TICK.onTick();
         NETHER_TICK.onTick();
         ENDER_TICK.onTick();
diff --git a/src/main/java/carpet/network/ClientNetworkHandler.java b/src/main/java/carpet/network/ClientNetworkHandler.java
index 4d3cc1ad..7f5ffd73 100644
--- a/src/main/java/carpet/network/ClientNetworkHandler.java
+++ b/src/main/java/carpet/network/ClientNetworkHandler.java
@@ -65,6 +65,13 @@ public class ClientNetworkHandler
             }
         });
         dataHandlers.put("TickRate", (p, t) -> TickSpeed.tickrate(((AbstractNumberTag)t).getFloat(), false));
+        dataHandlers.put("TickingState", (p, t) -> {
+            CompoundTag tickingState = (CompoundTag)t;
+            TickSpeed.is_paused = tickingState.getBoolean("is_paused");
+            TickSpeed.deepFreeze = tickingState.getBoolean("deepFreeze");
+            TickSpeed.is_superHot = tickingState.getBoolean("is_superHot");
+        });
+        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..60c444a8 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().withTickingState().withTickPlayerActiveTimeout();
         CarpetServer.settingsManager.getRules().forEach(data::withRule);
         CarpetServer.extensions.forEach(e -> {
             SettingsManager eManager = e.customSettingsManager();
@@ -173,6 +173,30 @@ public class ServerNetworkHandler
         }
     }
 
+    public static void updateTickingStateToConnectedPlayers()
+    {
+        if (CarpetSettings.superSecretSetting) return;
+        for (ServerPlayerEntity player : remoteCarpetPlayers.keySet())
+        {
+            player.networkHandler.sendPacket(new CustomPayloadS2CPacket(
+                    CarpetClient.CARPET_CHANNEL,
+                    DataBuilder.create().withTickingState().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 +264,20 @@ public class ServerNetworkHandler
             tag.putFloat("TickRate", TickSpeed.tickrate);
             return this;
         }
+        private DataBuilder withTickingState()
+        {
+            CompoundTag tickingState = new CompoundTag();
+            tickingState.putBoolean("is_paused", TickSpeed.is_paused);
+            tickingState.putBoolean("deepFreeze", TickSpeed.deepFreeze);
+            tickingState.putBoolean("is_superhot", TickSpeed.is_superHot);
+            tag.put("TickingState", tickingState);
+            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 4b6b31b6..69aec8c9 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;
@@ -960,6 +961,8 @@ public class CarpetEventServer
 
     public void tick()
     {
+        if (!TickSpeed.process_entities && TickSpeed.deepFreeze)
+            return;
         Iterator eventIterator = scheduledCalls.iterator();
         List currentCalls = new ArrayList<>();
         while(eventIterator.hasNext())
diff --git a/src/main/java/carpet/script/utils/ShapesRenderer.java b/src/main/java/carpet/script/utils/ShapesRenderer.java
index 371b840d..8be35a77 100644
--- a/src/main/java/carpet/script/utils/ShapesRenderer.java
+++ b/src/main/java/carpet/script/utils/ShapesRenderer.java
@@ -167,6 +167,15 @@ public class ShapesRenderer
         }
     }
 
+    public void renewShapes()
+    {
+        synchronized (shapes)
+        {
+            shapes.values().forEach(el -> el.values().forEach(shape -> {
+                shape.expiryTick++;
+            }));
+        }
+    }
 
     public abstract static class RenderedShape
     {

From 6225e5f632d57267ccc6d2d30d0521dd861f4dd7 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Wed, 23 Dec 2020 11:55:45 -0400
Subject: [PATCH 03/27] player() returns consistently on player scoped apps.

---
 docs/scarpet/api/Entities.md                  |  5 ++++-
 src/main/java/carpet/script/ScriptHost.java   |  2 +-
 src/main/java/carpet/script/api/Entities.java | 15 +++++++++++----
 3 files changed, 16 insertions(+), 6 deletions(-)

diff --git a/docs/scarpet/api/Entities.md b/docs/scarpet/api/Entities.md
index 34584e8b..7f03f748 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 
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/Entities.java b/src/main/java/carpet/script/api/Entities.java
index ccf35938..68a02904 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) ->

From f0a52fc7513bd86f8548b3f976e85a3a59992ef3 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" 
Date: Wed, 23 Dec 2020 15:56:27 +0000
Subject: [PATCH 04/27] Merge docs for 'player() returns consistently on player
 scoped apps.'

---
 docs/scarpet/Full.md | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md
index b5aec1b9..cae98c9d 100644
--- a/docs/scarpet/Full.md
+++ b/docs/scarpet/Full.md
@@ -2893,7 +2893,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 

From d6eb53b6ce9ca12cef88ff555c1d3ce55c5099fc Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Thu, 24 Dec 2020 06:48:14 -0400
Subject: [PATCH 05/27] player entity value may become invalid due to respawns.
 This not only fixes #633, but also other cases where player reference is used
 over time while player respawns and its instance is recycled.

---
 .../fakes/ServerPlayerEntityInterface.java    |  3 +++
 .../PlayerManager_scarpetEventsMixin.java     |  8 +++++++
 .../ServerPlayerEntity_scarpetEventMixin.java | 17 +++++++++++++-
 .../java/carpet/script/value/EntityValue.java | 22 ++++++++++++-------
 4 files changed, 41 insertions(+), 9 deletions(-)

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/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/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/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java
index 283d3548..9504166a 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;
@@ -90,7 +91,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)
     {
@@ -119,6 +120,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;
     }
 
@@ -170,7 +176,7 @@ public class EntityValue extends Value
     @Override
     public String getString()
     {
-        return entity.getName().getString();
+        return getEntity().getName().getString();
     }
 
     @Override
@@ -184,7 +190,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);
     }
@@ -220,7 +226,7 @@ public class EntityValue extends Value
     @Override
     public int hashCode()
     {
-        return entity.hashCode();
+        return getEntity().hashCode();
     }
 
     public static EntityClassDescriptor getEntityDescriptor(String who, MinecraftServer server)
@@ -374,7 +380,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)
         {
@@ -835,7 +841,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)
         {
@@ -1468,7 +1474,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
@@ -1477,7 +1483,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;
     }
 }

From 23e83dfd1b87ad7cdc0f22718f1c45e48790ef3a Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Thu, 24 Dec 2020 06:55:12 -0400
Subject: [PATCH 06/27] clarify, or rather convolute the docs

---
 docs/scarpet/api/Events.md | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

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

From 386afc1079b833d47b333a423ff5f0d388eefbc0 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" 
Date: Thu, 24 Dec 2020 06:55:47 +0000
Subject: [PATCH 07/27] Merge docs for 'clarify, or rather convolute the docs'

---
 docs/scarpet/Full.md | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md
index cae98c9d..73d12b78 100644
--- a/docs/scarpet/Full.md
+++ b/docs/scarpet/Full.md
@@ -4112,8 +4112,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

From 7ad6de64ffc4af41c915b699ebb547d6191604cd Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Fri, 25 Dec 2020 04:55:32 -0400
Subject: [PATCH 08/27] slice can accept negative indices

---
 docs/scarpet/language/Containers.md           | 11 +++-
 .../java/carpet/script/value/ListValue.java   | 57 +++++++------------
 .../java/carpet/script/value/StringValue.java |  2 +
 src/main/java/carpet/script/value/Value.java  | 12 ++--
 4 files changed, 37 insertions(+), 45 deletions(-)

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/src/main/java/carpet/script/value/ListValue.java b/src/main/java/carpet/script/value/ListValue.java index c7a7a409..f6a3d8eb 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,15 @@ 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); + 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 +390,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 +417,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 +432,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/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..aea956a6 100644 --- a/src/main/java/carpet/script/value/Value.java +++ b/src/main/java/carpet/script/value/Value.java @@ -178,16 +178,14 @@ 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); + int to = ListValue.normalizeIndex(toDesc, size); + if (from > to) return StringValue.EMPTY; + return new StringValue(value.substring(from, to)); } public double readDoubleNumber() { From 8424de1d51acb0964cf9bb93f5545bc704326100 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 25 Dec 2020 04:57:26 +0000 Subject: [PATCH 09/27] Merge docs for 'slice can accept negative indices' --- docs/scarpet/Full.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index 73d12b78..8ba07e00 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 ...)` From ba2586743aa6c97b4fda88cbadf27216c320370e Mon Sep 17 00:00:00 2001 From: gnembon Date: Fri, 25 Dec 2020 05:34:55 -0400 Subject: [PATCH 10/27] errors in arguments include failing argument custom tupe. --- .../script/command/CommandArgument.java | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/java/carpet/script/command/CommandArgument.java b/src/main/java/carpet/script/command/CommandArgument.java index d2ea2f7d..b2e02272 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); } } From aabff1623b58c01f08962475b04872a5b114850e Mon Sep 17 00:00:00 2001 From: altrisi Date: Sun, 27 Dec 2020 02:04:19 +0100 Subject: [PATCH 11/27] Reading/Writing JSON, first working version --- .../java/carpet/script/CarpetScriptHost.java | 18 +++-- .../java/carpet/script/api/Auxiliary.java | 35 ++++++--- .../carpet/script/bundled/FileModule.java | 22 +++++- .../java/carpet/script/bundled/Module.java | 41 +++++++--- .../script/utils/ScarpetJsonDeserializer.java | 78 +++++++++++++++++++ .../script/value/AbstractListValue.java | 2 +- 6 files changed, 163 insertions(+), 33 deletions(-) create mode 100644 src/main/java/carpet/script/utils/ScarpetJsonDeserializer.java diff --git a/src/main/java/carpet/script/CarpetScriptHost.java b/src/main/java/carpet/script/CarpetScriptHost.java index 09e08153..3bed089d 100644 --- a/src/main/java/carpet/script/CarpetScriptHost.java +++ b/src/main/java/carpet/script/CarpetScriptHost.java @@ -705,7 +705,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 +714,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 Value 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/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java index cb8dbee4..0949b6bd 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,9 @@ 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 com.google.gson.Gson; +import com.google.gson.GsonBuilder; + import net.minecraft.block.BlockState; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; @@ -86,12 +90,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().registerTypeAdapter(Value.class, new ScarpetJsonDeserializer()).create(); public static String recognizeResource(Value value, boolean isFloder) { @@ -112,7 +111,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 +525,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 +802,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 +819,17 @@ public class Auxiliary { if (state == null) return LazyValue.NULL; retVal = new NBTSerializableValue(state); } + else if (fdesc.getMiddle().equals("json")) + { + Value parsedJson = ((CarpetScriptHost) c.host).readJsonFile(fdesc.getLeft(), fdesc.getMiddle(), fdesc.getRight()); + 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 +858,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/bundled/FileModule.java b/src/main/java/carpet/script/bundled/FileModule.java index b710b376..ad798e27 100644 --- a/src/main/java/carpet/script/bundled/FileModule.java +++ b/src/main/java/carpet/script/bundled/FileModule.java @@ -25,6 +25,9 @@ import java.util.List; import java.util.Locale; import java.util.stream.Stream; +import carpet.script.api.Auxiliary; +import carpet.script.value.Value; + public class FileModule extends Module { private String name; @@ -137,11 +140,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 +162,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 +179,17 @@ public class FileModule extends Module return null; } } + + public static Value readJsonContent(Path filePath) + { + try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) { + return Auxiliary.gson.fromJson(reader, Value.class); + } + 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..510aa6e0 100644 --- a/src/main/java/carpet/script/bundled/Module.java +++ b/src/main/java/carpet/script/bundled/Module.java @@ -1,13 +1,17 @@ package carpet.script.bundled; import carpet.CarpetServer; +import carpet.script.value.Value; import net.minecraft.nbt.Tag; import net.minecraft.util.WorldSavePath; import org.apache.commons.io.FilenameUtils; +import com.google.common.collect.ImmutableMap; + 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 Value 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/utils/ScarpetJsonDeserializer.java b/src/main/java/carpet/script/utils/ScarpetJsonDeserializer.java new file mode 100644 index 00000000..759e0dd5 --- /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/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() { } From 929c17da361ea3534fcb00816e6fbe13f5f33ab5 Mon Sep 17 00:00:00 2001 From: altrisi Date: Sun, 27 Dec 2020 15:53:44 +0100 Subject: [PATCH 12/27] Fix Value mess --- .../java/carpet/script/CarpetScriptHost.java | 4 +- .../java/carpet/script/api/Auxiliary.java | 10 +- .../carpet/script/bundled/FileModule.java | 12 ++- .../java/carpet/script/bundled/Module.java | 4 +- .../script/utils/ScarpetJsonDeserializer.java | 102 +++++++++--------- 5 files changed, 69 insertions(+), 63 deletions(-) diff --git a/src/main/java/carpet/script/CarpetScriptHost.java b/src/main/java/carpet/script/CarpetScriptHost.java index 3bed089d..60070cdc 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; @@ -720,7 +722,7 @@ public class CarpetScriptHost extends ScriptHost return Module.listFile(main, resource, type, isShared); } - public Value readJsonFile(String resource, String type, boolean isShared) + public JsonElement readJsonFile(String resource, String type, boolean isShared) { if (getName() == null && !isShared) return null; return Module.readJsonFile(main, resource, type, isShared); diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java index 0949b6bd..0fe2a643 100644 --- a/src/main/java/carpet/script/api/Auxiliary.java +++ b/src/main/java/carpet/script/api/Auxiliary.java @@ -35,9 +35,6 @@ import carpet.script.value.StringValue; import carpet.script.value.Value; import carpet.script.value.ValueConversions; import carpet.utils.Messenger; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - import net.minecraft.block.BlockState; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; @@ -73,6 +70,10 @@ 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 java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -821,7 +822,8 @@ public class Auxiliary { } else if (fdesc.getMiddle().equals("json")) { - Value parsedJson = ((CarpetScriptHost) c.host).readJsonFile(fdesc.getLeft(), fdesc.getMiddle(), fdesc.getRight()); + JsonElement json = ((CarpetScriptHost) c.host).readJsonFile(fdesc.getLeft(), fdesc.getMiddle(), fdesc.getRight()); + Value parsedJson = gson.fromJson(json, Value.class); if (parsedJson == null) retVal = Value.NULL; else diff --git a/src/main/java/carpet/script/bundled/FileModule.java b/src/main/java/carpet/script/bundled/FileModule.java index ad798e27..8c32034e 100644 --- a/src/main/java/carpet/script/bundled/FileModule.java +++ b/src/main/java/carpet/script/bundled/FileModule.java @@ -25,8 +25,9 @@ import java.util.List; import java.util.Locale; import java.util.stream.Stream; -import carpet.script.api.Auxiliary; -import carpet.script.value.Value; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; public class FileModule extends Module { @@ -180,10 +181,11 @@ public class FileModule extends Module } } - public static Value readJsonContent(Path filePath) + public static JsonElement readJsonContent(Path filePath) { - try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) { - return Auxiliary.gson.fromJson(reader, Value.class); + try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) + { + return new JsonParser().parse(new JsonReader(reader)); } catch (IOException e) { diff --git a/src/main/java/carpet/script/bundled/Module.java b/src/main/java/carpet/script/bundled/Module.java index 510aa6e0..4a352e8e 100644 --- a/src/main/java/carpet/script/bundled/Module.java +++ b/src/main/java/carpet/script/bundled/Module.java @@ -1,12 +1,12 @@ package carpet.script.bundled; import carpet.CarpetServer; -import carpet.script.value.Value; 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; @@ -80,7 +80,7 @@ public abstract class Module synchronized (writeIOSync) { return FileModule.listFileContent(dataFile); } } - public static Value readJsonFile(Module module, String resourceName, String type, 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; diff --git a/src/main/java/carpet/script/utils/ScarpetJsonDeserializer.java b/src/main/java/carpet/script/utils/ScarpetJsonDeserializer.java index 759e0dd5..9b13ddc5 100644 --- a/src/main/java/carpet/script/utils/ScarpetJsonDeserializer.java +++ b/src/main/java/carpet/script/utils/ScarpetJsonDeserializer.java @@ -22,57 +22,57 @@ 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; - } + @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 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; - } + 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; + } } From 2e19f24405ccb3a44b6aaafa76730a413509d30b Mon Sep 17 00:00:00 2001 From: altrisi Date: Sun, 27 Dec 2020 16:10:59 +0100 Subject: [PATCH 13/27] Add docs for JSON --- docs/scarpet/api/Auxiliary.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index 3db7a3df..0f2f9222 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 From 6375108dbf204ef0ea06379c2cbebb7351c7ff80 Mon Sep 17 00:00:00 2001 From: Ghoulboy78 Date: Mon, 28 Dec 2020 10:28:20 +0100 Subject: [PATCH 14/27] Add game_data_version (it's mostly for WE dev, ngl) --- docs/scarpet/api/Auxiliary.md | 5 +++-- src/main/java/carpet/script/utils/SystemInfo.java | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index 3db7a3df..e7c5c7ff 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -488,7 +488,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 +506,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 +514,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/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())); From b120f46764650118788bdd5e504f57a552c01d1d Mon Sep 17 00:00:00 2001 From: replaceitem <40722305+replaceitem@users.noreply.github.com> Date: Mon, 28 Dec 2020 12:03:20 +0100 Subject: [PATCH 15/27] Update docs/scarpet/api/Scoreboard.md Co-authored-by: gnembon <41132274+gnembon@users.noreply.github.com> --- docs/scarpet/api/Scoreboard.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/scarpet/api/Scoreboard.md b/docs/scarpet/api/Scoreboard.md index b1b2b2d5..09004484 100644 --- a/docs/scarpet/api/Scoreboard.md +++ b/docs/scarpet/api/Scoreboard.md @@ -12,7 +12,7 @@ With specified `objective` and 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 objective was added, returns true. +If the objective was added, returns `true`. If nothing is affected, returns `null`
 scoreboard_add('counter')
@@ -163,4 +163,3 @@ for(bossbar(),bossbar(_,'remove'))  -> remove all bossbars
 
 
 
-

From 5771d42c980181c1c3c500bb1cc836602b857ec4 Mon Sep 17 00:00:00 2001
From: replaceitem 
Date: Mon, 28 Dec 2020 12:44:17 +0100
Subject: [PATCH 16/27] scoreboard_add returns current criterion and null

---
 docs/scarpet/api/Scoreboard.md                   | 2 +-
 src/main/java/carpet/script/api/Scoreboards.java | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/docs/scarpet/api/Scoreboard.md b/docs/scarpet/api/Scoreboard.md
index 09004484..826c5328 100644
--- a/docs/scarpet/api/Scoreboard.md
+++ b/docs/scarpet/api/Scoreboard.md
@@ -11,7 +11,7 @@ With specified `objective` and
 ### `scoreboard_add(objective, criterion?)`
 
 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 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`
 
 
diff --git a/src/main/java/carpet/script/api/Scoreboards.java b/src/main/java/carpet/script/api/Scoreboards.java
index 44968d4e..d7cdf1e8 100644
--- a/src/main/java/carpet/script/api/Scoreboards.java
+++ b/src/main/java/carpet/script/api/Scoreboards.java
@@ -137,6 +137,8 @@ public class Scoreboards {
 
             ScoreboardObjective objective = scoreboard.getObjective(objectiveName);
             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);

From a4584bd6a04537c33be81c3339972cbd3c3119d7 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" 
Date: Tue, 29 Dec 2020 02:37:56 +0000
Subject: [PATCH 17/27] Merge docs for 'Merge pull request #646 from
 Ghoulboy78/adding-game-data-version

Add game_data_version to system_info'
---
 docs/scarpet/Full.md | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md
index 8ba07e00..03255965 100644
--- a/docs/scarpet/Full.md
+++ b/docs/scarpet/Full.md
@@ -4954,7 +4954,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
@@ -4972,7 +4972,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
@@ -4980,6 +4980,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

From 40a46ba599ca24d9eef0e0acd16507798503689e Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" 
Date: Tue, 29 Dec 2020 02:49:13 +0000
Subject: [PATCH 18/27] Merge docs for 'Merge pull request #643 from
 altrisi/json-reading

Reading/Writing JSON in Scarpet'
---
 docs/scarpet/Full.md | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md
index 03255965..4b41f448 100644
--- a/docs/scarpet/Full.md
+++ b/docs/scarpet/Full.md
@@ -4771,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

From db0ef5fbe50445fd3afd583761d0971fb3fa5f5e Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" 
Date: Tue, 29 Dec 2020 04:03:19 +0000
Subject: [PATCH 19/27] Merge docs for 'Merge pull request #631 from
 replaceitem/scoreboard-criterion-mod

Added ability to change criterion with scoreboard_add Fixes #624'
---
 docs/scarpet/Full.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md
index 4b41f448..0c427544 100644
--- a/docs/scarpet/Full.md
+++ b/docs/scarpet/Full.md
@@ -4311,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')
@@ -4463,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

From 6d3ac93e4c71b2d1c4bee42a19d8c610793690df Mon Sep 17 00:00:00 2001
From: altrisi 
Date: Tue, 29 Dec 2020 16:00:07 +0100
Subject: [PATCH 20/27] Use setters and getters for frozen state, separate
 superHot and

don't tick regular tick events
---
 .../java/carpet/commands/TickCommand.java     | 12 +++----
 src/main/java/carpet/helpers/TickSpeed.java   | 36 +++++++++++++++++--
 .../mixins/ChunkTicketManager_tickMixin.java  |  2 +-
 .../CommandFunctionManager_tickMixin.java     |  2 +-
 .../carpet/mixins/MinecraftClientMixin.java   |  2 +-
 .../mixins/MinecraftServer_scarpetMixin.java  |  2 +-
 .../carpet/network/ClientNetworkHandler.java  |  8 +++--
 .../carpet/network/ServerNetworkHandler.java  | 30 ++++++++++++----
 .../java/carpet/script/CarpetEventServer.java |  2 +-
 9 files changed, 71 insertions(+), 25 deletions(-)

diff --git a/src/main/java/carpet/commands/TickCommand.java b/src/main/java/carpet/commands/TickCommand.java
index f4f04268..8b6b3826 100644
--- a/src/main/java/carpet/commands/TickCommand.java
+++ b/src/main/java/carpet/commands/TickCommand.java
@@ -96,19 +96,15 @@ 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");
         }
-        ServerNetworkHandler.updateTickingStateToConnectedPlayers();
         return 1;
     }
 
@@ -121,10 +117,10 @@ public class TickCommand
     private static int toggleSuperHot(ServerCommandSource source)
     {
         TickSpeed.is_superHot = !TickSpeed.is_superHot;
-        ServerNetworkHandler.updateTickingStateToConnectedPlayers();
+        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/helpers/TickSpeed.java b/src/main/java/carpet/helpers/TickSpeed.java
index 756bbbb9..ef1982cd 100644
--- a/src/main/java/carpet/helpers/TickSpeed.java
+++ b/src/main/java/carpet/helpers/TickSpeed.java
@@ -25,10 +25,42 @@ 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;
+    }
+
+    /**
+     * This can be used for things that you may not normally want
+     * to freeze, but may need to in some situations.
+     * This should be used with {@link #process_entities} to make sure the 
+     * current tick is actually frozen
+     * @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.
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 118642b1..034f06db 100644
--- a/src/main/java/carpet/mixins/CommandFunctionManager_tickMixin.java
+++ b/src/main/java/carpet/mixins/CommandFunctionManager_tickMixin.java
@@ -16,7 +16,7 @@ public class CommandFunctionManager_tickMixin
     @Inject(method = "tick", at = @At("HEAD"), cancellable = true)
     private void beforeDatapacks(CallbackInfo ci)
     {
-    	if (!TickSpeed.process_entities && TickSpeed.deepFreeze) ci.cancel();
+    	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 6b519175..974ac320 100644
--- a/src/main/java/carpet/mixins/MinecraftClientMixin.java
+++ b/src/main/java/carpet/mixins/MinecraftClientMixin.java
@@ -28,7 +28,7 @@ public class MinecraftClientMixin
         if (this.world != null) {
             if (CarpetServer.minecraft_server == null)
                 TickSpeed.tick();
-            if (!TickSpeed.process_entities && TickSpeed.deepFreeze)
+            if (!TickSpeed.process_entities && TickSpeed.deeplyFrozen())
                 CarpetClient.shapes.renewShapes();
         }
     }
diff --git a/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java b/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java
index 599e3796..a4934a7f 100644
--- a/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java
+++ b/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java
@@ -70,7 +70,7 @@ public abstract class MinecraftServer_scarpetMixin extends ReentrantThreadExecut
     ))
     public void tickTasks(BooleanSupplier booleanSupplier_1, CallbackInfo ci)
     {
-        if (!TickSpeed.process_entities && TickSpeed.deepFreeze)
+        if (!TickSpeed.process_entities)
             return;
         TICK.onTick();
         NETHER_TICK.onTick();
diff --git a/src/main/java/carpet/network/ClientNetworkHandler.java b/src/main/java/carpet/network/ClientNetworkHandler.java
index 7f5ffd73..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;
@@ -67,9 +68,10 @@ public class ClientNetworkHandler
         dataHandlers.put("TickRate", (p, t) -> TickSpeed.tickrate(((AbstractNumberTag)t).getFloat(), false));
         dataHandlers.put("TickingState", (p, t) -> {
             CompoundTag tickingState = (CompoundTag)t;
-            TickSpeed.is_paused = tickingState.getBoolean("is_paused");
-            TickSpeed.deepFreeze = tickingState.getBoolean("deepFreeze");
-            TickSpeed.is_superHot = tickingState.getBoolean("is_superHot");
+            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
diff --git a/src/main/java/carpet/network/ServerNetworkHandler.java b/src/main/java/carpet/network/ServerNetworkHandler.java
index 60c444a8..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().withTickingState().withTickPlayerActiveTimeout();
+        DataBuilder data = DataBuilder.create().withTickRate().withFrozenState().withTickPlayerActiveTimeout(); // .withSuperHotState()
         CarpetServer.settingsManager.getRules().forEach(data::withRule);
         CarpetServer.extensions.forEach(e -> {
             SettingsManager eManager = e.customSettingsManager();
@@ -173,14 +173,26 @@ public class ServerNetworkHandler
         }
     }
 
-    public static void updateTickingStateToConnectedPlayers()
+    public static void updateFrozenStateToConnectedPlayers()
     {
         if (CarpetSettings.superSecretSetting) return;
         for (ServerPlayerEntity player : remoteCarpetPlayers.keySet())
         {
             player.networkHandler.sendPacket(new CustomPayloadS2CPacket(
                     CarpetClient.CARPET_CHANNEL,
-                    DataBuilder.create().withTickingState().build()
+                    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()
             ));
         }
     }
@@ -264,15 +276,19 @@ public class ServerNetworkHandler
             tag.putFloat("TickRate", TickSpeed.tickrate);
             return this;
         }
-        private DataBuilder withTickingState()
+        private DataBuilder withFrozenState()
         {
             CompoundTag tickingState = new CompoundTag();
-            tickingState.putBoolean("is_paused", TickSpeed.is_paused);
-            tickingState.putBoolean("deepFreeze", TickSpeed.deepFreeze);
-            tickingState.putBoolean("is_superhot", TickSpeed.is_superHot);
+            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);
diff --git a/src/main/java/carpet/script/CarpetEventServer.java b/src/main/java/carpet/script/CarpetEventServer.java
index 69aec8c9..a2ba8207 100644
--- a/src/main/java/carpet/script/CarpetEventServer.java
+++ b/src/main/java/carpet/script/CarpetEventServer.java
@@ -961,7 +961,7 @@ public class CarpetEventServer
 
     public void tick()
     {
-        if (!TickSpeed.process_entities && TickSpeed.deepFreeze)
+        if (!TickSpeed.process_entities && TickSpeed.deeplyFrozen())
             return;
         Iterator eventIterator = scheduledCalls.iterator();
         List currentCalls = new ArrayList<>();

From e38e8f74ea99911bd008e41204cd68ff9d902f32 Mon Sep 17 00:00:00 2001
From: altrisi 
Date: Tue, 29 Dec 2020 16:17:59 +0100
Subject: [PATCH 21/27] Better javadoc for #deeplyFrozen()

---
 src/main/java/carpet/helpers/TickSpeed.java | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/main/java/carpet/helpers/TickSpeed.java b/src/main/java/carpet/helpers/TickSpeed.java
index ef1982cd..a71301fb 100644
--- a/src/main/java/carpet/helpers/TickSpeed.java
+++ b/src/main/java/carpet/helpers/TickSpeed.java
@@ -39,10 +39,11 @@ public class TickSpeed
     }
 
     /**
+     * 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 used with {@link #process_entities} to make sure the 
-     * current tick is actually frozen
+     * 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() {

From 96244dc4e2f8f42f242393503fc43557efd6ee10 Mon Sep 17 00:00:00 2001
From: altrisi 
Date: Wed, 30 Dec 2020 14:13:56 +0100
Subject: [PATCH 22/27] Update Gradle and reintroduce automatic JSON version

---
 build.gradle                             |  47 ++++++++++++++---------
 gradle.properties                        |   2 +-
 gradle/wrapper/gradle-wrapper.jar        | Bin 55616 -> 59203 bytes
 gradle/wrapper/gradle-wrapper.properties |   2 +-
 gradlew                                  |  34 ++++++++--------
 gradlew.bat                              |  25 ++++--------
 src/main/resources/fabric.mod.json       |   2 +-
 7 files changed, 54 insertions(+), 58 deletions(-)

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/gradle.properties b/gradle.properties
index 9551a36d..83348ba8 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 5c2d1cf016b3885f6930543d57b744ea8c220a1a..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f 100644
GIT binary patch
delta 23334
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
WDWbsF$p{Gi@ZUasP|4|kdH)CXgbPdn

delta 19998
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

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/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
index be2c43ff..af56a541 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",

From ffeee1fa9d946e056f5daa9136fb2963c75c4282 Mon Sep 17 00:00:00 2001
From: altrisi 
Date: Sat, 2 Jan 2021 12:57:45 +0100
Subject: [PATCH 23/27] Freeze schedules too

---
 src/main/java/carpet/script/CarpetEventServer.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/carpet/script/CarpetEventServer.java b/src/main/java/carpet/script/CarpetEventServer.java
index a2ba8207..71157512 100644
--- a/src/main/java/carpet/script/CarpetEventServer.java
+++ b/src/main/java/carpet/script/CarpetEventServer.java
@@ -961,7 +961,7 @@ public class CarpetEventServer
 
     public void tick()
     {
-        if (!TickSpeed.process_entities && TickSpeed.deeplyFrozen())
+        if (!TickSpeed.process_entities)
             return;
         Iterator eventIterator = scheduledCalls.iterator();
         List currentCalls = new ArrayList<>();

From 706d632956e4d2eec29d34a7d95e4db165aee877 Mon Sep 17 00:00:00 2001
From: altrisi 
Date: Sun, 3 Jan 2021 23:55:47 +0100
Subject: [PATCH 24/27] Also freeze shapes

---
 src/main/java/carpet/mixins/MinecraftClientMixin.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/carpet/mixins/MinecraftClientMixin.java b/src/main/java/carpet/mixins/MinecraftClientMixin.java
index 974ac320..d0a340a5 100644
--- a/src/main/java/carpet/mixins/MinecraftClientMixin.java
+++ b/src/main/java/carpet/mixins/MinecraftClientMixin.java
@@ -28,7 +28,7 @@ public class MinecraftClientMixin
         if (this.world != null) {
             if (CarpetServer.minecraft_server == null)
                 TickSpeed.tick();
-            if (!TickSpeed.process_entities && TickSpeed.deeplyFrozen())
+            if (!TickSpeed.process_entities)
                 CarpetClient.shapes.renewShapes();
         }
     }

From cdaf17ea1ec359c2a8e1e77fcfdc7e8d273ea85b Mon Sep 17 00:00:00 2001
From: altrisi 
Date: Tue, 5 Jan 2021 02:42:02 +0100
Subject: [PATCH 25/27] JSON support tweaks

---
 src/main/java/carpet/script/api/Auxiliary.java | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java
index 0fe2a643..7625ea94 100644
--- a/src/main/java/carpet/script/api/Auxiliary.java
+++ b/src/main/java/carpet/script/api/Auxiliary.java
@@ -73,6 +73,7 @@ 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;
@@ -91,7 +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));
-    public static final Gson gson = new GsonBuilder().setPrettyPrinting().registerTypeAdapter(Value.class, new ScarpetJsonDeserializer()).create();
+    public static final Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().registerTypeAdapter(Value.class, new ScarpetJsonDeserializer()).create();
 
     public static String recognizeResource(Value value, boolean isFloder)
     {
@@ -822,7 +823,18 @@ public class Auxiliary {
             }
             else if (fdesc.getMiddle().equals("json"))
             {
-                JsonElement json = ((CarpetScriptHost) c.host).readJsonFile(fdesc.getLeft(), fdesc.getMiddle(), fdesc.getRight());
+                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;

From d853ec69e6f807a386ebb5a00a1ff46498dd3044 Mon Sep 17 00:00:00 2001
From: BisUmTo <31419791+BisUmTo@users.noreply.github.com>
Date: Tue, 5 Jan 2021 19:21:59 +0100
Subject: [PATCH 26/27] Missing 'invulnerable' entity modifier in the Docs

---
 docs/scarpet/api/Entities.md | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/docs/scarpet/api/Entities.md b/docs/scarpet/api/Entities.md
index 7f03f748..bbd2872e 100644
--- a/docs/scarpet/api/Entities.md
+++ b/docs/scarpet/api/Entities.md
@@ -246,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.
@@ -726,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.

From f3f8c689a8f370f6ce187242c1c13dd7fa57452e Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Thu, 7 Jan 2021 00:40:39 -0400
Subject: [PATCH 27/27] fixing incorrect default upper bound for `slice`. Fixes
 #657

---
 src/main/java/carpet/script/language/DataStructures.java | 2 +-
 src/main/java/carpet/script/value/FunctionValue.java     | 2 +-
 src/main/java/carpet/script/value/LazyListValue.java     | 4 ++--
 src/main/java/carpet/script/value/ListValue.java         | 4 +++-
 src/main/java/carpet/script/value/MapValue.java          | 2 +-
 src/main/java/carpet/script/value/Value.java             | 3 ++-
 6 files changed, 10 insertions(+), 7 deletions(-)

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/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 f6a3d8eb..a036af56 100644
--- a/src/main/java/carpet/script/value/ListValue.java
+++ b/src/main/java/carpet/script/value/ListValue.java
@@ -334,11 +334,13 @@ public class ListValue extends AbstractListValue implements ContainerValueInterf
     }
 
     @Override
-    public Value slice(long fromDesc, long toDesc)
+    public Value slice(long fromDesc, Long toDesc)
     {
         List items = getItems();
         int size = items.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();
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/Value.java b/src/main/java/carpet/script/value/Value.java
index aea956a6..8f79f462 100644
--- a/src/main/java/carpet/script/value/Value.java
+++ b/src/main/java/carpet/script/value/Value.java
@@ -178,11 +178,12 @@ public abstract class Value implements Comparable, Cloneable
         return getString().length();
     }
 
-    public Value slice(long fromDesc, long toDesc)
+    public Value slice(long fromDesc, Long toDesc)
     {
         String value = this.getString();
         int size = value.length();
         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));