diff --git a/base/data/gm4/function/announce_echeck_results.mcfunction b/base/data/gm4/function/announce_echeck_results.mcfunction new file mode 100644 index 0000000000..306b2ff201 --- /dev/null +++ b/base/data/gm4/function/announce_echeck_results.mcfunction @@ -0,0 +1,17 @@ +# Copies the results of environment checks into the log queue to prepare for printing to chat +# @s = none +# at unspecified +# run from await_echeck_results + +# if no check failed, no need to print +execute unless data storage gm4:log echecks[{result:{passed:0}}] unless data storage gm4:log echecks[{result:{passed:-1}}] run return 0 + +# copy results into queue, KEEP VERSION IN echecks, SO WE CAN INSPECT IT FOR DEBUGGING +data modify storage gm4:log queue set from storage gm4:log echecks + +# add extra text around warnings +data modify storage gm4:log queue prepend value {type:"text",message:{"text":"[GM4]: Some environment checks have not succeeded:","color":"#4AA0C7"}} +data modify storage gm4:log queue append value {type:"text",message:{"text":"[GM4]: This may lead to unintended behavior.","color":"#4AA0C7"}} + +# start announcing +function gm4:log_start diff --git a/base/data/gm4/function/await_echeck_results.mcfunction b/base/data/gm4/function/await_echeck_results.mcfunction new file mode 100644 index 0000000000..6ea4837570 --- /dev/null +++ b/base/data/gm4/function/await_echeck_results.mcfunction @@ -0,0 +1,12 @@ +# Waits until all environment checks have returned their results +# run from post_load + +# maintain timeout timer +scoreboard players remove $echeck_timeout gm4_data 1 +execute if score $echeck_timeout gm4_data matches ..0 run return run function gm4:announce_echeck_results + +# Peek if any environment checks are still pending (passed:-1), if so, continue waiting +execute if data storage gm4:log echecks[{result:{passed:-1}}] run return run schedule function gm4:await_echeck_results 1t + +# all test have returned, print to chat +function gm4:announce_echeck_results diff --git a/base/data/gm4/function/echeck/assign_score.mcfunction b/base/data/gm4/function/echeck/assign_score.mcfunction new file mode 100644 index 0000000000..69bf592911 --- /dev/null +++ b/base/data/gm4/function/echeck/assign_score.mcfunction @@ -0,0 +1,17 @@ +# Assigns a score to this marker and reads it back. Kills the marker afterwards +# @s = test marker, just summoned +# at @s +# run from gm4:echeck/non_player_entity_has_score +# set up marker & run test +scoreboard players set @s gm4_data 1 +# depending on test outcome, set the 'result' object in storage. Base uses this to test if the check has completed. + +# if the score is present, mark the check as passed by removing it from storage +execute if score @s gm4_data matches 1 run data modify storage gm4:log echecks[{echeck_id:"gm4:echeck/non_player_entity_has_score"}].result set value {passed:1,probable_cause:""} + +# if no score is present, we don't know why this would happen. Good luck. +execute unless score @s gm4_data matches 1 run data modify storage gm4:log echecks[{echeck_id:"gm4:echeck/non_player_entity_has_score"}].result set value {passed:0,probable_cause:""} + +# clean up marker +scoreboard players reset @s gm4_data +kill @s diff --git a/base/data/gm4/function/echeck/join_team.mcfunction b/base/data/gm4/function/echeck/join_team.mcfunction new file mode 100644 index 0000000000..8792840a0f --- /dev/null +++ b/base/data/gm4/function/echeck/join_team.mcfunction @@ -0,0 +1,19 @@ +# Assigns a score to this marker and reads it back. Kills the marker afterwards +# @s = test marker, just summoned +# at @s +# run from gm4:echeck/non_player_entity_has_score +# set up marker & run test +team add gm4_echeck_non_player_entity_on_team_temp +team join gm4_echeck_non_player_entity_on_team_temp @s + +# depending on test outcome, set the 'result' object in storage. Base uses this to test if the check has completed. + +# if the marker is now on the team, mark the check as passed by removing it from storage +execute if entity @s[team=gm4_echeck_non_player_entity_on_team_temp] run data modify storage gm4:log echecks[{echeck_id:"gm4:echeck/non_player_entity_has_score"}].result set value {passed:1,probable_cause:""} + +# if the marker is not on the team, provide a probable cause message to the user +execute unless entity @s[team=gm4_echeck_non_player_entity_on_team_temp] run data modify storage gm4:log echecks[{echeck_id:"gm4:echeck/non_player_entity_has_score"}].result set value {passed:0,probable_cause:"This may be caused by the Paper/Spigot setting 'scoreboards.allow-non-player-entities-on-scoreboards=false'."} + +# clean up +team remove gm4_echeck_non_player_entity_on_team_temp +kill @s diff --git a/base/data/gm4/function/echeck/non_player_entity_has_score.mcfunction b/base/data/gm4/function/echeck/non_player_entity_has_score.mcfunction new file mode 100644 index 0000000000..b0ebaa886d --- /dev/null +++ b/base/data/gm4/function/echeck/non_player_entity_has_score.mcfunction @@ -0,0 +1,7 @@ +# Tests if non-player entities can hold scores. We think spigot might have done this at some point, but are not sure. +# @s = unspecified +# at unspecified +# run from modules which require this environment check + +# start environment check as marker +execute summon marker run function gm4:echeck/assign_score diff --git a/base/data/gm4/function/echeck/non_player_entity_on_team.mcfunction b/base/data/gm4/function/echeck/non_player_entity_on_team.mcfunction new file mode 100644 index 0000000000..9c2ac24891 --- /dev/null +++ b/base/data/gm4/function/echeck/non_player_entity_on_team.mcfunction @@ -0,0 +1,7 @@ +# Tests if non-player entities can be on teams. Spigot prevents non-players on teams if the config is changed. +# @s = unspecified +# at unspecified +# run from modules which require this environment check + +# start environment check as marker +execute summon marker run function gm4:echeck/join_team diff --git a/base/data/gm4/function/load.mcfunction b/base/data/gm4/function/load.mcfunction index 215ba36011..3aa804ccd6 100644 --- a/base/data/gm4/function/load.mcfunction +++ b/base/data/gm4/function/load.mcfunction @@ -1,4 +1,6 @@ data merge storage gm4:log {queue:[],versions:[]} +data modify storage gm4:log echecks set value [] +schedule clear gm4:await_echeck_results data modify storage gm4:log queue append value {type:"text",message:{"text":"[GM4]: Checking for updates...","color":"#4AA0C7"}} scoreboard objectives add gm4_modules dummy diff --git a/base/data/gm4/function/log.mcfunction b/base/data/gm4/function/log.mcfunction index 37194dc37c..873b6ee891 100644 --- a/base/data/gm4/function/log.mcfunction +++ b/base/data/gm4/function/log.mcfunction @@ -4,6 +4,8 @@ execute if data storage gm4:log log{type:"install"} run tellraw @a[tag=gm4_show_ execute if data storage gm4:log log{type:"missing"} run tellraw @a[tag=gm4_show_log] [{"nbt":"log.module","storage":"gm4:log","color":"red"},{"text":" is disabled because ","color":"red"},{"nbt":"log.require","storage":"gm4:log","color":"red"},{"text":" is not installed."}] execute if data storage gm4:log log{type:"outdated"} run function gm4:outdated_logs/outdated_start execute if data storage gm4:log log{type:"version_conflict"} run function gm4:conflict_logs/version_conflict_start +execute if data storage gm4:log log.echeck_id if data storage gm4:log log.result{passed:-1} run tellraw @a[tag=gm4_show_log] [{"text":"-","color":"white"},{"nbt":"log.echeck_id","storage":"gm4:log","interpret":true, "color": "yellow"},{"text":" has timed out!","color":"white"}] +execute if data storage gm4:log log.echeck_id if data storage gm4:log log.result{passed:0} run tellraw @a[tag=gm4_show_log] [{"text":"-","color":"white"},{"nbt":"log.echeck_id","storage":"gm4:log","interpret":true, "color": "yellow"},{"text":" has failed! ","color":"white"},{"nbt":"log.result.probable_cause","storage":"gm4:log","interpret":true,"color":"white"}] data remove storage gm4:log queue[0] execute store result score #log_size gm4_data run data get storage gm4:log queue diff --git a/base/data/gm4/function/post_load.mcfunction b/base/data/gm4/function/post_load.mcfunction index 1012328392..7dd809d2bb 100644 --- a/base/data/gm4/function/post_load.mcfunction +++ b/base/data/gm4/function/post_load.mcfunction @@ -1,4 +1,9 @@ +scoreboard players reset * gm4.echeck_results execute unless data storage gm4:log queue[{type:"install"}] run data modify storage gm4:log queue append value {type:"text",message:{"text":"[GM4]: No updates found.","color":"#4AA0C7"}} execute if data storage gm4:log queue[{type:"install"}] run data modify storage gm4:log queue append value {type:"text",message:{"text":"[GM4]: Updates completed.","color":"#4AA0C7"}} function gm4:log_wait + +function #gm4:evaluate_echecks +scoreboard players set $echeck_timeout gm4_data 600 +schedule function gm4:await_echeck_results 1t diff --git a/base/data/gm4/tags/function/evaluate_echecks.json b/base/data/gm4/tags/function/evaluate_echecks.json new file mode 100644 index 0000000000..c7be2aa97c --- /dev/null +++ b/base/data/gm4/tags/function/evaluate_echecks.json @@ -0,0 +1,5 @@ +{ + "values": [ + "gm4:echeck/non_player_entity_has_score" + ] +} diff --git a/docs/making-a-module.md b/docs/making-a-module.md index 44dfad1c68..85fcabea30 100644 --- a/docs/making-a-module.md +++ b/docs/making-a-module.md @@ -68,6 +68,9 @@ meta: - main # namespace assumed to be the module id - gm4_bat_grenades:tick # but one can be manually specified + # A list of checks to run on reload while the module is installed. May be omitted. + echecks: [gm4:non_player_entity_has_score] + website: # A description. This should be a good summary of what this module adds or achieves, to get someone interested in this module description: Break apart gold and iron tools and weapons for materials. Attach this to a mobfarm to finally make use of those extra armour sets! diff --git a/docs/utilities.md b/docs/utilities.md new file mode 100644 index 0000000000..6b9ae7e823 --- /dev/null +++ b/docs/utilities.md @@ -0,0 +1,130 @@ +# Utilities +Chances are, the tough problem you are trying to tackle has been solved by someone else working on Gamemode 4 long before you did. +This document will give you an overview of utilities and tools that have been developed over the years and which may help you when making a module. + +## Table of contents +* [Common Tags](#common-tags) + * [Blocks Tags](#block-tags) + * [Entity Tags](#entity-tags) +* [Environment Checks]() + * [Base Checks]() + * [Other Notable Checks]() + * [Creating New Checks]() +* [Upgrade Paths]() + +## Common Tags +Selecting blocks or entities with common properties is an error prone task, and subsequent updates often miss module-specific, hardcoded tags. +As such, `base` provides various tags that are maintained through updates and which should be prioritized over module-specific solutions. + +### Block Tags +Gamemode 4's default block tags are located at `base/data/gm4/tags/block/`. + +| Tag Name | Source | Description | +|---------------------|---------------------|------------------------------------------------------------------------------------------------------| +| #gm4:air | air.json | All air types. | +| #gm4:foliage | foliage.json | Naturally generating decoration on surfaces, which are easily broken, i.e. are washed away by water. | +| #gm4:full_collision | full_collision.json | All blocks that have a full-block collision box. | +| #gm4:no_collision | no_collision.json | All blocks without any collision, including air. | +| #gm4:replaceable | replaceable.json | Blocks that can be replaced by placing another block inside it, including air. | +| #gm4:water | water.json | Blocks that act as a water source. | +| #gm4:waterloggable | waterloggable.json | Blocks which can be water-logged. | + +### Entity Tags +Gamemode 4's default entity tags are located at `base/data/gm4/tags/block/`. + +| Tag Name | Source | Description | +|----------------------|----------------------|--------------------------------------------------------------------------------------------------------------------------------------| +| #gm4:boats | boats.json | All boat variations, including rafts. | +| #gm4:boss | boss.json | Bosses, namely the Ender Dragon and the Wither. | +| #gm4:chest_boats | chest_boats.json | All boat variations with a chest. | +| #gm4:hostile | hostile.json | Living entities that are hostile towards player by default. | +| #gm4:minecarts | minecarts.json | All minecart variations. | +| #gm4:neutral_hostile | neutral_hostile.json | Hostile living entities that may be, given the right conditions, neutral towards the player by default but turn hostile if provoked. | +| #gm4:neutral_passive | neutral_passive.json | Entities that are normally neutral, but turn hostile if provoked. | +| #gm4:neutral | neutral.json | Entities that may be neutral given the right conditions. | +| #gm4:non-living | non-living.json | Entities that are not considered living. | +| #gm4:passive | passive.json | Entities that are normally friendly and do not turn hostile, even if provoked. | + +## Environment Checks +The environment a data pack is installed into may affect its performance. +Servers may have command blocks disabled, or mods may change the way the game reacts to changes made by commands. +Not all users are aware of this, which can lead to rather frustrating debugging experiences. +To counteract this, modules may include environment checks which warn the user of the data pack if certain conditions are not met. + +Environment checks are included by specifying them by name (including namespace) inside a module's `beet.yaml`, e.g. +```yaml +id: gm4_double_doors +name: Double Doors +version: 1.2.X + +data_pack: + load: . + +require: + - bolt + +pipeline: + - gm4_double_doors.generate + - gm4.plugins.extend.module + +meta: + gm4: + versioning: + schedule_loops: [] + + # List of environment checks to include + echecks: [gm4:non_player_entity_has_score] + website: + description: Tired of clicking twice to open a double door? Annoyed by the fact that doors are only two blocks tall? This data pack automatically opens adjacent doors, making double doors fully functional! Additionally, bottom trapdoors of matching wood type placed above a door are opened alongside the door when it is opened by a player. + recommended: [] + notes: [] + modrinth: + project_id: Vx4zJ1Np + smithed: + pack_id: gm4_double_doors + video: null + wiki: https://wiki.gm4.co/wiki/Double_Doors + credits: + Creator: + - Bloo + Icon Design: + - venomousbirds +``` + +Multiple checks may be included, however, the order they will be executed in is arbitrary: +```yaml +echecks: [gm4:non_player_entity_has_score, gm4_double_doors:bloo_is_not_online, lib_forceload:command_blocks_enabled] +``` + +On reload, `base` obtains a list of environment checks requested by all installed modules and libraries. During `post_load` this list is then executed. +Announcements of the test results are only made if at least one test did not succeed and may not be instantaneous, as environment checks are allowed 30s of runtime. +If any check does not return after 30s it is marked as "timed-out" and will be announced as such. + +For debug purposes, you may inspect the contents of storage `gm4:log echecks` to see the results of the latest environment checks. This storage is only cleared before new checks are executed. + +### Base Environment Checks +`base` comes with some fundamental environment checks that can be referenced by the `gm4:` namespace. + +| Check Name | Description | +|--------------------------------|--------------------------------------------------------------------------------------------------------------------------| +| gm4:non_player_entity_has_score | Checks if non-player entities can be added to a scoreboard. Fails if a test marker's score can not be set and read back. | + +### Creating New Environment Checks +Modules may also introduce their own environment checks. Environment checks are defined as a single `.mcfunction` entry point, but may call other `.mcfunction` files, predicates, etc. from their entry point. +Caching of environment check results is provided automatically, you do not have to implement caching. + +When introducing an environment check, the corresponding module must add the environment check's entry point to the `#gm4:evaluate_echecks` function tag. +Upon completion of the test (failure or success), the environment check needs to add a `result` object to its entry in the `gm4:log echeck` storage. + +For example, the test with entry point located at `gm4:echeck/non_player_entity_has_score` may indicate a test failure as follows +```mcfunction +data modify storage gm4:log echecks[{echeck_id:"gm4:echeck/non_player_entity_has_score"}].result set value {passed:0,probable_cause:"This may be caused by the Paper/Spigot setting 'scoreboards.allow-non-player-entities-on-scoreboards=false'."} +``` +Or a test success as +```mcfunction +data modify storage gm4:log echecks[{echeck_id:"gm4:echeck/non_player_entity_has_score"}].result set value {passed:1,probable_cause:""} +``` + +The `result.probable_cause` is shown to the user in chat in case the test fails. + +For a textbook example of an environment check, inspect `gm4:non_player_entity_has_score` in `base`. diff --git a/gm4/plugins/versioning.py b/gm4/plugins/versioning.py index 89dd053fe2..bef52659f1 100644 --- a/gm4/plugins/versioning.py +++ b/gm4/plugins/versioning.py @@ -1,3 +1,5 @@ +from typing import List + from beet import Context, Function, FunctionTag, PluginOptions, configurable from beet.contrib.rename_files import rename_files from beet.contrib.find_replace import find_replace @@ -11,6 +13,7 @@ class VersionInjectionConfig(PluginOptions): advancements: list[str] = [] class VersioningConfig(PluginOptions, extra="ignore"): + echecks: list[str] = [] schedule_loops: list[str] = [] required: dict[str, str] = {} extra_version_injections: VersionInjectionConfig = Field(default=VersionInjectionConfig()) @@ -20,11 +23,14 @@ def modules(ctx: Context, opts: VersioningConfig): """Assembles version-functions for modules from dependency information: - load:{module_name}.json - {module_name}:load.mcfunction - - load:load.json""" + - {module_name}:echecks.mcfunction + - load:load.json + """ ctx.cache["currently_building"].json = {"name": ctx.project_name, "id": ctx.project_id, "added_libs": []} # cache module's project id for access within library pipelines dependencies = opts.required manifest = gm4.plugins.manifest.ManifestCacheModel.model_validate(ctx.cache["gm4_manifest"].json) - lines = ["execute ", ""] + dependency_check_command = "execute " + log_message_commands: List[str] = [] # {{module_name}}.json tag load_tag = dependency_load_tags(ctx, dependencies) @@ -47,33 +53,63 @@ def modules(ctx: Context, opts: VersioningConfig): dep_id = manifest.libraries.get(dep_id, NoneAttribute()).id # append to startup check - lines[0] += f"if score {dep_id} load.status matches {dep_ver.major} if score {dep_id}_minor load.status matches {dep_ver.minor}.. " + dependency_check_command += f"if score {dep_id} load.status matches {dep_ver.major} if score {dep_id}_minor load.status matches {dep_ver.minor}.. " # failure logs - lines.append(f"execute unless score {dep_id} load.status matches 1.. run data modify storage gm4:log queue append value {{type:\"missing\",module:\"{ctx.project_name}\",id:\"{ctx.project_id}\",require:\"{dep_name}\",require_id:\"{dep_id}\"}}") + log_message_commands.append(f"execute unless score {dep_id} load.status matches 1.. run data modify storage gm4:log queue append value {{type:\"missing\",module:\"{ctx.project_name}\",id:\"{ctx.project_id}\",require:\"{dep_name}\",require_id:\"{dep_id}\"}}") log_data = f"{{type:\"version_conflict\",module:\"{ctx.project_name}\",id:\"{ctx.project_id}\",require:\"{dep_name}\",require_id:\"{dep_id}\",require_ver:\"{dep_ver}\"}}" - lines.append(f"execute if score {dep_id} load.status matches 1.. unless score {dep_id} load.status matches {dep_ver.major} run data modify storage gm4:log queue append value {log_data}") - lines.append(f"execute if score {dep_id} load.status matches {dep_ver.major} unless score {dep_id}_minor load.status matches {dep_ver.minor}.. run data modify storage gm4:log queue append value {log_data}") - - # finalize startup check + log_message_commands.append(f"execute if score {dep_id} load.status matches 1.. unless score {dep_id} load.status matches {dep_ver.major} run data modify storage gm4:log queue append value {log_data}") + log_message_commands.append(f"execute if score {dep_id} load.status matches {dep_ver.major} unless score {dep_id}_minor load.status matches {dep_ver.minor}.. run data modify storage gm4:log queue append value {log_data}") + + # add environment check requests + echeck_requests: List[str] = [] + for namespaced_echeck in opts.echecks: + match namespaced_echeck.split(":"): + case [ + check + ]: # if no namespace is given, assume current project's namespace + namespace = ctx.project_id + case [namespace, check]: + pass + case _: + raise ValueError(f"{namespaced_echeck} is not a valid environment check name!") + echeck_requests.append(f"execute unless data storage gm4:log echecks[{{echeck_id:\"{namespace}:{check}\"}}] run data modify storage gm4:log echecks append value {{echeck_id:\"{namespace}:{check}\",required_by:[],result:{{passed:-1}}}}") + echeck_requests.append(f"data modify storage gm4:log echecks[{{echeck_id:\"{namespace}:{check}\"}}].required_by append value \"{ctx.project_id}\"") + + if 0 < len(echeck_requests): # append an empty line to envcheck command list if there is at least one envcheck + echeck_requests.append("") + + # parse module version module_ver = Version(ctx.project_version) - lines[1] = lines[0] + f"run scoreboard players set {ctx.project_id}_minor load.status {module_ver.minor}" - lines[0] += f"run scoreboard players set {ctx.project_id} load.status {module_ver.major}" # otherwise, log failed startup with -1 load.status - lines.append(f"execute unless score {ctx.project_id} load.status matches 1.. run scoreboard players set {ctx.project_id} load.status -1") + log_message_commands.append(f"execute unless score {ctx.project_id} load.status matches 1.. run scoreboard players set {ctx.project_id} load.status -1") - lines.append('') + log_message_commands.append('') # start module clocks - lines.append(f"execute if score {ctx.project_id} load.status matches {module_ver.major} run function {ctx.project_id}:init") + log_message_commands.append(f"execute if score {ctx.project_id} load.status matches {module_ver.major} run function {ctx.project_id}:init") # unschedule clocks for function in opts.schedule_loops: namespaced_function = f"{ctx.project_id}:{function}" if ":" not in function else function - lines.append(f"execute unless score {ctx.project_id} load.status matches {module_ver.major} run schedule clear {namespaced_function}") + log_message_commands.append(f"execute unless score {ctx.project_id} load.status matches {module_ver.major} run schedule clear {namespaced_function}") + + # populate function + ctx.data.functions[f"{ctx.project_id}:load"] = Function( + [ + dependency_check_command + + f"run scoreboard players set {ctx.project_id} load.status {module_ver.major}", + dependency_check_command + + f"run scoreboard players set {ctx.project_id}_minor load.status {module_ver.minor}", + "", + *echeck_requests, + *log_message_commands, + ] + ) - ctx.data.functions[f"{ctx.project_id}:load"] = Function(lines) + # echecks.mcfunction if this data pack defines environment checks + index_echecks(ctx, module_ver) # load.json tag ctx.data.function_tags["load:load"] = FunctionTag({ @@ -83,18 +119,20 @@ def modules(ctx: Context, opts: VersioningConfig): }) # inject module load success checks (load.status 1..) into technical and display advancements - # advancements get score checks injected into every criteria + # advancements get score checks injected into every criteria versioned_advancements(ctx, Version("X.X.X"), [a for a in ctx.data.advancements.keys() if not a=="gm4:root"], False) @configurable("gm4.versioning", validator=VersioningConfig) def libraries(ctx: Context, opts: VersioningConfig): """Assembles version-functions for libraries from dependency information: - - {lib_name}:enumerate.mcfunction - - {lib_name}:resolve_load.mcfunction - - load:{lib_name}.json - - load:{lib_name}/enumerate.json - - load:{lib_name}/resolve_load.json - - load:{lib_name}/dependencies.json""" + - {lib_name}:enumerate.mcfunction + - {lib_name}:resolve_load.mcfunction + - {lib_name}:echecks.mcfunction + - load:{lib_name}.json + - load:{lib_name}/enumerate.json + - load:{lib_name}/resolve_load.json + - load:{lib_name}/dependencies.json + """ dependencies = opts.required manifest = gm4.plugins.manifest.ManifestCacheModel.model_validate(ctx.cache["gm4_manifest"].json) lib_ver = Version(ctx.project_version) @@ -131,6 +169,9 @@ def libraries(ctx: Context, opts: VersioningConfig): ctx.data.functions[f"{ctx.project_id}:resolve_load"] = Function(lines) + # echecks.mcfunction if this data pack defines environment checks + index_echecks(ctx, lib_ver) + # load/tags {{ lib name }}.json ctx.data.function_tags[f"load:{ctx.project_id}"] = FunctionTag({ "values": [ @@ -165,16 +206,9 @@ def libraries(ctx: Context, opts: VersioningConfig): # additional version injections # NOTE functions get version checks replaced onto `load.status` checks - ctx.require(find_replace(data_pack={"match": { - "functions": [f if ':' in f else f"{ctx.project_id}:{f}" for f in opts.extra_version_injections.functions]} - }, - substitute={ - "find": f"{ctx.project_id} load\\.status matches \\d(?: if score {ctx.project_id}_minor load\\.status matches \\d)?", - "replace": f"{ctx.project_id} load.status matches {lib_ver.major} if score {ctx.project_id}_minor load.status matches {lib_ver.minor}" - } - )) + versioned_functions(ctx, lib_ver, opts.extra_version_injections.functions) - # stamp version number and module bring packaged into into load.mcfunction + # stamp version number and module bring packaged into into load.mcfunction handle = ctx.data.functions[f"{ctx.project_id}:load"] handle.append([ "\n", @@ -209,10 +243,16 @@ def base(ctx: Context, opts: VersioningConfig): lines = f"execute if score gm4 load.status matches {ver.major} if score gm4_minor load.status matches {ver.minor} run function gm4:post_load" ctx.data.functions[f"gm4:resolve_post_load"] = Function(lines) + # index env checks + index_echecks(ctx, ver) + versioned_advancements(ctx, ver, opts.extra_version_injections.advancements, strict=True) #type:ignore + versioned_functions(ctx, ver, opts.extra_version_injections.functions) + versioned_namespace(ctx, ver) + def versioned_namespace(ctx: Context, version: Version): """Puts the project version into the namespace, and renames all references to match Used for libraries that may have multiple versions exist in a world at once without @@ -225,7 +265,7 @@ def versioned_namespace(ctx: Context, version: Version): "replace": f"{versioned_namespace}:\\1" })) ctx.require(find_replace(data_pack={"match": "*"}, substitute={ - "find": f"(? index_echecks. Injects active-version checks into environment check entry points.",] + for entry_point in ctx.data.function_tags['gm4:evaluate_echecks'].data["values"]: + + # parse function tag entry + # entry point may be a dict instead of a string (e.g. {"required":false, "id":<...>}) + # if that is the case, find the id field and extract the string-form namespace resource locator + if isinstance(entry_point, dict): + if ("id" not in entry_point) or (not isinstance(entry_point["id"], str)): + raise ValueError(f"Failed to parse echeck entry point from 'gm4:evaluate_echecks' entry '{entry_point}'.") + entry_point = entry_point["id"] + + # strings should be a namespaced resource locator + if isinstance(entry_point, str): + if ":" not in entry_point: + raise ValueError(f"Failed to parse echeck entry point from 'gm4:evaluate_echecks' entry '{entry_point}'. Missing namespace!") + namespace, path = entry_point.split(":", maxsplit=1) + + # unknown type + else: + raise ValueError(f"Failed to parse echeck entry point from from 'gm4:evaluate_echecks' entry '{entry_point}' (type '{type(entry_point)}').") + + # append conditional environment check entry point + # only run check if: + # - the active version of the library/base matches the version this check was written in (in case this is a versioned library or base) + # - the environment check has been requested by a module/library/base + # - the environment check has not yet been evaluated this reload (in-built caching) + lines.append( + f"""execute if score {namespace} load.status matches {ver.major} if score {namespace}_minor load.status matches {ver.minor} \\ + if data storage gm4:log echecks.[{{echeck_id:"{namespace}:{path}"}}] \\ + if data storage gm4:log echecks.[{{echeck_id:"{namespace}:{path}",result:{{passed:-1}}}}] \\ + run function {namespace}:{path}""" + ) + ctx.data.functions[f"{ctx.project_id}:echecks"] = Function(lines) + + # point function tag to echecks.mcfunction + ctx.data.function_tags["gm4:evaluate_echecks"] = FunctionTag( + {"values": [f"{ctx.project_id}:echecks"]} + ) + + def warn_on_future_version(ctx: Context, dep_id: str, ver: Version): - """Issues a console warning if the dependancy version a module requires is greater than the current version of that dependancy""" + """Issues a console warning if the dependency version a module requires is greater than the current version of that dependency""" if dep_id == "gm4": return # the base version is not in the manifest, tis a special case if "lib" in dep_id: diff --git a/gm4/skin_cache.json b/gm4/skin_cache.json index e0ff0678cf..c2d7fcb211 100644 --- a/gm4/skin_cache.json +++ b/gm4/skin_cache.json @@ -1093,7 +1093,6 @@ "gm4_potion_liquids:liquids/harming", "gm4_potion_liquids:liquids/healing", "gm4_potion_liquids:liquids/leaping", - "gm4_potion_liquids:liquids/harming", "gm4_potion_liquids:liquids/poison", "gm4_potion_liquids:liquids/regeneration", "gm4_potion_liquids:liquids/strength", @@ -1104,15 +1103,6 @@ ], "gm4_auto_crafting": [ "gm4_custom_crafters:custom_crafter" - ], - "gm4_zauber_liquids": [ - "gm4_potion_liquids:liquids/harming", - "gm4_potion_liquids:liquids/healing", - "gm4_potion_liquids:liquids/leaping", - "gm4_potion_liquids:liquids/poison", - "gm4_potion_liquids:liquids/regeneration", - "gm4_potion_liquids:liquids/strength", - "gm4_potion_liquids:liquids/swiftness" ] } } diff --git a/gm4_animi_shamir/beet.yaml b/gm4_animi_shamir/beet.yaml index 5265aa0b02..47409fc1b6 100644 --- a/gm4_animi_shamir/beet.yaml +++ b/gm4_animi_shamir/beet.yaml @@ -20,6 +20,7 @@ meta: required: gm4_metallurgy: 1.5.0 lib_player_death: 1.3.0 + echecks: [gm4:echeck/non_player_entity_has_score] schedule_loops: [main] model_data: - reference: shamir/animi diff --git a/gm4_double_doors/beet.yaml b/gm4_double_doors/beet.yaml index d0162212a2..b4842278a2 100644 --- a/gm4_double_doors/beet.yaml +++ b/gm4_double_doors/beet.yaml @@ -16,6 +16,7 @@ meta: gm4: versioning: schedule_loops: [] + echecks: [gm4:non_player_entity_has_score] website: description: Tired of clicking twice to open a double door? Annoyed by the fact that doors are only two blocks tall? This data pack automatically opens adjacent doors, making double doors fully functional! Additionally, bottom trapdoors of matching wood type placed above a door are opened alongside the door when it is opened by a player. recommended: [] diff --git a/gm4_monsters_unbound/beet.yaml b/gm4_monsters_unbound/beet.yaml index 2c068ad40c..1383cf69d5 100644 --- a/gm4_monsters_unbound/beet.yaml +++ b/gm4_monsters_unbound/beet.yaml @@ -23,6 +23,7 @@ meta: - tick - main - slow_clock + echecks: [gm4:echeck/non_player_entity_on_team] website: description: Mobs gain special effects based on their biome. recommended: