Skip to content

Commit 246070a

Browse files
committed
clean up the code a bit
1 parent cc33118 commit 246070a

File tree

1 file changed

+113
-62
lines changed

1 file changed

+113
-62
lines changed

gm4_timelines/plugins/generate.py

Lines changed: 113 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
1-
from beet import Context, JsonFile, Function, NamespaceFileScope
1+
from beet import Context, JsonFile, Function, NamespaceFileScope # pyright: ignore[reportMissingImports]
2+
from pydantic import BaseModel # pyright: ignore[reportMissingImports]
3+
from typing import Tuple, Dict, Any, List, Optional, ClassVar
24
from pathlib import Path
35
import json
46
import copy
5-
from typing import Tuple, Dict, Any, List, Optional, ClassVar
6-
from pydantic import BaseModel
77

88
TICK_FACTOR: int = 3
99
TICK_OFFSET: int = -6000
10-
FOLDER_PATH: str = "gm4_timelines/raw_data/"
11-
10+
DAY_DURATION_TICKS = 24000
11+
FOLDER_PATH: Path = Path("gm4_timelines/raw_data")
12+
SLIME_VALUES = {
13+
"full_moon": 0.5,
14+
"waning_gibbous": 0.375,
15+
"third_quarter": 0.25,
16+
"waning_crescent": 0.125,
17+
"new_moon": 0,
18+
"waxing_crescent": 0.125,
19+
"first_quarter": 0.25,
20+
"waxing_gibbous": 0.375,
21+
}
1222

1323
# ------------------
1424
# - PYDANTIC STUFF -
@@ -46,17 +56,18 @@ class Timeline(JsonFile):
4656
# -------------
4757

4858
def beet_default(ctx: Context) -> None:
59+
# We register Timeline to Beet here since it doesn't support it yet, when it does this can be removed
4960
ctx.data.extend_namespace += [Timeline]
5061

51-
vanilla_folder = Path(FOLDER_PATH) / "vanilla"
62+
vanilla_folder = FOLDER_PATH / "vanilla"
5263

5364
for file_path in vanilla_folder.glob("*.json"):
5465
with file_path.open("r", encoding="utf-8") as f:
5566
data_dict: Dict[str, Any] = json.load(f)
5667
data = TimelineData(**data_dict)
5768

5869
# factor and offset the ticks so it matches the new daytime
59-
data = factor_ticks(ctx, data)
70+
data = factor_ticks(data)
6071

6172
file_name = file_path.stem
6273
if file_name == "day":
@@ -66,97 +77,133 @@ def beet_default(ctx: Context) -> None:
6677

6778

6879

69-
def factor_ticks(ctx: Context, data: TimelineData) -> TimelineData:
80+
def factor_ticks(data: TimelineData) -> TimelineData:
7081
"""
82+
Each keyframe tick is shifted by a fixed offset, wrapped to the original
83+
period, and then multiplied by the tick factor. The timeline period
84+
itself is also scaled accordingly.
85+
86+
Args:
87+
data: Timeline data whose ticks will be modified.
88+
89+
Returns:
90+
TimelineData instance with modified tick values.
7191
"""
72-
period = data.period_ticks
92+
factored_data = copy.deepcopy(data)
93+
period = factored_data.period_ticks
7394

7495
# offset and scale the ticks
75-
for track in data.tracks.values():
96+
for track in factored_data.tracks.values():
7697
for kf in track.keyframes:
7798
kf.ticks = ((kf.ticks + TICK_OFFSET) % period) * TICK_FACTOR
7899
track.keyframes.sort(key=lambda k: k.ticks)
79100

80101
# scale the period
81-
data.period_ticks = period * TICK_FACTOR
102+
factored_data.period_ticks = period * TICK_FACTOR
82103

83-
return data
104+
return factored_data
84105

85106

86107

87108
def register_days(ctx: Context, data: TimelineData) -> TimelineData:
88109
"""
89-
"""
90-
build_timeline = TimelineData(period_ticks=0, tracks={})
110+
Load day definition files from either dev or days folder. Build combined
111+
timeline by concatenating the tracks of each day's timeline
112+
113+
Args:
114+
data: Base timeline used as the default state for each generated day.
91115
92-
dev_folder = Path(FOLDER_PATH) / "dev"
116+
Returns:
117+
TimelineData instance representing the concatenated day timeline.
118+
"""
119+
dev_folder = FOLDER_PATH / "dev"
93120
dev_files = list(dev_folder.glob("*.json"))
94121

95122
if dev_files:
96-
build_timeline, function_data = process_day_files(ctx, dev_folder, build_timeline, data, is_dev=True)
123+
return process_day_files(ctx, dev_folder, data, is_dev=True)
97124
else:
98-
days_folder = Path(FOLDER_PATH) / "days"
99-
build_timeline, function_data = process_day_files(ctx, days_folder, build_timeline, data)
100-
101-
# create function
102-
day_durarion = 24000 * TICK_FACTOR
103-
ctx.data["gm4_timelines:register/days"] = Function([
104-
"# register days",
105-
"# generated from generate.py",
106-
"",
107-
f'data modify storage gm4_timelines:data day_duration set value {day_durarion}',
108-
f'data modify storage gm4_timelines:data day_registry set value {function_data}',
109-
])
110-
111-
return build_timeline
125+
days_folder = FOLDER_PATH / "days"
126+
return process_day_files(ctx, days_folder, data)
112127

113128

114129

115130
def process_day_files(
116131
ctx: Context,
117132
folder_path: Path,
118-
build_timeline: TimelineData,
119-
data: TimelineData,
133+
default_data: TimelineData,
120134
is_dev: bool = False
121-
) -> Tuple[TimelineData, list[Any]]:
135+
) -> TimelineData:
122136
"""
137+
Load each day definition from provided folder and build a timeline for them.
138+
Days are added once per supported moon phase. Also collects metadata needed to
139+
register the day into storage for the datapack.
140+
141+
Args:
142+
folder_path: Directory containing day definition JSON files.
143+
default_data: Default timeline used as a base for each day.
144+
is_dev (default = False): Whether the day data comes from the development folder.
145+
146+
Returns:
147+
TimelineData instance representing the concatenated day timeline.
123148
"""
124-
function_data: list[Any] = []
149+
function_data: List[Dict[str, Any]] = []
150+
full_timeline = TimelineData(period_ticks=0, tracks={})
125151

152+
# loop over day definitions
126153
for file_path in folder_path.glob("*.json"):
127154
with file_path.open("r", encoding="utf-8") as f:
128155
day_data_dict = json.load(f)
129156
day_data = DayData(**day_data_dict)
130157

131-
moon_phases = day_data.settings['moon_phase']
132-
158+
# loop over supported moon phases
159+
moon_phases = day_data.settings.get("moon_phase", [])
133160
for moon_phase in moon_phases:
134-
day_build, functions = register_day(ctx, data, day_data, moon_phase)
135-
build_timeline = append_to_timeline(build_timeline, day_build)
161+
day_build, functions = register_day(default_data, day_data, moon_phase)
162+
full_timeline = append_to_timeline(full_timeline, day_build)
136163

137164
function_data.append({
138165
"moon_phase": moon_phase,
139166
"in_type": day_data.settings['in_type'],
140167
"out_type": day_data.settings['out_type'],
141168
"weight": day_data.settings['weight'],
142-
"start_time": build_timeline.period_ticks,
169+
"start_time": full_timeline.period_ticks,
143170
"functions": functions,
144171
"dev": is_dev
145172
})
146173

147-
build_timeline.period_ticks += day_build.period_ticks
174+
full_timeline.period_ticks += day_build.period_ticks
148175

149-
return build_timeline, function_data
176+
# create function
177+
day_duration = DAY_DURATION_TICKS * TICK_FACTOR
178+
ctx.data["gm4_timelines:register/days"] = Function([
179+
"# register days",
180+
"# generated from generate.py",
181+
"",
182+
f'data modify storage gm4_timelines:data day_duration set value {day_duration}',
183+
f'data modify storage gm4_timelines:data day_registry set value {function_data}',
184+
])
185+
186+
return full_timeline
150187

151188

152189

153190
def register_day(
154-
ctx: Context,
155191
default_data: TimelineData,
156192
day_data: DayData,
157193
moon_phase: str
158194
) -> Tuple[TimelineData, List[Dict[str, Any]]]:
159195
"""
196+
Build the day data for a day definition, add moon phase and slime spawn chance
197+
tracks and get scheduled function calls. Any non-modified tracks are taken from default_data
198+
199+
Args:
200+
default_data: Base timeline providing default tracks and values.
201+
day_data: Parsed day configuration and schedule.
202+
moon_phase: Moon phase identifier for this day variant.
203+
204+
Returns:
205+
A tuple containing the generated day timeline and a list of function
206+
calls to be triggered at specific ticks.
160207
"""
161208
result = TimelineData(period_ticks=default_data.period_ticks, tracks={})
162209
seen_tracks: set[str] = set()
@@ -170,17 +217,7 @@ def register_day(
170217
seen_tracks.add(track_name)
171218

172219
# create the surface slime spawn chance track
173-
slime_values = {
174-
"full_moon": 0.5,
175-
"waning_gibbous": 0.375,
176-
"third_quarter": 0.25,
177-
"waning_crescent": 0.125,
178-
"new_moon": 0,
179-
"waxing_crescent": 0.125,
180-
"first_quarter": 0.25,
181-
"waxing_gibbous": 0.375,
182-
}
183-
slime_value = slime_values.get(moon_phase, 0.0)
220+
slime_value = SLIME_VALUES.get(moon_phase, 0.0)
184221
track_name = "minecraft:gameplay/surface_slime_spawn_chance"
185222
result.tracks[track_name] = Track(
186223
keyframes=[Keyframe(ticks=0, value=slime_value)],
@@ -194,9 +231,11 @@ def register_day(
194231
ticks = entry.time
195232
effects = entry.effects
196233

197-
if entry.functions != []:
234+
# register function calls
235+
if entry.functions:
198236
functions.append({"tick":ticks,"functions":entry.functions})
199237

238+
# transform effects into timeline tracks
200239
for effect_name, value in effects.items():
201240
track_name = f"minecraft:{effect_name}"
202241
seen_tracks.add(track_name)
@@ -207,39 +246,51 @@ def register_day(
207246
track = Track(
208247
keyframes=[],
209248
modifier=default_track.modifier if default_track else None,
210-
ease=copy.deepcopy(default_track.ease) if default_track else None
249+
ease=default_track.ease if default_track else None
211250
)
212251
result.tracks[track_name] = track
213252

214-
track.keyframes.append(Keyframe(ticks=ticks, value=copy.deepcopy(value)))
253+
track.keyframes.append(Keyframe(ticks=ticks, value=value))
215254

216-
# Copy over untouched default tracks
255+
# copy over untouched default tracks
217256
for track_name, track_data in default_data.tracks.items():
218257
if track_name not in seen_tracks:
219258
result.tracks[track_name] = copy.deepcopy(track_data)
220259

221260
return result, functions
222261

223262

224-
def append_to_timeline(build_timeline: TimelineData, new_data: TimelineData) -> TimelineData:
263+
def append_to_timeline(full_timeline: TimelineData, new_data: TimelineData) -> TimelineData:
225264
"""
265+
Append the next day timeline into the full timeline by offsetting its ticks, they are
266+
shifted by the current period of the full timeline and then merged into the matching
267+
tracks.
268+
269+
Args:
270+
full_timeline: The timeline being appended to.
271+
new_data: The timeline segment to append.
272+
273+
Returns:
274+
The build timeline with the new timeline data merged in.
226275
"""
227-
offset = build_timeline.period_ticks
276+
offset = full_timeline.period_ticks
228277

229278
for track_name, src_track in new_data.tracks.items():
230-
dst_track = build_timeline.tracks.get(track_name)
231279

280+
# find matching track, or create if neccesary
281+
dst_track = full_timeline.tracks.get(track_name)
232282
if not dst_track:
233283
dst_track = Track(
234284
keyframes=[],
235285
modifier=src_track.modifier,
236286
ease=copy.deepcopy(src_track.ease)
237287
)
238-
build_timeline.tracks[track_name] = dst_track
288+
full_timeline.tracks[track_name] = dst_track
239289

290+
# append the keyframes, adding the offset to each ticks value
240291
for kf in src_track.keyframes:
241292
dst_track.keyframes.append(
242293
Keyframe(ticks=kf.ticks + offset, value=copy.deepcopy(kf.value))
243294
)
244295

245-
return build_timeline
296+
return full_timeline

0 commit comments

Comments
 (0)