]> git.lizzy.rs Git - nothing.git/blob - src/game/level/level_editor.c
(tsoding#1185) replace buffer with formatted print
[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("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         if (camera_render_text_screen(
352                 camera,
353                 save_as_text,
354                 LEVEL_EDITOR_EDIT_FIELD_SIZE,
355                 LEVEL_EDITOR_EDIT_FIELD_COLOR,
356                 position) < 0) {
357             return -1;
358         }
359
360         if (edit_field_render_screen(
361                 level_editor->edit_field_filename,
362                 camera,
363                 vec(position.x + save_as_width, position.y)) < 0) {
364             return -1;
365         }
366     }
367
368     const Rect screen_viewport = camera_view_port_screen(camera);
369     const Vec2f text_size = fading_wiggly_text_size(&level_editor->notice);
370
371     fading_wiggly_text_render(
372         &level_editor->notice, camera,
373         vec(screen_viewport.w * 0.5f - text_size.x * 0.5f,
374             LEVEL_EDITOR_NOTICE_PADDING_TOP));
375
376     return 0;
377 }
378
379 static
380 int level_editor_saveas_event(LevelEditor *level_editor,
381                               const SDL_Event *event,
382                               const Camera *camera)
383 {
384     trace_assert(level_editor);
385     trace_assert(event);
386     trace_assert(camera);
387
388     switch (event->type) {
389     case SDL_KEYDOWN: {
390         if (event->key.keysym.sym == SDLK_RETURN) {
391             trace_assert(level_editor->file_name == NULL);
392             char path[LEVEL_FOLDER_MAX_LENGTH];
393             snprintf(
394                 path,
395                 LEVEL_FOLDER_MAX_LENGTH,
396                 "./assets/levels//%s.txt",
397                 edit_field_as_text(level_editor->edit_field_filename));
398             level_editor->file_name = PUSH_LT(
399                 level_editor->lt,
400                 path,
401                 free);
402             level_editor_dump(level_editor);
403             SDL_StopTextInput();
404             level_editor->state = LEVEL_EDITOR_IDLE;
405             return 0;
406         }
407     } break;
408     }
409
410     return edit_field_event(level_editor->edit_field_filename, event);
411 }
412
413 static
414 int level_editor_idle_event(LevelEditor *level_editor,
415                             const SDL_Event *event,
416                             Camera *camera)
417 {
418     trace_assert(level_editor);
419     trace_assert(event);
420     trace_assert(camera);
421
422     switch (event->type) {
423     case SDL_KEYDOWN: {
424         switch(event->key.keysym.sym) {
425         case SDLK_s: {
426             if (!SDL_IsTextInputActive()) {
427                 if (level_editor->file_name) {
428                     level_editor_dump(level_editor);
429                     log_info("Saving level to `%s`\n", level_editor->file_name);
430                 } else {
431                     SDL_StartTextInput();
432                     level_editor->state = LEVEL_EDITOR_SAVEAS;
433                 }
434             }
435         } break;
436
437         case SDLK_z: {
438             if (event->key.keysym.mod & KMOD_CTRL) {
439                 if (undo_history_empty(&level_editor->undo_history)) {
440                     level_editor->bell = 1;
441                 }
442                 undo_history_pop(&level_editor->undo_history);
443             }
444         } break;
445         }
446     } break;
447
448     case SDL_MOUSEWHEEL: {
449         int x, y;
450         SDL_GetMouseState(&x, &y);
451
452         Vec2f position = camera_map_screen(camera, x, y);
453         if (event->wheel.y > 0) {
454             level_editor->camera_scale += 0.1f;
455         } else if (event->wheel.y < 0) {
456             level_editor->camera_scale = fmaxf(0.1f, level_editor->camera_scale - 0.1f);
457         }
458         camera_scale(camera, level_editor->camera_scale);
459         Vec2f zoomed_position = camera_map_screen(camera, x, y);
460
461         level_editor->camera_position =
462             vec_sum(
463                 level_editor->camera_position,
464                 vec_sub(position, zoomed_position));
465         camera_center_at(camera, level_editor->camera_position);
466     } break;
467
468     case SDL_MOUSEBUTTONUP:
469     case SDL_MOUSEBUTTONDOWN: {
470         if (event->type == SDL_MOUSEBUTTONDOWN && event->button.button == SDL_BUTTON_MIDDLE) {
471             level_editor->drag = true;
472         }
473
474         if (event->type == SDL_MOUSEBUTTONUP && event->button.button == SDL_BUTTON_MIDDLE) {
475             level_editor->drag = false;
476         }
477     } break;
478
479     case SDL_MOUSEMOTION: {
480         if (level_editor->drag) {
481             const Vec2f next_position = camera_map_screen(camera, event->motion.x, event->motion.y);
482             const Vec2f prev_position = camera_map_screen(
483                 camera,
484                 event->motion.x + event->motion.xrel,
485                 event->motion.y + event->motion.yrel);
486
487             vec_add(&level_editor->camera_position,
488                     vec_sub(next_position, prev_position));
489             camera_center_at(camera, level_editor->camera_position);
490         }
491
492     } break;
493     }
494
495     bool selected = false;
496     if (layer_picker_event(
497             &level_editor->layer_picker,
498             event,
499             camera,
500             &selected) < 0) {
501         return -1;
502     }
503
504     if (!selected) {
505         if (layer_event(
506                 level_editor->layers[level_editor->layer_picker],
507                 event,
508                 camera,
509                 &level_editor->undo_history) < 0) {
510             return -1;
511         }
512     } else {
513         level_editor->click = 1;
514     }
515
516
517     return 0;
518 }
519
520 int level_editor_event(LevelEditor *level_editor,
521                        const SDL_Event *event,
522                        Camera *camera)
523 {
524     trace_assert(level_editor);
525     trace_assert(event);
526     trace_assert(camera);
527
528     switch (level_editor->state) {
529     case LEVEL_EDITOR_IDLE:
530         return level_editor_idle_event(level_editor, event, camera);
531
532     case LEVEL_EDITOR_SAVEAS:
533         return level_editor_saveas_event(level_editor, event, camera);
534     }
535
536     return 0;
537 }
538
539 int level_editor_focus_camera(LevelEditor *level_editor,
540                               Camera *camera)
541 {
542     camera_center_at(camera, level_editor->camera_position);
543     camera_scale(camera, level_editor->camera_scale);
544     return 0;
545 }
546
547 static LayerPicker level_format_layer_order[LAYER_PICKER_N] = {
548     LAYER_PICKER_BACKGROUND,
549     LAYER_PICKER_PLAYER,
550     LAYER_PICKER_PLATFORMS,
551     LAYER_PICKER_GOALS,
552     LAYER_PICKER_LAVA,
553     LAYER_PICKER_BACK_PLATFORMS,
554     LAYER_PICKER_BOXES,
555     LAYER_PICKER_LABELS,
556     LAYER_PICKER_REGIONS
557 };
558
559 /* TODO(#904): LevelEditor does not check that the saved level file is modified by external program */
560 static int level_editor_dump(LevelEditor *level_editor)
561 {
562     trace_assert(level_editor);
563
564     FILE *filedump = PUSH_LT(
565         level_editor->lt,
566         fopen(level_editor->file_name, "w"),
567         fclose_lt);
568
569     if (fprintf(filedump, "%s\n", level_metadata_title(level_editor->metadata)) < 0) {
570         return -1;
571     }
572
573     for (size_t i = 0; i < LAYER_PICKER_N; ++i) {
574         if (layer_dump_stream(
575                 level_editor->layers[level_format_layer_order[i]],
576                 filedump) < 0) {
577             return -1;
578         }
579     }
580
581     fclose(RELEASE_LT(level_editor->lt, filedump));
582
583     fading_wiggly_text_reset(&level_editor->notice);
584     level_editor->save = 1;
585
586     return 0;
587 }
588
589 int level_editor_update(LevelEditor *level_editor, float delta_time)
590 {
591     return fading_wiggly_text_update(&level_editor->notice, delta_time);
592 }
593
594 void level_editor_sound(LevelEditor *level_editor, Sound_samples *sound_samples)
595 {
596     trace_assert(sound_samples);
597
598     if (level_editor) {
599         if (level_editor->bell) {
600             level_editor->bell = 0;
601             sound_samples_play_sound(sound_samples, 2);
602         }
603
604         if (level_editor->click) {
605             level_editor->click = 0;
606             sound_samples_play_sound(sound_samples, 3);
607         }
608
609         if (level_editor->save) {
610             level_editor->save = 0;
611             sound_samples_play_sound(sound_samples, 4);
612         }
613     }
614 }