]> git.lizzy.rs Git - nothing.git/blob - src/game/level/level_editor.c
Migrate UndoHistory's backend to RingBuffah
[nothing.git] / src / game / level / level_editor.c
1 #include <stdbool.h>
2
3 #include "game/camera.h"
4 #include "game/sound_samples.h"
5 #include "game/level/boxes.h"
6 #include "game/level/level_editor/action_picker.h"
7 #include "game/level/level_editor/color_picker.h"
8 #include "game/level/level_editor/rect_layer.h"
9 #include "game/level/level_editor/point_layer.h"
10 #include "game/level/level_editor/player_layer.h"
11 #include "game/level/level_editor/label_layer.h"
12 #include "game/level/level_editor/background_layer.h"
13 #include "ui/edit_field.h"
14 #include "system/stacktrace.h"
15 #include "system/nth_alloc.h"
16 #include "system/lt.h"
17 #include "system/lt_adapters.h"
18 #include "system/log.h"
19 #include "system/str.h"
20 #include "config.h"
21 #include "math/extrema.h"
22 #include "system/file.h"
23
24 #include "level_editor.h"
25
26 #define LEVEL_FOLDER_MAX_LENGTH 512
27 #define LEVEL_EDITOR_EDIT_FIELD_SIZE vec(5.0f, 5.0f)
28 #define LEVEL_EDITOR_EDIT_FIELD_COLOR COLOR_BLACK
29
30 #define LEVEL_EDITOR_NOTICE_SCALE vec(10.0f, 10.0f)
31 #define LEVEL_EDITOR_NOTICE_DURATION 1.0f
32 #define LEVEL_EDITOR_NOTICE_PADDING_TOP 100.0f
33 #define LEVEL_EDITOR_TMPMEM_CAPACITY (640 * KILO)
34
35 static int level_editor_dump(LevelEditor *level_editor);
36
37 // TODO(#994): too much duplicate code between create_level_editor and create_level_editor_from_file
38
39 LevelEditor *create_level_editor(Cursor *cursor)
40 {
41     Lt *lt = create_lt();
42     LevelEditor *level_editor = PUSH_LT(
43         lt,
44         nth_calloc(1, sizeof(LevelEditor)),
45         free);
46     if (level_editor == NULL) {
47         RETURN_LT(lt, NULL);
48     }
49     level_editor->lt = lt;
50
51     level_editor->edit_field_filename = PUSH_LT(
52         lt,
53         create_edit_field(
54             LEVEL_EDITOR_EDIT_FIELD_SIZE,
55             LEVEL_EDITOR_EDIT_FIELD_COLOR),
56         destroy_edit_field);
57     if (level_editor->edit_field_filename == NULL) {
58         RETURN_LT(lt, NULL);
59     }
60
61     level_editor->background_layer = create_background_layer(hexstr("fffda5"));
62
63     level_editor->player_layer =
64         create_player_layer(vec(0.0f, 0.0f), hexstr("ff8080"));
65
66     level_editor->platforms_layer = PUSH_LT(
67         lt,
68         create_rect_layer("platform", cursor),
69         destroy_rect_layer);
70     if (level_editor->platforms_layer == NULL) {
71         RETURN_LT(lt, NULL);
72     }
73
74     level_editor->goals_layer = PUSH_LT(
75         lt,
76         create_point_layer("goal"),
77         destroy_point_layer);
78     if (level_editor->goals_layer == NULL) {
79         RETURN_LT(lt, NULL);
80     }
81
82     level_editor->lava_layer = PUSH_LT(
83         lt,
84         create_rect_layer("lava", cursor),
85         destroy_rect_layer);
86     if (level_editor->lava_layer == NULL) {
87         RETURN_LT(lt, NULL);
88     }
89
90     level_editor->back_platforms_layer = PUSH_LT(
91         lt,
92         create_rect_layer("back_platform", cursor),
93         destroy_rect_layer);
94     if (level_editor->back_platforms_layer == NULL) {
95         RETURN_LT(lt, NULL);
96     }
97
98     level_editor->boxes_layer = PUSH_LT(
99         lt,
100         create_rect_layer("box", cursor),
101         destroy_rect_layer);
102     if (level_editor->boxes_layer == NULL) {
103         RETURN_LT(lt, NULL);
104     }
105
106     level_editor->label_layer = PUSH_LT(
107         lt,
108         create_label_layer("label"),
109         destroy_label_layer);
110     if (level_editor->label_layer == NULL) {
111         RETURN_LT(lt, NULL);
112     }
113
114     level_editor->regions_layer = PUSH_LT(
115         lt,
116         create_rect_layer("region", cursor),
117         destroy_rect_layer);
118     if (level_editor->regions_layer == NULL) {
119         RETURN_LT(lt, NULL);
120     }
121
122     level_editor->layers[LAYER_PICKER_BOXES] = rect_layer_as_layer(level_editor->boxes_layer);
123     level_editor->layers[LAYER_PICKER_PLATFORMS] = rect_layer_as_layer(level_editor->platforms_layer);
124     level_editor->layers[LAYER_PICKER_BACK_PLATFORMS] = rect_layer_as_layer(level_editor->back_platforms_layer);
125     level_editor->layers[LAYER_PICKER_GOALS] = point_layer_as_layer(level_editor->goals_layer);
126     level_editor->layers[LAYER_PICKER_PLAYER] = player_layer_as_layer(&level_editor->player_layer);
127     level_editor->layers[LAYER_PICKER_LAVA] = rect_layer_as_layer(level_editor->lava_layer);
128     level_editor->layers[LAYER_PICKER_REGIONS] = rect_layer_as_layer(level_editor->regions_layer);
129     level_editor->layers[LAYER_PICKER_BACKGROUND] = background_layer_as_layer(&level_editor->background_layer);
130     level_editor->layers[LAYER_PICKER_LABELS] = label_layer_as_layer(level_editor->label_layer);
131
132     level_editor->notice = (FadingWigglyText) {
133         .wiggly_text = {
134             .text = "Level saved",
135             .color = rgba(0.0f, 0.0f, 0.0f, 0.0f),
136             .scale = LEVEL_EDITOR_NOTICE_SCALE
137         },
138         .duration = LEVEL_EDITOR_NOTICE_DURATION,
139     };
140
141     level_editor->camera_scale = 1.0f;
142
143     level_editor->undo_history = create_undo_history();
144
145     return level_editor;
146 }
147
148 LevelEditor *create_level_editor_from_file(const char *file_name, Cursor *cursor)
149 {
150     trace_assert(file_name);
151
152     Lt *lt = create_lt();
153     LevelEditor *level_editor = PUSH_LT(
154         lt,
155         nth_calloc(1, sizeof(LevelEditor)),
156         free);
157     if (level_editor == NULL) {
158         RETURN_LT(lt, NULL);
159     }
160     level_editor->lt = lt;
161
162     level_editor->edit_field_filename = PUSH_LT(
163         lt,
164         create_edit_field(
165             LEVEL_EDITOR_EDIT_FIELD_SIZE,
166             LEVEL_EDITOR_EDIT_FIELD_COLOR),
167         destroy_edit_field);
168     if (level_editor->edit_field_filename == NULL) {
169         RETURN_LT(lt, NULL);
170     }
171
172     level_editor->file_name =
173         PUSH_LT(
174             lt,
175             string_duplicate(file_name, NULL),
176             free);
177
178     Memory tmpmem = {
179         .capacity = LEVEL_EDITOR_TMPMEM_CAPACITY,
180         .buffer = malloc(LEVEL_EDITOR_TMPMEM_CAPACITY),
181     };
182     trace_assert(tmpmem.buffer);
183
184     String input = read_whole_file(&tmpmem, file_name);
185     trace_assert(input.data);
186
187     String version = trim(chop_by_delim(&input, '\n'));
188
189     if (string_equal(version, STRING_LIT("1"))) {
190         chop_by_delim(&input, '\n');
191     } else if (string_equal(version, STRING_LIT("2"))) {
192         // Nothing
193     } else {
194         log_fail("Version `%s` is not supported. Expected version `%s`.\n",
195                  string_to_cstr(&tmpmem, version),
196                  VERSION);
197         RETURN_LT(lt, NULL);
198     }
199
200     level_editor->background_layer = chop_background_layer(&input);
201     level_editor->player_layer = chop_player_layer(&tmpmem, &input);
202     level_editor->platforms_layer = chop_rect_layer(&tmpmem, &input, "platform", cursor);
203     level_editor->goals_layer = chop_point_layer(&tmpmem, &input, "goal");
204     level_editor->lava_layer = chop_rect_layer(&tmpmem, &input, "lava", cursor);
205     level_editor->back_platforms_layer = chop_rect_layer(&tmpmem, &input, "back_platform", cursor);
206     level_editor->boxes_layer = chop_rect_layer(&tmpmem, &input, "box", cursor);
207     level_editor->label_layer = chop_label_layer(&tmpmem, &input, "label");
208     level_editor->regions_layer = chop_rect_layer(&tmpmem, &input, "region", cursor),
209
210     level_editor->layers[LAYER_PICKER_BOXES] = rect_layer_as_layer(level_editor->boxes_layer);
211     level_editor->layers[LAYER_PICKER_PLATFORMS] = rect_layer_as_layer(level_editor->platforms_layer);
212     level_editor->layers[LAYER_PICKER_BACK_PLATFORMS] = rect_layer_as_layer(level_editor->back_platforms_layer);
213     level_editor->layers[LAYER_PICKER_GOALS] = point_layer_as_layer(level_editor->goals_layer);
214     level_editor->layers[LAYER_PICKER_PLAYER] = player_layer_as_layer(&level_editor->player_layer);
215     level_editor->layers[LAYER_PICKER_LAVA] = rect_layer_as_layer(level_editor->lava_layer);
216     level_editor->layers[LAYER_PICKER_REGIONS] = rect_layer_as_layer(level_editor->regions_layer);
217     level_editor->layers[LAYER_PICKER_BACKGROUND] = background_layer_as_layer(&level_editor->background_layer);
218     level_editor->layers[LAYER_PICKER_LABELS] = label_layer_as_layer(level_editor->label_layer);
219
220     level_editor->drag = false;
221
222     level_editor->notice = (FadingWigglyText) {
223         .wiggly_text = {
224             .text = "Level saved",
225             .color = rgba(0.0f, 0.0f, 0.0f, 0.0f),
226             .scale = LEVEL_EDITOR_NOTICE_SCALE
227         },
228         .duration = LEVEL_EDITOR_NOTICE_DURATION,
229     };
230
231     level_editor->camera_scale = 1.0f;
232
233     level_editor->undo_history = create_undo_history();
234
235     free(tmpmem.buffer);
236
237     return level_editor;
238 }
239
240 void destroy_level_editor(LevelEditor *level_editor)
241 {
242     trace_assert(level_editor);
243     destroy_undo_history(level_editor->undo_history);
244     RETURN_LT0(level_editor->lt);
245 }
246
247 int level_editor_render(const LevelEditor *level_editor,
248                         const Camera *camera)
249 {
250     trace_assert(level_editor);
251     trace_assert(camera);
252
253     if (camera_clear_background(camera, color_picker_rgba(&level_editor->background_layer.color_picker)) < 0) {
254         return -1;
255     }
256
257     const Rect world_viewport = camera_view_port(camera);
258
259     if (PLAYER_DEATH_LEVEL < world_viewport.y + world_viewport.h) {
260         if (camera_fill_rect(
261                 camera,
262                 rect(
263                     world_viewport.x, PLAYER_DEATH_LEVEL,
264                     world_viewport.w, world_viewport.h + fmaxf(0.0f, world_viewport.y - PLAYER_DEATH_LEVEL)),
265                 LEVEL_EDITOR_DETH_LEVEL_COLOR) < 0) {
266             return -1;
267         }
268     }
269
270     for (size_t i = 0; i < LAYER_PICKER_N; ++i) {
271         if (layer_render(
272                 level_editor->layers[i],
273                 camera,
274                 i == level_editor->layer_picker) < 0) {
275             return -1;
276         }
277     }
278
279     if (layer_picker_render(&level_editor->layer_picker, camera) < 0) {
280         return -1;
281     }
282
283     if (level_editor->state == LEVEL_EDITOR_SAVEAS) {
284         /* CSS */
285         const Vec2f size = LEVEL_EDITOR_EDIT_FIELD_SIZE;
286         const char *save_as_text = "Save as: ";
287         const Vec2f position = vec(200.0f, 200.0f);
288         const float save_as_width =
289             (float) strlen(save_as_text) * FONT_CHAR_WIDTH * size.x;
290
291         /* HTML */
292         camera_render_text_screen(
293             camera,
294             save_as_text,
295             LEVEL_EDITOR_EDIT_FIELD_SIZE,
296             LEVEL_EDITOR_EDIT_FIELD_COLOR,
297             position);
298
299         if (edit_field_render_screen(
300                 level_editor->edit_field_filename,
301                 camera,
302                 vec(position.x + save_as_width, position.y)) < 0) {
303             return -1;
304         }
305     }
306
307     const Rect screen_viewport = camera_view_port_screen(camera);
308     const Vec2f text_size = fading_wiggly_text_size(&level_editor->notice);
309
310     fading_wiggly_text_render(
311         &level_editor->notice, camera,
312         vec(screen_viewport.w * 0.5f - text_size.x * 0.5f,
313             LEVEL_EDITOR_NOTICE_PADDING_TOP));
314
315     return 0;
316 }
317
318 static
319 int level_editor_saveas_event(LevelEditor *level_editor,
320                               const SDL_Event *event,
321                               const Camera *camera)
322 {
323     trace_assert(level_editor);
324     trace_assert(event);
325     trace_assert(camera);
326
327     switch (event->type) {
328     case SDL_KEYDOWN: {
329         if (event->key.keysym.sym == SDLK_RETURN) {
330             trace_assert(level_editor->file_name == NULL);
331             char path[LEVEL_FOLDER_MAX_LENGTH];
332             snprintf(
333                 path,
334                 LEVEL_FOLDER_MAX_LENGTH,
335                 "./assets/levels/%s.txt",
336                 edit_field_as_text(level_editor->edit_field_filename));
337             level_editor->file_name = PUSH_LT(
338                 level_editor->lt,
339                 string_duplicate(path, NULL),
340                 free);
341             level_editor_dump(level_editor);
342             SDL_StopTextInput();
343             level_editor->state = LEVEL_EDITOR_IDLE;
344             return 0;
345         }
346     } break;
347     }
348
349     return edit_field_event(level_editor->edit_field_filename, event);
350 }
351
352 static
353 int level_editor_idle_event(LevelEditor *level_editor,
354                             const SDL_Event *event,
355                             Camera *camera)
356 {
357     trace_assert(level_editor);
358     trace_assert(event);
359     trace_assert(camera);
360
361     switch (event->type) {
362     case SDL_KEYDOWN: {
363         switch(event->key.keysym.sym) {
364         case SDLK_s: {
365             if (!SDL_IsTextInputActive()) {
366                 if (level_editor->file_name) {
367                     level_editor_dump(level_editor);
368                     log_info("Saving level to `%s`\n", level_editor->file_name);
369                 } else {
370                     SDL_StartTextInput();
371                     level_editor->state = LEVEL_EDITOR_SAVEAS;
372                 }
373             }
374         } break;
375
376         case SDLK_z: {
377             if (event->key.keysym.mod & KMOD_CTRL) {
378                 if (undo_history_empty(&level_editor->undo_history)) {
379                     level_editor->bell = 1;
380                 }
381                 undo_history_pop(&level_editor->undo_history);
382             }
383         } break;
384         }
385     } break;
386
387     case SDL_MOUSEWHEEL: {
388         int x, y;
389         SDL_GetMouseState(&x, &y);
390
391         Vec2f position = camera_map_screen(camera, x, y);
392         if (event->wheel.y > 0) {
393             level_editor->camera_scale += 0.1f;
394         } else if (event->wheel.y < 0) {
395             level_editor->camera_scale = fmaxf(0.1f, level_editor->camera_scale - 0.1f);
396         }
397         camera_scale(camera, level_editor->camera_scale);
398         Vec2f zoomed_position = camera_map_screen(camera, x, y);
399
400         level_editor->camera_position =
401             vec_sum(
402                 level_editor->camera_position,
403                 vec_sub(position, zoomed_position));
404         camera_center_at(camera, level_editor->camera_position);
405     } break;
406
407     case SDL_MOUSEBUTTONUP:
408     case SDL_MOUSEBUTTONDOWN: {
409         if (event->type == SDL_MOUSEBUTTONDOWN && event->button.button == SDL_BUTTON_MIDDLE) {
410             level_editor->drag = true;
411         }
412
413         if (event->type == SDL_MOUSEBUTTONUP && event->button.button == SDL_BUTTON_MIDDLE) {
414             level_editor->drag = false;
415         }
416     } break;
417
418     case SDL_MOUSEMOTION: {
419         if (level_editor->drag) {
420             const Vec2f next_position = camera_map_screen(camera, event->motion.x, event->motion.y);
421             const Vec2f prev_position = camera_map_screen(
422                 camera,
423                 event->motion.x + event->motion.xrel,
424                 event->motion.y + event->motion.yrel);
425
426             vec_add(&level_editor->camera_position,
427                     vec_sub(next_position, prev_position));
428             camera_center_at(camera, level_editor->camera_position);
429         }
430
431     } break;
432     }
433
434     bool selected = false;
435     if (layer_picker_event(
436             &level_editor->layer_picker,
437             event,
438             camera,
439             &selected) < 0) {
440         return -1;
441     }
442
443     if (!selected) {
444         if (layer_event(
445                 level_editor->layers[level_editor->layer_picker],
446                 event,
447                 camera,
448                 &level_editor->undo_history) < 0) {
449             return -1;
450         }
451     } else {
452         level_editor->click = 1;
453     }
454
455
456     return 0;
457 }
458
459 int level_editor_event(LevelEditor *level_editor,
460                        const SDL_Event *event,
461                        Camera *camera)
462 {
463     trace_assert(level_editor);
464     trace_assert(event);
465     trace_assert(camera);
466
467     switch (level_editor->state) {
468     case LEVEL_EDITOR_IDLE:
469         return level_editor_idle_event(level_editor, event, camera);
470
471     case LEVEL_EDITOR_SAVEAS:
472         return level_editor_saveas_event(level_editor, event, camera);
473     }
474
475     return 0;
476 }
477
478 int level_editor_focus_camera(LevelEditor *level_editor,
479                               Camera *camera)
480 {
481     camera_center_at(camera, level_editor->camera_position);
482     camera_scale(camera, level_editor->camera_scale);
483     return 0;
484 }
485
486 static LayerPicker level_format_layer_order[LAYER_PICKER_N] = {
487     LAYER_PICKER_BACKGROUND,
488     LAYER_PICKER_PLAYER,
489     LAYER_PICKER_PLATFORMS,
490     LAYER_PICKER_GOALS,
491     LAYER_PICKER_LAVA,
492     LAYER_PICKER_BACK_PLATFORMS,
493     LAYER_PICKER_BOXES,
494     LAYER_PICKER_LABELS,
495     LAYER_PICKER_REGIONS
496 };
497
498 /* TODO(#904): LevelEditor does not check that the saved level file is modified by external program */
499 static int level_editor_dump(LevelEditor *level_editor)
500 {
501     trace_assert(level_editor);
502
503     FILE *filedump = PUSH_LT(
504         level_editor->lt,
505         fopen(level_editor->file_name, "w"),
506         fclose_lt);
507
508     if (fprintf(filedump, "%s\n", VERSION) < 0) {
509         return -1;
510     }
511
512     for (size_t i = 0; i < LAYER_PICKER_N; ++i) {
513         if (layer_dump_stream(
514                 level_editor->layers[level_format_layer_order[i]],
515                 filedump) < 0) {
516             return -1;
517         }
518     }
519
520     fclose(RELEASE_LT(level_editor->lt, filedump));
521
522     fading_wiggly_text_reset(&level_editor->notice);
523     level_editor->save = 1;
524
525     return 0;
526 }
527
528 int level_editor_update(LevelEditor *level_editor, float delta_time)
529 {
530     return fading_wiggly_text_update(&level_editor->notice, delta_time);
531 }
532
533 void level_editor_sound(LevelEditor *level_editor, Sound_samples *sound_samples)
534 {
535     trace_assert(sound_samples);
536
537     if (level_editor) {
538         if (level_editor->bell) {
539             level_editor->bell = 0;
540             sound_samples_play_sound(sound_samples, 2);
541         }
542
543         if (level_editor->click) {
544             level_editor->click = 0;
545             sound_samples_play_sound(sound_samples, 3);
546         }
547
548         if (level_editor->save) {
549             level_editor->save = 0;
550             sound_samples_play_sound(sound_samples, 4);
551         }
552     }
553 }