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