From dd4f2aa12d4f4953a4cee7bbbf9e688161f7a445 Mon Sep 17 00:00:00 2001 From: X-Green <35984104+X-Green@users.noreply.github.com> Date: Sun, 12 Apr 2020 10:55:22 +0800 Subject: [PATCH 1/6] More infomation in TNT logging #1 --- .../carpet/logging/logHelpers/TNTLogHelper.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/java/carpet/logging/logHelpers/TNTLogHelper.java b/src/main/java/carpet/logging/logHelpers/TNTLogHelper.java index 7e51f743..41078113 100644 --- a/src/main/java/carpet/logging/logHelpers/TNTLogHelper.java +++ b/src/main/java/carpet/logging/logHelpers/TNTLogHelper.java @@ -9,6 +9,8 @@ public class TNTLogHelper { public boolean initialized; private double primedX, primedY, primedZ; + private static long lastGametime = 0; + private static int tntCount = 0; private Vec3d primedAngle; /** * Runs when the TNT is primed. Expects the position and motion angle of the TNT. @@ -24,18 +26,30 @@ public class TNTLogHelper /** * Runs when the TNT explodes. Expects the position of the TNT. */ - public void onExploded(double x, double y, double z) + public void onExploded(double x, double y, double z, long gametime) { + if (!(lastGametime == gametime)){ + tntCount = 0; + lastGametime = gametime; + } + tntCount++; LoggerRegistry.getLogger("tnt").log( (option) -> { switch (option) { case "brief": return new BaseText[]{Messenger.c( + "g #" + String.valueOf(tntCount), + "m @" + String.valueOf(gametime), + "g : ", "l P ",Messenger.dblt("l",primedX,primedY,primedZ), "w ",Messenger.dblt("l", primedAngle.x, primedAngle.y, primedAngle.z), "r E ",Messenger.dblt("r",x, y, z))}; case "full": return new BaseText[]{Messenger.c( + "g #" + String.valueOf(tntCount), + "m @" + String.valueOf(gametime), + "g : ", + "g #" + String.valueOf(tntCount) + ": ", "l P ",Messenger.dblf("l",primedX,primedY,primedZ), "w ",Messenger.dblf("l", primedAngle.x, primedAngle.y, primedAngle.z), "r E ",Messenger.dblf("r",x, y, z))}; From ec0aecf6b5c564c38d7b5b9a669b42ec91ef9c78 Mon Sep 17 00:00:00 2001 From: X-Green <35984104+X-Green@users.noreply.github.com> Date: Sun, 12 Apr 2020 10:56:06 +0800 Subject: [PATCH 2/6] More infomation in TNT logging #2 --- src/main/java/carpet/mixins/TntEntityMixin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/carpet/mixins/TntEntityMixin.java b/src/main/java/carpet/mixins/TntEntityMixin.java index 4a296389..2e67b1e5 100644 --- a/src/main/java/carpet/mixins/TntEntityMixin.java +++ b/src/main/java/carpet/mixins/TntEntityMixin.java @@ -72,7 +72,7 @@ public abstract class TntEntityMixin extends Entity implements TntEntityInterfac private void onExplode(CallbackInfo ci) { if (LoggerRegistry.__tnt && logHelper != null) - logHelper.onExploded(getX(), getY(), getZ()); + logHelper.onExploded(getX(), getY(), getZ(), this.world.getTime()); if (mergedTNT > 1) for (int i = 0; i < mergedTNT - 1; i++) From 07b3029820a99b41a15b784ffea77040e1464bd6 Mon Sep 17 00:00:00 2001 From: gnembon Date: Mon, 13 Apr 2020 02:04:00 -0300 Subject: [PATCH 3/6] added mixer option and defaulted sounds to master. Fixes #201 --- docs/scarpet/Full.md | 9 ++++++--- docs/scarpet/api/Auxiliary.md | 9 ++++++--- src/main/java/carpet/script/CarpetExpression.java | 12 +++++++++++- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index baed6c5d..3668b28f 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -2932,10 +2932,13 @@ Collection of other methods that control smaller, yet still important aspects of ## Sounds -### `sound(name, pos, volume?, pitch?)` +### `sound(name, pos, volume?, pitch?, mixer?)` -Plays a specific sound `name`, at block or position `pos`, with optional `volume` and modified `pitch`. `pos` can be -either a block, triple of coords, or a list of thee numbers. Uses the same options as a corresponding `playsound` command. +Plays a specific sound `name`, at block or position `pos`, with optional `volume` and modified `pitch`, and under +optional `mixer`. Default values for `volume`, `pitch` and `mixer` are `1.0`, `1.0`, and `master`. +Valid mixer options are `master`, `music`, `record`, `weather`, `block`, `hostile`,`neutral`, `player`, `ambient` +and `voice`. `pos` can be either a block, triple of coords, or a list of thee numbers. Uses the same options as a + corresponding `playsound` command. ## Particles diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index 17b1d003..205656ae 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -4,10 +4,13 @@ Collection of other methods that control smaller, yet still important aspects of ## Sounds -### `sound(name, pos, volume?, pitch?)` +### `sound(name, pos, volume?, pitch?, mixer?)` -Plays a specific sound `name`, at block or position `pos`, with optional `volume` and modified `pitch`. `pos` can be -either a block, triple of coords, or a list of thee numbers. Uses the same options as a corresponding `playsound` command. +Plays a specific sound `name`, at block or position `pos`, with optional `volume` and modified `pitch`, and under +optional `mixer`. Default values for `volume`, `pitch` and `mixer` are `1.0`, `1.0`, and `master`. +Valid mixer options are `master`, `music`, `record`, `weather`, `block`, `hostile`,`neutral`, `player`, `ambient` +and `voice`. `pos` can be either a block, triple of coords, or a list of thee numbers. Uses the same options as a + corresponding `playsound` command. ## Particles diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java index f246d4fb..d78e178a 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -132,6 +132,7 @@ import java.util.Optional; import java.util.UUID; import java.util.function.BiFunction; import java.util.function.Predicate; +import java.util.stream.Collector; import java.util.stream.Collectors; import static carpet.script.utils.WorldTools.canHasChunk; @@ -2378,6 +2379,8 @@ public class CarpetExpression }); } + private static Map mixerMap = Arrays.stream(SoundCategory.values()).collect(Collectors.toMap(SoundCategory::getName, k -> k)); + private void API_AuxiliaryAspects() { this.expr.addLazyFunction("sound", -1, (c, t, lv) -> { @@ -2388,12 +2391,19 @@ public class CarpetExpression throw new InternalExpressionException("No such sound: "+soundName.getPath()); float volume = 1.0F; float pitch = 1.0F; + SoundCategory mixer = SoundCategory.MASTER; if (lv.size() > 0+locator.offset) { volume = (float) NumericValue.asNumber(lv.get(0+locator.offset).evalValue(c)).getDouble(); if (lv.size() > 1+locator.offset) { pitch = (float) NumericValue.asNumber(lv.get(1+locator.offset).evalValue(c)).getDouble(); + if (lv.size() > 2+locator.offset) + { + String mixerName = lv.get(2+locator.offset).evalValue(c).getString(); + mixer = mixerMap.get(mixerName.toLowerCase(Locale.ROOT)); + if (mixer == null) throw new InternalExpressionException(mixerName +" is not a valid mixer name"); + } } } Vec3d vec = locator.vec; @@ -2402,7 +2412,7 @@ public class CarpetExpression for (ServerPlayerEntity player : cc.s.getWorld().getPlayers( (p) -> p.squaredDistanceTo(vec) < d0)) { count++; - player.networkHandler.sendPacket(new PlaySoundIdS2CPacket(soundName, SoundCategory.PLAYERS, vec, volume, pitch)); + player.networkHandler.sendPacket(new PlaySoundIdS2CPacket(soundName, mixer, vec, volume, pitch)); } int totalPlayed = count; return (_c, _t) -> new NumericValue(totalPlayed); From 3e76adbdaaedd4afe5c13e8fed5fad309227b4d2 Mon Sep 17 00:00:00 2001 From: gnembon Date: Tue, 14 Apr 2020 01:28:00 -0300 Subject: [PATCH 4/6] added deep frozen state where chunk tickets will not be expiring as well, preventing chunks from unloading. Fixes #204 --- .../java/carpet/commands/TickCommand.java | 10 +++++--- src/main/java/carpet/helpers/TickSpeed.java | 1 + .../mixins/ChunkTicketManager_tickMixin.java | 23 +++++++++++++++++++ .../MinecraftServer_tickspeedMixin.java | 2 +- src/main/resources/carpet.mixins.json | 1 + 5 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 src/main/java/carpet/mixins/ChunkTicketManager_tickMixin.java diff --git a/src/main/java/carpet/commands/TickCommand.java b/src/main/java/carpet/commands/TickCommand.java index ac65b7bd..cc7dc38c 100644 --- a/src/main/java/carpet/commands/TickCommand.java +++ b/src/main/java/carpet/commands/TickCommand.java @@ -43,7 +43,8 @@ public class TickCommand c.getSource(), getInteger(c,"ticks"), getString(c, "tail command")))))). - then(literal("freeze").executes( (c)-> toggleFreeze(c.getSource()))). + then(literal("freeze").executes( (c)-> toggleFreeze(c.getSource(), false)). + then(literal("deep").executes( (c)-> toggleFreeze(c.getSource(), true)))). then(literal("step"). executes((c) -> step(1)). then(argument("ticks", integer(1,72000)). @@ -95,15 +96,18 @@ public class TickCommand return 1; } - private static int toggleFreeze(ServerCommandSource source) + private static int toggleFreeze(ServerCommandSource source, boolean isDeep) { TickSpeed.is_paused = !TickSpeed.is_paused; if (TickSpeed.is_paused) { - Messenger.m(source, "gi Game is paused"); + TickSpeed.deepFreeze = isDeep; + Messenger.m(source, "gi Game is "+(isDeep?"deeply ":"")+"frozen"); + } else { + TickSpeed.deepFreeze = false; Messenger.m(source, "gi Game runs normally"); } return 1; diff --git a/src/main/java/carpet/helpers/TickSpeed.java b/src/main/java/carpet/helpers/TickSpeed.java index 79d05576..2daf4fd1 100644 --- a/src/main/java/carpet/helpers/TickSpeed.java +++ b/src/main/java/carpet/helpers/TickSpeed.java @@ -25,6 +25,7 @@ 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; public static boolean is_superHot = false; diff --git a/src/main/java/carpet/mixins/ChunkTicketManager_tickMixin.java b/src/main/java/carpet/mixins/ChunkTicketManager_tickMixin.java new file mode 100644 index 00000000..9b70b707 --- /dev/null +++ b/src/main/java/carpet/mixins/ChunkTicketManager_tickMixin.java @@ -0,0 +1,23 @@ +package carpet.mixins; + +import carpet.helpers.TickSpeed; +import net.minecraft.server.world.ChunkTicketManager; +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; + +@Mixin(ChunkTicketManager.class) +public class ChunkTicketManager_tickMixin +{ + @Shadow private long age; + + @Inject(method = "purge", at = @At("HEAD"), cancellable = true) + private void pauseTicketSystem(CallbackInfo ci) + { + // pausing expiry of tickets + // that will prevent also chunks from unloading, so require a deep frozen state + if (!TickSpeed.process_entities && TickSpeed.deepFreeze) age--; + } +} diff --git a/src/main/java/carpet/mixins/MinecraftServer_tickspeedMixin.java b/src/main/java/carpet/mixins/MinecraftServer_tickspeedMixin.java index ac475142..9b695772 100644 --- a/src/main/java/carpet/mixins/MinecraftServer_tickspeedMixin.java +++ b/src/main/java/carpet/mixins/MinecraftServer_tickspeedMixin.java @@ -120,7 +120,7 @@ public abstract class MinecraftServer_tickspeedMixin extends ReentrantThreadExec this.profiler.startTick(); this.profiler.push("tick"); - this.tick(TickSpeed.time_warp_start_time != 0 ? ()->true : this::shouldKeepTicking); + this.tick(TickSpeed.time_warp_start_time != 0 ? ()->!hasRunningTasks() : this::shouldKeepTicking); this.profiler.swap("nextTickWait"); this.field_19249 = true; this.field_19248 = Math.max(Util.getMeasuringTimeMs() + /*50L*/ msThisTick, this.timeReference); diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json index ea11cc6d..80faf7b8 100644 --- a/src/main/resources/carpet.mixins.json +++ b/src/main/resources/carpet.mixins.json @@ -25,6 +25,7 @@ "ServerWorld_tickMixin", "ServerChunkManager_tickMixin", "ThreadedAnvilChunkStorage_tickMixin", + "ChunkTicketManager_tickMixin", "ThreadedAnvilChunkStorage_scarpetChunkCreationMixin", "ChunkHolder_scarpetChunkCreationMixin", From 4c5b19767e3a903596a18da756821026447b95e4 Mon Sep 17 00:00:00 2001 From: X-Green <35984104+X-Green@users.noreply.github.com> Date: Tue, 14 Apr 2020 16:19:38 +0800 Subject: [PATCH 5/6] Update TNTLogHelper.java --- src/main/java/carpet/logging/logHelpers/TNTLogHelper.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/carpet/logging/logHelpers/TNTLogHelper.java b/src/main/java/carpet/logging/logHelpers/TNTLogHelper.java index 41078113..b968d0b4 100644 --- a/src/main/java/carpet/logging/logHelpers/TNTLogHelper.java +++ b/src/main/java/carpet/logging/logHelpers/TNTLogHelper.java @@ -38,9 +38,6 @@ public class TNTLogHelper { case "brief": return new BaseText[]{Messenger.c( - "g #" + String.valueOf(tntCount), - "m @" + String.valueOf(gametime), - "g : ", "l P ",Messenger.dblt("l",primedX,primedY,primedZ), "w ",Messenger.dblt("l", primedAngle.x, primedAngle.y, primedAngle.z), "r E ",Messenger.dblt("r",x, y, z))}; From 8dbf269fdeb336cfdc5df49b5072b64b36dfeee8 Mon Sep 17 00:00:00 2001 From: gnembon Date: Wed, 15 Apr 2020 01:58:46 -0300 Subject: [PATCH 6/6] added world_time and day_time. Add ability to store app data in shared space. as well as store in file hierarchies. Fixes #215 --- docs/scarpet/Full.md | 44 ++++++++++++++++--- docs/scarpet/api/Auxiliary.md | 43 +++++++++++++++--- docs/scarpet/resources/editors/idea/2.txt | 4 +- .../scarpet/resources/editors/npp/scarpet.xml | 2 +- .../java/carpet/script/CarpetExpression.java | 44 ++++++++++++++++--- .../java/carpet/script/CarpetScriptHost.java | 18 ++++---- .../carpet/script/bundled/FileModule.java | 5 ++- .../java/carpet/script/bundled/Module.java | 41 ++++++++++------- 8 files changed, 150 insertions(+), 51 deletions(-) diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index 3668b28f..5447429a 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -3007,23 +3007,52 @@ run('give @s stone 4') -> 1 // this operation was successful once Performs autosave, saves all chunks, player data, etc. Useful for programs where autosave is disabled due to performance reasons and saves the world only on demand. -### `load_app_data(), load_app_data(file)` +### `load_app_data(), load_app_data(file), load_app_data(file, shared?)` Loads the app data associated with the app from the world /scripts folder. Without argument returns the memory managed and buffered / throttled NBT tag. With a file name - reads explicitly a file with that name from the -scripts folder. +scripts folder that belongs exclusively to the app. if `shared` is true, the file location is not exclusive +to the app anymore, but located in a shared app space. + +File descriptor can contain letters, numbers and folder separator: `'/'`. Any other characters are stripped +from the name before saving/loading. Empty descriptors are invalid. + +Function returns nbt value with the file content, or `null` if the file is missing or there were problems +with retrieving the data. + +The default no-name app, via `/script run` command can only save/load file from the shared data location. + +If the app's name is `'foo'`, the script location would +be `world/scripts/foo.sc`, system-managed default app data storage is in `world/scripts/foo.data.nbt`, app +specific data directory is under `world/scripts/foo.data/bar/../baz.nbt`, and shared data space is under +`world/scripts/shared/bar/../baz.nbt`. You can use app data to save non-vanilla information separately from the world and other scripts. -### `store_app_data(tag), store_app_data(tag, file)` +### `store_app_data(tag), store_app_data(tag, file), store_app_data(tag, file, shared?)` -Stores the app data associated with the app from the world /scripts folder. With the `file` parameter saves -immediately and with every call, without `file` parameter, it may take up to 10 seconds for the output file +Stores the app data associated with the app from the world `/scripts` folder. With the `file` parameter saves +immediately and with every call to a specific file defined by the `file`, either in app space, or in the scripts +shared space if `shared` is true. Without `file` parameter, it may take up to 10 + seconds for the output file to sync preventing flickering in case this tag changes frequently. It will be synced when server closes. +Returns `true` if the file was saved successfully, `false` otherwise. + +Uses the same file structure for exclusive app data, and shared data folder as `load_app_data`. + ### `tick_time()` -Returns game tick counter. Can be used to run certain operations every n-th ticks, or to count in-game time +Returns server tick counter. Can be used to run certain operations every n-th ticks, or to count in-game time. + +### `world_time()` + +Returns dimension-specific tick counter. + +### `day_time(new_time?)` + +Returns current daytime clock value. If `new_time` is specified, sets a new clock +to that value. Daytime clocks are shared between all dimensions. ### `game_tick(mstime?)` @@ -3074,7 +3103,8 @@ For the options of `entry`, consult your statistics page, or give it a guess. The call will return `null` if the statistics options are incorrect, or player didn't get these in their history. If the player encountered the statistic, or game created for him empty one, it will return a number. Scarpet will not affect the entries of the statistics, even if it is just creating empty ones. With `null` response -it could either mean your input is wrong, or statistic has effectively a value of `0`.# `/script run` command +it could either mean your input is wrong, or statistic has effectively a value of `0`. +# `/script run` command Primary way to input commands. The command executes in the context, position, and dimension of the executing player, commandblock, etc... The command receives 4 variables, `x`, `y`, `z` and `p` indicating position and diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index 205656ae..52941068 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -79,23 +79,52 @@ run('give @s stone 4') -> 1 // this operation was successful once Performs autosave, saves all chunks, player data, etc. Useful for programs where autosave is disabled due to performance reasons and saves the world only on demand. -### `load_app_data(), load_app_data(file)` +### `load_app_data(), load_app_data(file), load_app_data(file, shared?)` Loads the app data associated with the app from the world /scripts folder. Without argument returns the memory managed and buffered / throttled NBT tag. With a file name - reads explicitly a file with that name from the -scripts folder. +scripts folder that belongs exclusively to the app. if `shared` is true, the file location is not exclusive +to the app anymore, but located in a shared app space. + +File descriptor can contain letters, numbers and folder separator: `'/'`. Any other characters are stripped +from the name before saving/loading. Empty descriptors are invalid. + +Function returns nbt value with the file content, or `null` if the file is missing or there were problems +with retrieving the data. + +The default no-name app, via `/script run` command can only save/load file from the shared data location. + +If the app's name is `'foo'`, the script location would +be `world/scripts/foo.sc`, system-managed default app data storage is in `world/scripts/foo.data.nbt`, app +specific data directory is under `world/scripts/foo.data/bar/../baz.nbt`, and shared data space is under +`world/scripts/shared/bar/../baz.nbt`. You can use app data to save non-vanilla information separately from the world and other scripts. -### `store_app_data(tag), store_app_data(tag, file)` +### `store_app_data(tag), store_app_data(tag, file), store_app_data(tag, file, shared?)` -Stores the app data associated with the app from the world /scripts folder. With the `file` parameter saves -immediately and with every call, without `file` parameter, it may take up to 10 seconds for the output file +Stores the app data associated with the app from the world `/scripts` folder. With the `file` parameter saves +immediately and with every call to a specific file defined by the `file`, either in app space, or in the scripts +shared space if `shared` is true. Without `file` parameter, it may take up to 10 + seconds for the output file to sync preventing flickering in case this tag changes frequently. It will be synced when server closes. +Returns `true` if the file was saved successfully, `false` otherwise. + +Uses the same file structure for exclusive app data, and shared data folder as `load_app_data`. + ### `tick_time()` -Returns game tick counter. Can be used to run certain operations every n-th ticks, or to count in-game time +Returns server tick counter. Can be used to run certain operations every n-th ticks, or to count in-game time. + +### `world_time()` + +Returns dimension-specific tick counter. + +### `day_time(new_time?)` + +Returns current daytime clock value. If `new_time` is specified, sets a new clock +to that value. Daytime clocks are shared between all dimensions. ### `game_tick(mstime?)` @@ -146,4 +175,4 @@ For the options of `entry`, consult your statistics page, or give it a guess. The call will return `null` if the statistics options are incorrect, or player didn't get these in their history. If the player encountered the statistic, or game created for him empty one, it will return a number. Scarpet will not affect the entries of the statistics, even if it is just creating empty ones. With `null` response -it could either mean your input is wrong, or statistic has effectively a value of `0`. \ No newline at end of file +it could either mean your input is wrong, or statistic has effectively a value of `0`. diff --git a/docs/scarpet/resources/editors/idea/2.txt b/docs/scarpet/resources/editors/idea/2.txt index c98522d1..31aa5e6f 100644 --- a/docs/scarpet/resources/editors/idea/2.txt +++ b/docs/scarpet/resources/editors/idea/2.txt @@ -13,6 +13,7 @@ brightness chunk_tickets create_marker current_dimension +day_time destroy diamond drop_item @@ -95,4 +96,5 @@ top transparent update volume -without_updates \ No newline at end of file +without_updates +world_time \ No newline at end of file diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml index 8c06f293..6f5366e0 100644 --- a/docs/scarpet/resources/editors/npp/scarpet.xml +++ b/docs/scarpet/resources/editors/npp/scarpet.xml @@ -67,7 +67,7 @@ logger sound particle particle_line particle_rect - run save tick_time game_tick seed current_dimension in_dimension + run save tick_time world_time day_time game_tick seed current_dimension in_dimension plop harvest destroy schedule nbt create_marker remove_all_markers load_app_data store_app_data escape_nbt statistic diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java index d78e178a..d11f126e 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -2669,6 +2669,24 @@ public class CarpetExpression return (cc, tt) -> time; }); + this.expr.addLazyFunction("world_time", 0, (c, t, lv) -> + { + Value time = new NumericValue(((CarpetContext) c).s.getWorld().getTime()); + return (cc, tt) -> time; + }); + + this.expr.addLazyFunction("day_time", -1, (c, t, lv) -> + { + Value time = new NumericValue(((CarpetContext) c).s.getWorld().getTimeOfDay()); + if (lv.size() > 0) + { + long newTime = NumericValue.asNumber(lv.get(0).evalValue(c)).getLong(); + if (newTime < 0) newTime = 0; + ((CarpetContext) c).s.getWorld().setTimeOfDay(newTime); + } + return (cc, tt) -> time; + }); + this.expr.addLazyFunction("game_tick", -1, (c, t, lv) -> { ServerCommandSource s = ((CarpetContext)c).s; if (!s.getMinecraftServer().isOnThread()) throw new InternalExpressionException("Unable to run ticks from threads"); @@ -2805,16 +2823,22 @@ public class CarpetExpression this.expr.addLazyFunction("load_app_data", -1, (c, t, lv) -> { String file = null; + boolean shared = false; if (lv.size()>0) { String origfile = lv.get(0).evalValue(c).getString(); - file = origfile.toLowerCase(Locale.ROOT).replaceAll("[^A-Za-z0-9]", ""); + file = origfile.toLowerCase(Locale.ROOT).replaceAll("[^A-Za-z0-9/]", ""); + file = Arrays.stream(file.split("/+")).filter(s -> !s.isEmpty()).collect(Collectors.joining("/")); if (file.isEmpty()) { - throw new InternalExpressionException("Cannot use "+file+" as resource name - must have some letters and numbers"); + throw new InternalExpressionException("Cannot use "+origfile+" as resource name - must have at least some letters and numbers"); + } + if (lv.size() > 1) + { + shared = lv.get(1).evalValue(c).getBoolean(); } } - Tag state = ((CarpetScriptHost)((CarpetContext)c).host).getGlobalState(file); + Tag state = ((CarpetScriptHost)((CarpetContext)c).host).getGlobalState(file, shared); if (state == null) return (cc, tt) -> Value.NULL; Value retVal = new NBTSerializableValue(state); @@ -2827,21 +2851,27 @@ public class CarpetExpression throw new InternalExpressionException("'store_app_data' needs NBT tag and an optional file"); Value val = lv.get(0).evalValue(c); String file = null; + boolean shared = false; if (lv.size()>1) { String origfile = lv.get(1).evalValue(c).getString(); - file = origfile.toLowerCase(Locale.ROOT).replaceAll("[^A-Za-z0-9]", ""); + file = origfile.toLowerCase(Locale.ROOT).replaceAll("[^A-Za-z0-9/]", ""); + file = Arrays.stream(file.split("/+")).filter(s -> !s.isEmpty()).collect(Collectors.joining("/")); if (file.isEmpty()) { - throw new InternalExpressionException("Cannot use "+file+" as resource name - must have some letters and numbers"); + throw new InternalExpressionException("Cannot use "+origfile+" as resource name - must have some letters and numbers"); + } + if (lv.size() > 2) + { + shared = lv.get(2).evalValue(c).getBoolean(); } } NBTSerializableValue tagValue = (val instanceof NBTSerializableValue) ? (NBTSerializableValue) val : new NBTSerializableValue(val.getString()); Tag tag = tagValue.getTag(); - ((CarpetScriptHost)((CarpetContext)c).host).setGlobalState(tag, file); - return (cc, tt) -> Value.NULL; + boolean success = ((CarpetScriptHost)((CarpetContext)c).host).setGlobalState(tag, file, shared); + return success?LazyValue.TRUE:LazyValue.FALSE; }); this.expr.addLazyFunction("statistic", 3, (c, t, lv) -> diff --git a/src/main/java/carpet/script/CarpetScriptHost.java b/src/main/java/carpet/script/CarpetScriptHost.java index 0b1a5eb7..c6a8afa8 100644 --- a/src/main/java/carpet/script/CarpetScriptHost.java +++ b/src/main/java/carpet/script/CarpetScriptHost.java @@ -361,32 +361,31 @@ public class CarpetScriptHost extends ScriptHost private void dumpState() { - main.saveData(null, globalState); + Module.saveData(main, null, globalState, false); } private Tag loadState() { - return main.getData(null); + return Module.getData(main, null, false); } - public Tag getGlobalState(String file) + public Tag getGlobalState(String file, boolean isShared) { - if (getName() == null ) return null; + if (getName() == null && !isShared) return null; if (file != null) - return main.getData(file); + return Module.getData(main, file, isShared); if (parent == null) return globalState; return ((CarpetScriptHost)parent).globalState; } - public void setGlobalState(Tag tag, String file) + public boolean setGlobalState(Tag tag, String file, boolean isShared) { - if (getName() == null ) return; + if (getName() == null && !isShared) return false; // if belongs to an app, cannot be default host. if (file!= null) { - main.saveData(file, tag); - return; + return Module.saveData(main, file, tag, isShared); } CarpetScriptHost responsibleHost = (parent != null)?(CarpetScriptHost) parent:this; @@ -396,6 +395,7 @@ public class CarpetScriptHost extends ScriptHost responsibleHost.dumpState(); responsibleHost.saveTimeout = 200; } + return true; } public void tick() diff --git a/src/main/java/carpet/script/bundled/FileModule.java b/src/main/java/carpet/script/bundled/FileModule.java index c9ff21e1..ef44a292 100644 --- a/src/main/java/carpet/script/bundled/FileModule.java +++ b/src/main/java/carpet/script/bundled/FileModule.java @@ -74,7 +74,7 @@ public class FileModule extends Module } //copied private method from net.minecraft.nbt.NbtIo.write() and client method safe_write - public static void write(Tag tag_1, File file) + public static boolean write(Tag tag_1, File file) { File file_2 = new File(file.getAbsolutePath() + "_tmp"); if (file_2.exists()) file_2.delete(); @@ -90,9 +90,10 @@ public class FileModule extends Module } catch (IOException e) { - return; + return false; } if (file.exists()) file.delete(); if (!file.exists()) file_2.renameTo(file); + return true; } } diff --git a/src/main/java/carpet/script/bundled/Module.java b/src/main/java/carpet/script/bundled/Module.java index 1cc804f1..c15cebd4 100644 --- a/src/main/java/carpet/script/bundled/Module.java +++ b/src/main/java/carpet/script/bundled/Module.java @@ -4,7 +4,6 @@ import carpet.CarpetServer; import net.minecraft.nbt.Tag; import java.io.File; -import java.io.IOException; import java.nio.file.Files; import static carpet.script.bundled.FileModule.read; @@ -16,30 +15,38 @@ public abstract class Module public abstract String getCode(); public abstract boolean isLibrary(); - public Tag getData(String file) + private static String getDescriptor(Module module, String file, boolean isShared) { + if (isShared) + { + return "shared/"+file; + } + else if (module != null && module.getName() != null) // appdata + { + return module.getName()+".data"+(file==null?"":"/"+file); + } + else + { + throw new RuntimeException("Invalid file descriptor: "+file); + } + } + + public static Tag getData(Module module, String file, boolean isShared) + { + if (!isShared && (module == null || module.getName() == null)) return null; File dataFile = CarpetServer.minecraft_server.getLevelStorage().resolveFile( - CarpetServer.minecraft_server.getLevelName(), "scripts/"+getName()+".data"+(file==null?"":"."+file)+".nbt"); + CarpetServer.minecraft_server.getLevelName(), "scripts/"+getDescriptor(module, file, isShared)+".nbt"); if (!Files.exists(dataFile.toPath()) || !(dataFile.isFile())) return null; return read(dataFile); } - public void saveData(String file, Tag globalState) + public static boolean saveData(Module module, String file, Tag globalState, boolean isShared) { + if (!isShared && (module == null || module.getName() == null)) return false; File dataFile =CarpetServer.minecraft_server.getLevelStorage().resolveFile( - CarpetServer.minecraft_server.getLevelName(), "scripts/"+getName()+".data"+(file==null?"":"."+file)+".nbt"); - if (!Files.exists(dataFile.toPath().getParent())) - { - try - { - Files.createDirectory(dataFile.toPath().getParent()); - } - catch (IOException ignored) - { - } - } - write(globalState, dataFile); - + CarpetServer.minecraft_server.getLevelName(), "scripts/"+getDescriptor(module, file, isShared)+".nbt"); + if (!Files.exists(dataFile.toPath().getParent()) && !dataFile.getParentFile().mkdirs()) return false; + return write(globalState, dataFile); } @Override