diff --git a/gm4_timelines/README.md b/gm4_timelines/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gm4_timelines/beet.yaml b/gm4_timelines/beet.yaml new file mode 100644 index 0000000000..56cd9a82aa --- /dev/null +++ b/gm4_timelines/beet.yaml @@ -0,0 +1,25 @@ +id: gm4_timelines +name: Timelines +version: 1.0.X + +data_pack: + load: . + +require: + - gm4_timelines.plugins.generate + +pipeline: + - gm4.plugins.extend.module + +meta: + gm4: + versioning: + schedule_loops: [main] + website: + description: Timelines! + video: null + wiki: https://wiki.gm4.co/wiki/Timelines + credits: + Creator: + - Djones + - Kyrius diff --git a/gm4_timelines/data/gm4_timelines/function/init.mcfunction b/gm4_timelines/data/gm4_timelines/function/init.mcfunction new file mode 100644 index 0000000000..b1764f99f5 --- /dev/null +++ b/gm4_timelines/data/gm4_timelines/function/init.mcfunction @@ -0,0 +1,21 @@ +execute unless score timelines gm4_modules matches 1 run data modify storage gm4:log queue append value {type:"install",module:"Timelines"} +execute unless score timelines gm4_earliest_version < timelines gm4_modules run scoreboard players operation timelines gm4_earliest_version = timelines gm4_modules +scoreboard players set timelines gm4_modules 1 + +scoreboard objectives add gm4_timelines.data dummy + +scoreboard players set #8 gm4_timelines.data 8 +scoreboard players set #24000 gm4_timelines.data 24000 + +gamerule advance_weather false + +function gm4_timelines:register/days +execute store result score #day.offset gm4_timelines.data run data get storage gm4_timelines:data day_data.offset +execute store result score #day.duration gm4_timelines.data run data get storage gm4_timelines:data day_data.duration + +# TODO: only run this when needed, it resets everything to day 0 +function gm4_timelines:reset + +schedule function gm4_timelines:main 1t + +#$moduleUpdateList diff --git a/gm4_timelines/data/gm4_timelines/function/main.mcfunction b/gm4_timelines/data/gm4_timelines/function/main.mcfunction new file mode 100644 index 0000000000..8d6e014ffe --- /dev/null +++ b/gm4_timelines/data/gm4_timelines/function/main.mcfunction @@ -0,0 +1,14 @@ +schedule function gm4_timelines:main 1t + +execute store result score $day.real gm4_timelines.data run time query day +execute store result score $daytime.real gm4_timelines.data run time query daytime + +scoreboard players operation $day.current gm4_timelines.data = $day.real gm4_timelines.data +scoreboard players operation $day.current gm4_timelines.data *= #24000 gm4_timelines.data +scoreboard players operation $day.current gm4_timelines.data += $daytime.real gm4_timelines.data +scoreboard players operation $day.current gm4_timelines.data -= #day.offset gm4_timelines.data +scoreboard players operation $daytime.current gm4_timelines.data = $day.current gm4_timelines.data +scoreboard players operation $day.current gm4_timelines.data /= #day.duration gm4_timelines.data +scoreboard players operation $daytime.current gm4_timelines.data %= #day.duration gm4_timelines.data + +execute unless score $day.active gm4_timelines.data = $day.current gm4_timelines.data run function gm4_timelines:pick_day/run diff --git a/gm4_timelines/data/gm4_timelines/function/pick_day/build_dayline/eval_pick_weight.mcfunction b/gm4_timelines/data/gm4_timelines/function/pick_day/build_dayline/eval_pick_weight.mcfunction new file mode 100644 index 0000000000..b5d8b8a5de --- /dev/null +++ b/gm4_timelines/data/gm4_timelines/function/pick_day/build_dayline/eval_pick_weight.mcfunction @@ -0,0 +1,2 @@ + +$execute store result score $picked_weight gm4_timelines.data run random value 0..$(max) gm4_timelines:pick_day diff --git a/gm4_timelines/data/gm4_timelines/function/pick_day/build_dayline/eval_possible_days.mcfunction b/gm4_timelines/data/gm4_timelines/function/pick_day/build_dayline/eval_possible_days.mcfunction new file mode 100644 index 0000000000..03f672e0c9 --- /dev/null +++ b/gm4_timelines/data/gm4_timelines/function/pick_day/build_dayline/eval_possible_days.mcfunction @@ -0,0 +1,2 @@ + +$data modify storage gm4_timelines:temp possible_days set from storage gm4_timelines:data day_data.registry.$(moon_phase).$(in_type) diff --git a/gm4_timelines/data/gm4_timelines/function/pick_day/build_dayline/loop_count_weights.mcfunction b/gm4_timelines/data/gm4_timelines/function/pick_day/build_dayline/loop_count_weights.mcfunction new file mode 100644 index 0000000000..ad95723666 --- /dev/null +++ b/gm4_timelines/data/gm4_timelines/function/pick_day/build_dayline/loop_count_weights.mcfunction @@ -0,0 +1,6 @@ + +execute store result score $this_weight gm4_timelines.data run data get storage gm4_timelines:temp count_weight[0].weight +scoreboard players operation $total_weight gm4_timelines.data += $this_weight gm4_timelines.data + +data remove storage gm4_timelines:temp count_weight[0] +execute if data storage gm4_timelines:temp count_weight[0] run function gm4_timelines:pick_day/build_dayline/loop_count_weights diff --git a/gm4_timelines/data/gm4_timelines/function/pick_day/build_dayline/loop_run.mcfunction b/gm4_timelines/data/gm4_timelines/function/pick_day/build_dayline/loop_run.mcfunction new file mode 100644 index 0000000000..a9dd2b53cd --- /dev/null +++ b/gm4_timelines/data/gm4_timelines/function/pick_day/build_dayline/loop_run.mcfunction @@ -0,0 +1,38 @@ + +# check moon cycle +scoreboard players operation $moon_cycle gm4_timelines.data = $last_build_day gm4_timelines.data +scoreboard players operation $moon_cycle gm4_timelines.data %= #8 gm4_timelines.data +execute if score $moon_cycle gm4_timelines.data matches 0 run data modify storage gm4_timelines:temp looking_for set value {moon_phase:"full_moon",in_type:"clear"} +execute if score $moon_cycle gm4_timelines.data matches 1 run data modify storage gm4_timelines:temp looking_for set value {moon_phase:"waning_gibbous",in_type:"clear"} +execute if score $moon_cycle gm4_timelines.data matches 2 run data modify storage gm4_timelines:temp looking_for set value {moon_phase:"third_quarter",in_type:"clear"} +execute if score $moon_cycle gm4_timelines.data matches 3 run data modify storage gm4_timelines:temp looking_for set value {moon_phase:"waning_crescent",in_type:"clear"} +execute if score $moon_cycle gm4_timelines.data matches 4 run data modify storage gm4_timelines:temp looking_for set value {moon_phase:"new_moon",in_type:"clear"} +execute if score $moon_cycle gm4_timelines.data matches 5 run data modify storage gm4_timelines:temp looking_for set value {moon_phase:"waxing_crescent",in_type:"clear"} +execute if score $moon_cycle gm4_timelines.data matches 6 run data modify storage gm4_timelines:temp looking_for set value {moon_phase:"first_quarter",in_type:"clear"} +execute if score $moon_cycle gm4_timelines.data matches 7 run data modify storage gm4_timelines:temp looking_for set value {moon_phase:"waxing_gibbous",in_type:"clear"} +# check required in_type +data modify storage gm4_timelines:temp looking_for.in_type set from storage gm4_timelines:data active_day.out_type +# get possible days +function gm4_timelines:pick_day/build_dayline/eval_possible_days with storage gm4_timelines:temp looking_for +data remove storage gm4_timelines:temp looking_for + +# get the total weight of all entries +scoreboard players set $total_weight gm4_timelines.data 0 +data modify storage gm4_timelines:temp count_weight set from storage gm4_timelines:temp possible_days +function gm4_timelines:pick_day/build_dayline/loop_count_weights +data remove storage gm4_timelines:temp count_weight + +# get a random weight between 0 and total weight +execute store result storage gm4_timelines:temp rng.max int 1 run scoreboard players get $total_weight gm4_timelines.data +function gm4_timelines:pick_day/build_dayline/eval_pick_weight with storage gm4_timelines:temp rng +data remove storage gm4_timelines:temp rng + +# loop to get the picked day and add it to they dayline +execute if score $picked_weight gm4_timelines.data matches 1.. run function gm4_timelines:pick_day/build_dayline/loop_to_picked_day +data modify storage gm4_timelines:data dayline append from storage gm4_timelines:temp possible_days[0] +data remove storage gm4_timelines:temp possible_days + +# continue doing this until we get to the desired day +scoreboard players add $last_build_day gm4_timelines.data 1 +scoreboard players remove $days_forward gm4_timelines.data 1 +execute if score $days_forward gm4_timelines.data matches 1.. run function gm4_timelines:pick_day/build_dayline/loop_run diff --git a/gm4_timelines/data/gm4_timelines/function/pick_day/build_dayline/loop_to_picked_day.mcfunction b/gm4_timelines/data/gm4_timelines/function/pick_day/build_dayline/loop_to_picked_day.mcfunction new file mode 100644 index 0000000000..d4aaac677f --- /dev/null +++ b/gm4_timelines/data/gm4_timelines/function/pick_day/build_dayline/loop_to_picked_day.mcfunction @@ -0,0 +1,8 @@ + +execute store result score $this_weight gm4_timelines.data run data get storage gm4_timelines:temp possible_days[0].weight +scoreboard players operation $picked_weight gm4_timelines.data -= $this_weight gm4_timelines.data + +execute if score $picked_weight gm4_timelines.data matches ..0 run return 1 + +data remove storage gm4_timelines:temp possible_days[0] +execute if data storage gm4_timelines:temp possible_days[0] run function gm4_timelines:pick_day/build_dayline/loop_to_picked_day diff --git a/gm4_timelines/data/gm4_timelines/function/pick_day/build_dayline/no_valid_day.mcfunction b/gm4_timelines/data/gm4_timelines/function/pick_day/build_dayline/no_valid_day.mcfunction new file mode 100644 index 0000000000..31b020375e --- /dev/null +++ b/gm4_timelines/data/gm4_timelines/function/pick_day/build_dayline/no_valid_day.mcfunction @@ -0,0 +1,3 @@ + +# data modify storage gm4_timelines:data active_day set from storage gm4_timelines:data day_registry[0] +tellraw @a [{text:"[GM4 Timelines] ",color:"#4AA0C7"},{text:"No valid day found!",color:"red"}] diff --git a/gm4_timelines/data/gm4_timelines/function/pick_day/build_dayline/run.mcfunction b/gm4_timelines/data/gm4_timelines/function/pick_day/build_dayline/run.mcfunction new file mode 100644 index 0000000000..2fb7622c67 --- /dev/null +++ b/gm4_timelines/data/gm4_timelines/function/pick_day/build_dayline/run.mcfunction @@ -0,0 +1,21 @@ + +# first get the last day in the build timeline +execute store result score $last_build_day gm4_timelines.data run data get storage gm4_timelines:data dayline + +# check how many days should be build +scoreboard players operation $days_forward gm4_timelines.data = $index_change gm4_timelines.data + +# build at most 32 days +execute if score $days_forward gm4_timelines.data matches 32.. run tellraw @a [{text:"[GM4 Timelines] ",color:"#4AA0C7"},{text:"Could only move 32 days into the future",color:"red"}] +execute if score $days_forward gm4_timelines.data matches 32.. run scoreboard players set $index_change gm4_timelines.data 32 +execute if score $days_forward gm4_timelines.data matches 32.. run scoreboard players set $days_forward gm4_timelines.data 32 + +# get data from last build day +data modify storage gm4_timelines:data active_day set from storage gm4_timelines:data dayline[-1] + +# build the amount of needed days +function gm4_timelines:pick_day/build_dayline/loop_run + +# get the last day +data modify storage gm4_timelines:data active_day set from storage gm4_timelines:data dayline[-1] +scoreboard players operation $day.current gm4_timelines.data = $last_build_day gm4_timelines.data diff --git a/gm4_timelines/data/gm4_timelines/function/pick_day/eval_day_from_dayline.mcfunction b/gm4_timelines/data/gm4_timelines/function/pick_day/eval_day_from_dayline.mcfunction new file mode 100644 index 0000000000..41c1845ebb --- /dev/null +++ b/gm4_timelines/data/gm4_timelines/function/pick_day/eval_day_from_dayline.mcfunction @@ -0,0 +1,2 @@ + +$data modify storage gm4_timelines:data active_day set from storage gm4_timelines:data dayline[$(index)] diff --git a/gm4_timelines/data/gm4_timelines/function/pick_day/eval_time.mcfunction b/gm4_timelines/data/gm4_timelines/function/pick_day/eval_time.mcfunction new file mode 100644 index 0000000000..0324526e93 --- /dev/null +++ b/gm4_timelines/data/gm4_timelines/function/pick_day/eval_time.mcfunction @@ -0,0 +1,2 @@ + +$time set $(time) diff --git a/gm4_timelines/data/gm4_timelines/function/pick_day/run.mcfunction b/gm4_timelines/data/gm4_timelines/function/pick_day/run.mcfunction new file mode 100644 index 0000000000..6a3422198c --- /dev/null +++ b/gm4_timelines/data/gm4_timelines/function/pick_day/run.mcfunction @@ -0,0 +1,33 @@ + + +# check how the days have changed +scoreboard players operation $index_change gm4_timelines.data = $day.current gm4_timelines.data +scoreboard players operation $index_change gm4_timelines.data -= $day.active gm4_timelines.data + +scoreboard players operation $day_index gm4_timelines.data = $day.game gm4_timelines.data +scoreboard players operation $day_index gm4_timelines.data += $index_change gm4_timelines.data + +# check what day index should be used +execute store result storage gm4_timelines:temp day.index int 1 run scoreboard players get $day_index gm4_timelines.data +data remove storage gm4_timelines:data active_day +function gm4_timelines:pick_day/eval_day_from_dayline with storage gm4_timelines:temp day + +# if that index does not yet exist try to extend the dayline to it +execute unless data storage gm4_timelines:data active_day run function gm4_timelines:pick_day/build_dayline/run + +# move time as needed +execute store result score $set_time gm4_timelines.data run data get storage gm4_timelines:data active_day.start_time +scoreboard players operation $add_time gm4_timelines.data = $daytime.real gm4_timelines.data +scoreboard players operation $add_time gm4_timelines.data -= #day.offset gm4_timelines.data +scoreboard players operation $add_time gm4_timelines.data %= #day.duration gm4_timelines.data +scoreboard players operation $set_time gm4_timelines.data += $add_time gm4_timelines.data +execute store result storage gm4_timelines:temp change.time int 1 run scoreboard players get $set_time gm4_timelines.data +function gm4_timelines:pick_day/eval_time with storage gm4_timelines:temp change +data remove storage gm4_timelines:temp change + +# mark this as the active day +execute store result score $day.active gm4_timelines.data run data get storage gm4_timelines:data active_day.start_time +scoreboard players operation $day.active gm4_timelines.data -= #day.offset gm4_timelines.data +scoreboard players operation $day.active gm4_timelines.data /= #day.duration gm4_timelines.data +# move the game day so we remember what day we're actually at +scoreboard players operation $day.game gm4_timelines.data += $index_change gm4_timelines.data diff --git a/gm4_timelines/data/gm4_timelines/function/reset.mcfunction b/gm4_timelines/data/gm4_timelines/function/reset.mcfunction new file mode 100644 index 0000000000..d4ffab57f3 --- /dev/null +++ b/gm4_timelines/data/gm4_timelines/function/reset.mcfunction @@ -0,0 +1,6 @@ + +# TODO: how does this impact local difficulty? +time set 1 +data modify storage gm4_timelines:data dayline set value [] +random reset gm4_timelines:pick_day 0 true true +scoreboard players set $day.active gm4_timelines.data -1 diff --git a/gm4_timelines/data/minecraft/timeline/early_game.json b/gm4_timelines/data/minecraft/timeline/early_game.json new file mode 100644 index 0000000000..a661a77910 --- /dev/null +++ b/gm4_timelines/data/minecraft/timeline/early_game.json @@ -0,0 +1,65 @@ +{ + "tracks": { + "minecraft:gameplay/can_pillager_patrol_spawn": { + "keyframes": [ + { + "ticks": 0, + "value": false + }, + { + "ticks": 120000, + "value": true + } + ], + "modifier": "and" + }, + "minecraft:gameplay/bed_rule": { + "modifier": "override", + "keyframes": [ + { + "ticks": 0, + "value": { + "can_sleep": "always", + "can_set_spawn": "always" + } + } + ] + }, + "minecraft:visual/cloud_fog_end_distance": { + "ease": "linear", + "keyframes": [ + { + "ticks": 0, + "value": 2048 + } + ] + }, + "minecraft:visual/fog_end_distance": { + "ease": "in_out_sine", + "keyframes": [ + { + "ticks": 0, + "value": 1024 + } + ] + }, + "minecraft:visual/fog_start_distance": { + "ease": "in_out_sine", + "keyframes": [ + { + "ticks": 0, + "value": 0 + } + ] + }, + "minecraft:visual/sky_fog_end_distance": { + "ease": "in_out_sine", + "keyframes": [ + { + "ticks": 0, + "value": 512 + } + ] + } + } +} diff --git a/gm4_timelines/data/minecraft/timeline/moon.json b/gm4_timelines/data/minecraft/timeline/moon.json new file mode 100644 index 0000000000..fdae15af3b --- /dev/null +++ b/gm4_timelines/data/minecraft/timeline/moon.json @@ -0,0 +1,3 @@ +{ + "tracks": {} +} diff --git a/gm4_timelines/data/minecraft/timeline/villager_schedule.json b/gm4_timelines/data/minecraft/timeline/villager_schedule.json new file mode 100644 index 0000000000..fdae15af3b --- /dev/null +++ b/gm4_timelines/data/minecraft/timeline/villager_schedule.json @@ -0,0 +1,3 @@ +{ + "tracks": {} +} diff --git a/gm4_timelines/plugins/generate.py b/gm4_timelines/plugins/generate.py new file mode 100644 index 0000000000..3a052f089a --- /dev/null +++ b/gm4_timelines/plugins/generate.py @@ -0,0 +1,327 @@ +from beet import Context, JsonFile, Function, NamespaceFileScope # pyright: ignore[reportMissingImports] +from pydantic import BaseModel # pyright: ignore[reportMissingImports] +from typing import Tuple, Dict, Any, List, Optional, ClassVar +from pathlib import Path +import json +import copy + +TICK_OFFSET: int = 6000 +DAY_DURATION_TICKS: int = 24000 +FOLDER_PATH: Path = Path("gm4_timelines/raw_data") +SLIME_VALUES: Dict[str, float] = { + "full_moon": 0.5, + "waning_gibbous": 0.375, + "third_quarter": 0.25, + "waning_crescent": 0.125, + "new_moon": 0, + "waxing_crescent": 0.125, + "first_quarter": 0.25, + "waxing_gibbous": 0.375, +} + +# ------------------ +# - PYDANTIC STUFF - +# ------------------ + +class Keyframe(BaseModel): + ticks: int + value: Any + +class Track(BaseModel): + modifier: Optional[str] = None + ease: Optional[Any] = None + keyframes: List[Keyframe] = [] + +class TimelineData(BaseModel): + period_ticks: int + tracks: Dict[str, Track] = {} + +class ScheduleEntry(BaseModel): + time: int + effects: Dict[str, Any] = {} + functions: List[str] = [] + +class DayData(BaseModel): + settings: Dict[str, Any] = {} + schedule: List[ScheduleEntry] = [] + +class Timeline(JsonFile): + scope: ClassVar[NamespaceFileScope] = ("timeline",) + extension: ClassVar[str] = ".json" + + +# ------------- +# - FUNCTIONS - +# ------------- + +def beet_default(ctx: Context) -> None: + # We register Timeline to Beet here since it doesn't support it yet, when it does this can be removed + ctx.data.extend_namespace += [Timeline] + + default_file = FOLDER_PATH / "default.json" + + with default_file.open("r", encoding="utf-8") as f: + data_dict = json.load(f) + data = TimelineData(**data_dict) + + # factor and offset the ticks so it matches the new daytime + # data = factor_ticks(data) + + data = register_days(ctx, data) + + data = remove_null_entries(data) + + ctx.data["minecraft:day"] = Timeline(data.model_dump()) + + + +def remove_null_entries(data: TimelineData) -> TimelineData: + """ + Remove 'modifier' and/or 'ease' keys from each track + if their value is null. + + Args: + data: Timeline data whose entries will be removed. + + Returns: + modified TimelineData instance. + """ + result = copy.deepcopy(data) + + for _, track_data in result.tracks.items(): + if getattr(track_data, "modifier", None) is None: + delattr(track_data, "modifier") + if getattr(track_data, "ease", None) is None: + delattr(track_data, "ease") + + return result + + + +def register_days(ctx: Context, data: TimelineData) -> TimelineData: + """ + Load day definition files from either dev or days folder. Build combined + timeline by concatenating the tracks of each day's timeline + + Args: + data: Base timeline used as the default state for each generated day. + + Returns: + TimelineData instance representing the concatenated day timeline. + """ + dev_folder = FOLDER_PATH / "dev" + dev_files = list(dev_folder.glob("*.json")) + + if dev_files: + return process_day_files(ctx, dev_folder, data) + else: + days_folder = FOLDER_PATH / "days" + return process_day_files(ctx, days_folder, data) + + + +def process_day_files( + ctx: Context, + folder_path: Path, + default_data: TimelineData + ) -> TimelineData: + """ + Load each day definition from provided folder and build a timeline for them. + Days are added once per supported moon phase. Also collects metadata needed to + register the day into storage for the datapack. + + Args: + folder_path: Directory containing day definition JSON files. + default_data: Default timeline used as a base for each day. + + Returns: + TimelineData instance representing the concatenated day timeline. + """ + function_data: dict[str, Any] = {} + + # start with day 0 (first 600 ticks) + full_timeline = build_day_zero(default_data) + + # loop over day definitions + for file_path in folder_path.glob("*.json"): + with file_path.open("r", encoding="utf-8") as f: + day_data_dict = json.load(f) + day_data = DayData(**day_data_dict) + + # loop over supported moon phases + moon_phases = day_data.settings.get("moon_phase", []) + for moon_phase in moon_phases: + + day_build, functions = register_day(default_data, day_data, moon_phase) + + full_timeline = append_to_timeline(full_timeline, day_build) + + function_moon_phase = function_data.setdefault(moon_phase, {}) + function_entries = function_moon_phase.setdefault(day_data.settings["in_type"], []) + function_entries.append({ + "moon_phase": moon_phase, + "in_type": day_data.settings["in_type"], + "out_type": day_data.settings["out_type"], + "weight": day_data.settings["weight"], + "start_time": full_timeline.period_ticks, + "functions": functions + }) + + full_timeline.period_ticks += day_build.period_ticks + + # create function + func: dict[str, Any] = {"duration":DAY_DURATION_TICKS,"offset":TICK_OFFSET,"registry":function_data} + ctx.data["gm4_timelines:register/days"] = Function([ + "# register days", + "# generated from generate.py", + "", + f'data modify storage gm4_timelines:data day_data set value {func}', + ]) + + return full_timeline + + + +def build_day_zero(default_data: TimelineData) -> TimelineData: + """ + Build the first ticks from daytime = 0 to daytime = TICK_OFFSET, this uses the + default day + + Args: + default_data: Default timeline used as a base for the first few ticks. + + Returns: + TimelineData instance representing the day timeline. + """ + day_zero = copy.deepcopy(default_data) + start_tick = DAY_DURATION_TICKS - TICK_OFFSET + + # get only the last TICK_OFFSET ticks from the default timeline + for track in day_zero.tracks.values(): + new_keyframes: list[Keyframe] = [] + + for kf in track.keyframes: + if kf.ticks >= start_tick: + new_keyframes.append( + Keyframe( + ticks=kf.ticks - start_tick, + value=copy.deepcopy(kf.value), + ) + ) + track.keyframes = new_keyframes + + # add this start to the full_timeline + full_timeline = append_to_timeline(TimelineData(period_ticks=0, tracks={}),day_zero) + full_timeline.period_ticks += TICK_OFFSET + + return full_timeline + + + +def register_day( + default_data: TimelineData, + day_data: DayData, + moon_phase: str + ) -> Tuple[TimelineData, List[Dict[str, Any]]]: + """ + Build the day data for a day definition, add moon phase and slime spawn chance + tracks and get scheduled function calls. Any non-modified tracks are taken from default_data + + Args: + default_data: Base timeline providing default tracks and values. + day_data: Parsed day configuration and schedule. + moon_phase: Moon phase identifier for this day variant. + + Returns: + A tuple containing the generated day timeline and a list of function + calls to be triggered at specific ticks. + """ + result = TimelineData(period_ticks=default_data.period_ticks, tracks={}) + seen_tracks: set[str] = set() + functions: List[Dict[str, Any]] = [] + + # create the moon phase track + track_name = "minecraft:visual/moon_phase" + result.tracks[track_name] = Track( + keyframes=[Keyframe(ticks=0, value=moon_phase)] + ) + seen_tracks.add(track_name) + + # create the surface slime spawn chance track + slime_value = SLIME_VALUES.get(moon_phase, 0.0) + track_name = "minecraft:gameplay/surface_slime_spawn_chance" + result.tracks[track_name] = Track( + keyframes=[Keyframe(ticks=0, value=slime_value)], + modifier="maximum", + ease="constant" + ) + seen_tracks.add(track_name) + + # any tracks in the schedule + for entry in day_data.schedule: + ticks = entry.time + effects = entry.effects + + # register function calls + if entry.functions: + functions.append({"tick":ticks,"functions":entry.functions}) + + # transform effects into timeline tracks + for effect_name, value in effects.items(): + track_name = f"minecraft:{effect_name}" + seen_tracks.add(track_name) + + track = result.tracks.get(track_name) + if not track: + default_track = default_data.tracks.get(track_name) + track = Track( + keyframes=[], + modifier=default_track.modifier if default_track else None, + ease=default_track.ease if default_track else None + ) + result.tracks[track_name] = track + + track.keyframes.append(Keyframe(ticks=ticks, value=value)) + + # copy over untouched default tracks + for track_name, track_data in default_data.tracks.items(): + if track_name not in seen_tracks: + result.tracks[track_name] = copy.deepcopy(track_data) + + return result, functions + + +def append_to_timeline(full_timeline: TimelineData, new_data: TimelineData) -> TimelineData: + """ + Append the next day timeline into the full timeline by offsetting its ticks, they are + shifted by the current period of the full timeline and then merged into the matching + tracks. + + Args: + full_timeline: The timeline being appended to. + new_data: The timeline segment to append. + + Returns: + The build timeline with the new timeline data merged in. + """ + offset = full_timeline.period_ticks + + for track_name, src_track in new_data.tracks.items(): + + # find matching track, or create if neccesary + dst_track = full_timeline.tracks.get(track_name) + if not dst_track: + dst_track = Track( + keyframes=[], + modifier=src_track.modifier, + ease=copy.deepcopy(src_track.ease) + ) + full_timeline.tracks[track_name] = dst_track + + # append the keyframes, adding the offset to each ticks value + for kf in src_track.keyframes: + dst_track.keyframes.append( + Keyframe(ticks=kf.ticks + offset, value=copy.deepcopy(kf.value)) + ) + + return full_timeline diff --git a/gm4_timelines/raw_data/days/converted.json b/gm4_timelines/raw_data/days/converted.json new file mode 100644 index 0000000000..ea33622cd6 --- /dev/null +++ b/gm4_timelines/raw_data/days/converted.json @@ -0,0 +1,228 @@ +{ + "settings": { + "weight": 500, + "moon_phase": ["new_moon"], + "in_type": "clear", + "out_type": "foggy" + }, + "schedule": [ + { + "time": 0, + "effects": { + "visual/sun_angle": 0, + "visual/moon_angle": 180, + "visual/star_angle": 0, + "visual/star_brightness": 0, + "visual/sky_light_factor": 1, + "visual/sky_light_color": "#fffedd", + "visual/sky_color": "#3888f0", + "visual/sunrise_sunset_color": "#00ffaa88", + "visual/fog_color": "#cbfdf2", + "visual/fog_end_distance": 500, + "visual/fog_start_distance": 50, + "visual/sky_fog_end_distance": 0.25, + "visual/cloud_color": "#00ffffff", + "visual/cloud_fog_end_distance": 0, + "visual/cloud_height": 1.5 + }, + "functions": [ + "weather/rain" + ] + }, + { + "time": 5000, + "effects": { + "visual/sunrise_sunset_color": "#00ffaa88" + } + }, + { + "time": 5333, + "effects": { + "visual/sky_color": "#3888f0" + } + }, + { + "time": 6000, + "effects": { + "visual/star_brightness": 0, + "visual/sky_light_color": "#fffedd", + "visual/fog_color": "#a0e5e5" + } + }, + { + "time": 7333, + "effects": { + "visual/sky_light_factor": 1, + "visual/sky_color": "#2c5cb4", + "visual/sunrise_sunset_color": "#ffffaa88", + "visual/fog_color": "#d19f76" + } + }, + { + "time": 8000, + "effects": { + "visual/star_angle": 90, + "visual/sky_light_color": "#f86d54", + "visual/sunrise_sunset_color": "#ffee8855" + } + }, + { + "time": 8333, + "effects": { + "visual/sky_light_factor": 0.8 + } + }, + { + "time": 8666, + "effects": { + "visual/sky_light_factor": 0.2, + "visual/sky_light_color": "#14046b", + "visual/sky_color": "#041230", + "visual/sunrise_sunset_color": "#002200ff", + "visual/fog_color": "#202533" + } + }, + { + "time": 9333, + "effects": { + "visual/sun_angle": 105, + "visual/star_brightness": 1, + "visual/sky_light_factor": 0, + "visual/sky_color": "#020918", + "visual/fog_color": "#000000" + } + }, + { + "time": 10666, + "effects": { + "visual/star_angle": 180, + "visual/cloud_height": 2 + } + }, + { + "time": 11000, + "effects": { + "visual/star_brightness": 1, + "visual/fog_end_distance": 500 + } + }, + { + "time": 13333, + "effects": { + "visual/sky_light_factor": 0, + "visual/cloud_color": "#00ffffff", + "visual/cloud_fog_end_distance": 0 + } + }, + { + "time": 14000, + "effects": { + "visual/sky_light_color": "#14046b", + "visual/fog_color": "#000000" + } + }, + { + "time": 14666, + "effects": { + "visual/sun_angle": 255 + } + }, + { + "time": 15000, + "effects": { + "visual/sunrise_sunset_color": "#00220055", + "visual/fog_start_distance": 50, + "visual/sky_fog_end_distance": 0.25, + "visual/cloud_color": "#11ff66aa" + } + }, + { + "time": 15333, + "effects": { + "visual/sky_light_factor": 0.2, + "visual/sky_color": "#020918" + } + }, + { + "time": 15666, + "effects": { + "visual/fog_color": "#556572", + "visual/cloud_fog_end_distance": 0.5 + } + }, + { + "time": 16000, + "effects": { + "visual/star_angle": 270, + "visual/sunrise_sunset_color": "#ffee8822", + "visual/fog_color": "#f8d78e", + "visual/fog_end_distance": 200, + "visual/fog_start_distance": 0 + } + }, + { + "time": 16333, + "effects": { + "visual/sky_light_factor": 0.65, + "visual/sky_light_color": "#f7ad68" + } + }, + { + "time": 16666, + "effects": { + "visual/cloud_color": "#66ff9966" + } + }, + { + "time": 17000, + "effects": { + "visual/sky_color": "#315aa5" + } + }, + { + "time": 17333, + "effects": { + "visual/star_brightness": 0, + "visual/sky_color": "#9ad7fa", + "visual/sunrise_sunset_color": "#00ffaa88", + "visual/fog_color": "#9ed6d6", + "visual/cloud_color": "#55ddddff" + } + }, + { + "time": 17666, + "effects": { + "visual/sky_light_factor": 1 + } + }, + { + "time": 18000, + "effects": { + "visual/sky_light_color": "#c2dee0" + } + }, + { + "time": 23333, + "effects": { + "visual/sky_fog_end_distance": 0.1 + } + }, + { + "time": 23999, + "effects": { + "visual/sun_angle": 360, + "visual/star_angle": 360, + "visual/sky_light_color": "#e1f0fc", + "visual/sky_color": "#76b2d4", + "visual/sunrise_sunset_color": "#00ffaa88", + "visual/fog_color": "#b9dbd4", + "visual/fog_end_distance": 100, + "visual/fog_start_distance": -10, + "visual/sky_fog_end_distance": 0.1, + "visual/cloud_color": "#55ddddff", + "visual/cloud_fog_end_distance": 0.05, + "visual/cloud_height": 0.6 + } + } + ] +} diff --git a/gm4_timelines/raw_data/days/default.json b/gm4_timelines/raw_data/days/default.json new file mode 100644 index 0000000000..e0b3cc9a5f --- /dev/null +++ b/gm4_timelines/raw_data/days/default.json @@ -0,0 +1,9 @@ +{ + "settings": { + "weight": 1, + "moon_phase": ["new_moon"], + "in_type": "clear", + "out_type": "foggy" + }, + "schedule": [] +} diff --git a/gm4_timelines/raw_data/default.json b/gm4_timelines/raw_data/default.json new file mode 100644 index 0000000000..795335393e --- /dev/null +++ b/gm4_timelines/raw_data/default.json @@ -0,0 +1,588 @@ +{ + "period_ticks": 24000, + "tracks": { + "minecraft:audio/firefly_bush_sounds": { + "modifier": "or", + "ease": null, + "keyframes": [ + { + "ticks": 6600, + "value": true + }, + { + "ticks": 17401, + "value": false + } + ] + }, + "minecraft:gameplay/baby_villager_activity": { + "modifier": null, + "ease": null, + "keyframes": [ + { + "ticks": 0, + "value": "minecraft:idle" + }, + { + "ticks": 4000, + "value": "minecraft:play" + }, + { + "ticks": 6000, + "value": "minecraft:rest" + }, + { + "ticks": 18010, + "value": "minecraft:idle" + }, + { + "ticks": 21000, + "value": "minecraft:play" + } + ] + }, + "minecraft:gameplay/bees_stay_in_hive": { + "modifier": "or", + "ease": null, + "keyframes": [ + { + "ticks": 6542, + "value": true + }, + { + "ticks": 17460, + "value": false + } + ] + }, + "minecraft:gameplay/cat_waking_up_gift_chance": { + "modifier": "maximum", + "ease": "constant", + "keyframes": [ + { + "ticks": 17667, + "value": 0.7 + }, + { + "ticks": 18362, + "value": 0 + } + ] + }, + "minecraft:gameplay/creaking_active": { + "modifier": "or", + "ease": null, + "keyframes": [ + { + "ticks": 6600, + "value": true + }, + { + "ticks": 17401, + "value": false + } + ] + }, + "minecraft:gameplay/eyeblossom_open": { + "modifier": "override", + "ease": null, + "keyframes": [ + { + "ticks": 6600, + "value": true + }, + { + "ticks": 17401, + "value": false + } + ] + }, + "minecraft:gameplay/monsters_burn": { + "modifier": "or", + "ease": null, + "keyframes": [ + { + "ticks": 6542, + "value": false + }, + { + "ticks": 17460, + "value": true + } + ] + }, + "minecraft:gameplay/sky_light_level": { + "modifier": "multiply", + "ease": "linear", + "keyframes": [ + { + "ticks": 5867, + "value": 1 + }, + { + "ticks": 7670, + "value": 0.26666668 + }, + { + "ticks": 16330, + "value": 0.26666668 + }, + { + "ticks": 18133, + "value": 1 + } + ] + }, + "minecraft:gameplay/turtle_egg_hatch_chance": { + "modifier": "maximum", + "ease": "constant", + "keyframes": [ + { + "ticks": 15062, + "value": 1 + }, + { + "ticks": 15905, + "value": 0.002 + } + ] + }, + "minecraft:gameplay/villager_activity": { + "modifier": null, + "ease": null, + "keyframes": [ + { + "ticks": 3000, + "value": "minecraft:meet" + }, + { + "ticks": 5000, + "value": "minecraft:idle" + }, + { + "ticks": 6000, + "value": "minecraft:rest" + }, + { + "ticks": 18010, + "value": "minecraft:idle" + }, + { + "ticks": 20000, + "value": "minecraft:work" + } + ] + }, + "minecraft:visual/cloud_color": { + "modifier": "override", + "ease": null, + "keyframes": [ + { + "ticks": 5867, + "value": -1 + }, + { + "ticks": 7670, + "value": -15132378 + }, + { + "ticks": 16330, + "value": -15132378 + }, + { + "ticks": 18133, + "value": -1 + } + ] + }, + "minecraft:visual/cloud_fog_end_distance": { + "modifier": "multiply", + "ease": "linear", + "keyframes": [ + { + "ticks": 18000, + "value": 1 + } + ] + }, + "minecraft:visual/cloud_height": { + "modifier": "multiply", + "ease": "linear", + "keyframes": [ + { + "ticks": 18000, + "value": 1 + } + ] + }, + "minecraft:visual/fog_end_distance": { + "modifier": "multiply", + "ease": "in_out_sine", + "keyframes": [ + { + "ticks": 18000, + "value": 1 + } + ] + }, + "minecraft:visual/fog_start_distance": { + "modifier": "multiply", + "ease": "in_out_sine", + "keyframes": [ + { + "ticks": 18000, + "value": 1 + } + ] + }, + "minecraft:visual/sky_fog_end_distance": { + "modifier": "multiply", + "ease": "in_out_sine", + "keyframes": [ + { + "ticks": 18000, + "value": 1 + } + ] + }, + "minecraft:visual/fog_color": { + "modifier": "override", + "ease": "in_out_sine", + "keyframes": [ + { + "ticks": 5867, + "value": "#ffffff" + }, + { + "ticks": 7670, + "value": "#0f0f16" + }, + { + "ticks": 16330, + "value": "#0f0f16" + }, + { + "ticks": 18133, + "value": "#ffffff" + } + ] + }, + "minecraft:visual/moon_angle": { + "modifier": null, + "ease": { + "cubic_bezier": [ + 0.362, + 0.241, + 0.638, + 0.759 + ] + }, + "keyframes": [ + { + "ticks": 0, + "value": 540 + }, + { + "ticks": 0, + "value": 180 + } + ] + }, + "minecraft:visual/sky_color": { + "modifier": "override", + "ease": null, + "keyframes": [ + { + "ticks": 5867, + "value": "#ffffff" + }, + { + "ticks": 7670, + "value": "#000000" + }, + { + "ticks": 16330, + "value": "#000000" + }, + { + "ticks": 18133, + "value": "#ffffff" + } + ] + }, + "minecraft:visual/sky_light_color": { + "modifier": "override", + "ease": "in_out_sine", + "keyframes": [ + { + "ticks": 5270, + "value": "#ffffff" + }, + { + "ticks": 7140, + "value": "#7a7aff" + }, + { + "ticks": 16860, + "value": "#7a7aff" + }, + { + "ticks": 18730, + "value": "#ffffff" + } + ] + }, + "minecraft:visual/sky_light_factor": { + "modifier": "multiply", + "ease": "linear", + "keyframes": [ + { + "ticks": 5270, + "value": 1 + }, + { + "ticks": 7140, + "value": 0.24 + }, + { + "ticks": 16860, + "value": 0.24 + }, + { + "ticks": 18730, + "value": 1 + } + ] + }, + "minecraft:visual/star_angle": { + "modifier": null, + "ease": { + "cubic_bezier": [ + 0.362, + 0.241, + 0.638, + 0.759 + ] + }, + "keyframes": [ + { + "ticks": 0, + "value": 360 + }, + { + "ticks": 0, + "value": 0 + } + ] + }, + "minecraft:visual/star_brightness": { + "modifier": "override", + "ease": "in_out_sine", + "keyframes": [ + { + "ticks": 5373, + "value": 0 + }, + { + "ticks": 5732, + "value": 0.016 + }, + { + "ticks": 5959, + "value": 0.044 + }, + { + "ticks": 6399, + "value": 0.143 + }, + { + "ticks": 6729, + "value": 0.258 + }, + { + "ticks": 7228, + "value": 0.5 + }, + { + "ticks": 16772, + "value": 0.5 + }, + { + "ticks": 17032, + "value": 0.364 + }, + { + "ticks": 17356, + "value": 0.225 + }, + { + "ticks": 17758, + "value": 0.101 + }, + { + "ticks": 18092, + "value": 0.037 + }, + { + "ticks": 18627, + "value": 0 + } + ] + }, + "minecraft:visual/sun_angle": { + "modifier": null, + "ease": { + "cubic_bezier": [ + 0.362, + 0.241, + 0.638, + 0.759 + ] + }, + "keyframes": [ + { + "ticks": 0, + "value": 360 + }, + { + "ticks": 0, + "value": 0 + } + ] + }, + "minecraft:visual/sunrise_sunset_color": { + "modifier": "override", + "ease": "in_out_sine", + "keyframes": [ + { + "ticks": 5270, + "value": "#00ffe533" + }, + { + "ticks": 5397, + "value": "#04fcd833" + }, + { + "ticks": 5522, + "value": "#0ff9cb33" + }, + { + "ticks": 5690, + "value": "#29f5ba33" + }, + { + "ticks": 5929, + "value": "#5fefa333" + }, + { + "ticks": 6243, + "value": "#b1e78733" + }, + { + "ticks": 6358, + "value": "#cce47e33" + }, + { + "ticks": 6512, + "value": "#e9e07233" + }, + { + "ticks": 6613, + "value": "#f6dd6b33" + }, + { + "ticks": 6732, + "value": "#feda6333" + }, + { + "ticks": 6841, + "value": "#fed75c33" + }, + { + "ticks": 7035, + "value": "#ecd25133" + }, + { + "ticks": 7252, + "value": "#c1cc4733" + }, + { + "ticks": 7775, + "value": "#36be3733" + }, + { + "ticks": 7888, + "value": "#1fbb3533" + }, + { + "ticks": 8039, + "value": "#09b73333" + }, + { + "ticks": 8192, + "value": "#00b33333" + }, + { + "ticks": 15807, + "value": "#00b23333" + }, + { + "ticks": 15961, + "value": "#09b73333" + }, + { + "ticks": 16112, + "value": "#1fbb3533" + }, + { + "ticks": 16225, + "value": "#36be3733" + }, + { + "ticks": 16748, + "value": "#c1cc4733" + }, + { + "ticks": 16965, + "value": "#ecd25133" + }, + { + "ticks": 17159, + "value": "#fed75c33" + }, + { + "ticks": 17272, + "value": "#feda6333" + }, + { + "ticks": 17488, + "value": "#e9e07233" + }, + { + "ticks": 17642, + "value": "#cce47e33" + }, + { + "ticks": 17757, + "value": "#b1e78733" + }, + { + "ticks": 18071, + "value": "#5fefa333" + }, + { + "ticks": 18310, + "value": "#29f5ba33" + }, + { + "ticks": 18565, + "value": "#06fbd433" + }, + { + "ticks": 18730, + "value": "#00ffe533" + } + ] + } + } +} diff --git a/gm4_timelines/raw_data/dev/test.json b/gm4_timelines/raw_data/dev/test.json new file mode 100644 index 0000000000..bb26bdb3a3 --- /dev/null +++ b/gm4_timelines/raw_data/dev/test.json @@ -0,0 +1,237 @@ +{ + "settings": { + "weight": 1, + "moon_phase": [ + "full_moon", + "waning_gibbous", + "third_quarter", + "waning_crescent", + "new_moon", + "waxing_crescent", + "first_quarter", + "waxing_gibbous" + ], + "in_type": "clear", + "out_type": "clear" + }, + "schedule": [ + { + "time": 0, + "effects": { + "visual/sun_angle": 0, + "visual/moon_angle": 180, + "visual/star_angle": 0, + "visual/star_brightness": 0, + "visual/sky_light_factor": 1, + "visual/sky_light_color": "#fffedd", + "visual/sky_color": "#3888f0", + "visual/sunrise_sunset_color": "#00ffaa88", + "visual/fog_color": "#cbfdf2", + "visual/fog_end_distance": 500, + "visual/fog_start_distance": 50, + "visual/sky_fog_end_distance": 0.25, + "visual/cloud_color": "#00ffffff", + "visual/cloud_fog_end_distance": 0, + "visual/cloud_height": 1.5 + }, + "functions": [ + "weather/rain" + ] + }, + { + "time": 5000, + "effects": { + "visual/sunrise_sunset_color": "#00ffaa88" + } + }, + { + "time": 5333, + "effects": { + "visual/sky_color": "#3888f0" + } + }, + { + "time": 6000, + "effects": { + "visual/star_brightness": 0, + "visual/sky_light_color": "#fffedd", + "visual/fog_color": "#a0e5e5" + } + }, + { + "time": 7333, + "effects": { + "visual/sky_light_factor": 1, + "visual/sky_color": "#2c5cb4", + "visual/sunrise_sunset_color": "#ffffaa88", + "visual/fog_color": "#d19f76" + } + }, + { + "time": 8000, + "effects": { + "visual/star_angle": 90, + "visual/sky_light_color": "#f86d54", + "visual/sunrise_sunset_color": "#ffee8855" + } + }, + { + "time": 8333, + "effects": { + "visual/sky_light_factor": 0.8 + } + }, + { + "time": 8666, + "effects": { + "visual/sky_light_factor": 0.2, + "visual/sky_light_color": "#14046b", + "visual/sky_color": "#041230", + "visual/sunrise_sunset_color": "#002200ff", + "visual/fog_color": "#202533" + } + }, + { + "time": 9333, + "effects": { + "visual/sun_angle": 105, + "visual/star_brightness": 1, + "visual/sky_light_factor": 0, + "visual/sky_color": "#020918", + "visual/fog_color": "#000000" + } + }, + { + "time": 10666, + "effects": { + "visual/star_angle": 180, + "visual/cloud_height": 2 + } + }, + { + "time": 11000, + "effects": { + "visual/star_brightness": 1, + "visual/fog_end_distance": 500 + } + }, + { + "time": 13333, + "effects": { + "visual/sky_light_factor": 0, + "visual/cloud_color": "#00ffffff", + "visual/cloud_fog_end_distance": 0 + } + }, + { + "time": 14000, + "effects": { + "visual/sky_light_color": "#14046b", + "visual/fog_color": "#000000" + } + }, + { + "time": 14666, + "effects": { + "visual/sun_angle": 255 + } + }, + { + "time": 15000, + "effects": { + "visual/sunrise_sunset_color": "#00220055", + "visual/fog_start_distance": 50, + "visual/sky_fog_end_distance": 0.25, + "visual/cloud_color": "#11ff66aa" + } + }, + { + "time": 15333, + "effects": { + "visual/sky_light_factor": 0.2, + "visual/sky_color": "#020918" + } + }, + { + "time": 15666, + "effects": { + "visual/fog_color": "#556572", + "visual/cloud_fog_end_distance": 0.5 + } + }, + { + "time": 16000, + "effects": { + "visual/star_angle": 270, + "visual/sunrise_sunset_color": "#ffee8822", + "visual/fog_color": "#f8d78e", + "visual/fog_end_distance": 200, + "visual/fog_start_distance": 0 + } + }, + { + "time": 16333, + "effects": { + "visual/sky_light_factor": 0.65, + "visual/sky_light_color": "#f7ad68" + } + }, + { + "time": 16666, + "effects": { + "visual/cloud_color": "#66ff9966" + } + }, + { + "time": 17000, + "effects": { + "visual/sky_color": "#315aa5" + } + }, + { + "time": 17333, + "effects": { + "visual/star_brightness": 0, + "visual/sky_color": "#9ad7fa", + "visual/sunrise_sunset_color": "#00ffaa88", + "visual/fog_color": "#9ed6d6", + "visual/cloud_color": "#55ddddff" + } + }, + { + "time": 17666, + "effects": { + "visual/sky_light_factor": 1 + } + }, + { + "time": 18000, + "effects": { + "visual/sky_light_color": "#c2dee0" + } + }, + { + "time": 23333, + "effects": { + "visual/sky_fog_end_distance": 0.1 + } + }, + { + "time": 23999, + "effects": { + "visual/sun_angle": 360, + "visual/star_angle": 360, + "visual/sky_light_color": "#e1f0fc", + "visual/sky_color": "#76b2d4", + "visual/sunrise_sunset_color": "#00ffaa88", + "visual/fog_color": "#b9dbd4", + "visual/fog_end_distance": 100, + "visual/fog_start_distance": -10, + "visual/sky_fog_end_distance": 0.1, + "visual/cloud_color": "#55ddddff", + "visual/cloud_fog_end_distance": 0.05, + "visual/cloud_height": 0.6 + } + } + ] +}