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