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