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