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