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