From 8dbf269fdeb336cfdc5df49b5072b64b36dfeee8 Mon Sep 17 00:00:00 2001 From: gnembon Date: Wed, 15 Apr 2020 01:58:46 -0300 Subject: [PATCH] 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