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"
21 #include "math/extrema.h"
23 #include "level_editor.h"
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
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
34 static int level_editor_dump(LevelEditor *level_editor);
36 // TODO(#994): too much duplicate code between create_level_editor and create_level_editor_from_file
38 LevelEditor *create_level_editor(Cursor *cursor)
41 LevelEditor *level_editor = PUSH_LT(
43 nth_calloc(1, sizeof(LevelEditor)),
45 if (level_editor == NULL) {
48 level_editor->lt = lt;
50 level_editor->edit_field_filename = PUSH_LT(
53 LEVEL_EDITOR_EDIT_FIELD_SIZE,
54 LEVEL_EDITOR_EDIT_FIELD_COLOR),
56 if (level_editor->edit_field_filename == NULL) {
60 level_editor->background_layer = create_background_layer(hexstr("fffda5"));
62 level_editor->player_layer =
63 create_player_layer(vec(0.0f, 0.0f), hexstr("ff8080"));
65 level_editor->platforms_layer = PUSH_LT(
67 create_rect_layer("platform", cursor),
69 if (level_editor->platforms_layer == NULL) {
73 level_editor->goals_layer = PUSH_LT(
75 create_point_layer("goal"),
77 if (level_editor->goals_layer == NULL) {
81 level_editor->lava_layer = PUSH_LT(
83 create_rect_layer("lava", cursor),
85 if (level_editor->lava_layer == NULL) {
89 level_editor->back_platforms_layer = PUSH_LT(
91 create_rect_layer("back_platform", cursor),
93 if (level_editor->back_platforms_layer == NULL) {
97 level_editor->boxes_layer = PUSH_LT(
99 create_rect_layer("box", cursor),
101 if (level_editor->boxes_layer == NULL) {
105 level_editor->label_layer = PUSH_LT(
107 create_label_layer("label"),
108 destroy_label_layer);
109 if (level_editor->label_layer == NULL) {
113 level_editor->regions_layer = PUSH_LT(
115 create_rect_layer("region", cursor),
117 if (level_editor->regions_layer == NULL) {
121 level_editor->layers[LAYER_PICKER_BOXES] = rect_layer_as_layer(level_editor->boxes_layer);
122 level_editor->layers[LAYER_PICKER_PLATFORMS] = rect_layer_as_layer(level_editor->platforms_layer);
123 level_editor->layers[LAYER_PICKER_BACK_PLATFORMS] = rect_layer_as_layer(level_editor->back_platforms_layer);
124 level_editor->layers[LAYER_PICKER_GOALS] = point_layer_as_layer(level_editor->goals_layer);
125 level_editor->layers[LAYER_PICKER_PLAYER] = player_layer_as_layer(&level_editor->player_layer);
126 level_editor->layers[LAYER_PICKER_LAVA] = rect_layer_as_layer(level_editor->lava_layer);
127 level_editor->layers[LAYER_PICKER_REGIONS] = rect_layer_as_layer(level_editor->regions_layer);
128 level_editor->layers[LAYER_PICKER_BACKGROUND] = background_layer_as_layer(&level_editor->background_layer);
129 level_editor->layers[LAYER_PICKER_LABELS] = label_layer_as_layer(level_editor->label_layer);
131 level_editor->notice = (FadingWigglyText) {
133 .text = "Level saved",
134 .color = rgba(0.0f, 0.0f, 0.0f, 0.0f),
135 .scale = LEVEL_EDITOR_NOTICE_SCALE
137 .duration = LEVEL_EDITOR_NOTICE_DURATION,
140 level_editor->camera_scale = 1.0f;
145 LevelEditor *create_level_editor_from_file(const char *file_name, Cursor *cursor)
147 trace_assert(file_name);
149 Lt *lt = create_lt();
150 LevelEditor *level_editor = PUSH_LT(
152 nth_calloc(1, sizeof(LevelEditor)),
154 if (level_editor == NULL) {
157 level_editor->lt = lt;
159 level_editor->edit_field_filename = PUSH_LT(
162 LEVEL_EDITOR_EDIT_FIELD_SIZE,
163 LEVEL_EDITOR_EDIT_FIELD_COLOR),
165 if (level_editor->edit_field_filename == NULL) {
169 level_editor->file_name =
172 string_duplicate(file_name, NULL),
175 LineStream *level_stream = PUSH_LT(
180 LEVEL_LINE_MAX_LENGTH),
181 destroy_line_stream);
182 if (level_stream == NULL) {
186 const char *line = line_stream_next(level_stream);
191 char version[METADATA_VERSION_MAX_SIZE] = {0};
192 memcpy(version, line,
195 METADATA_VERSION_MAX_SIZE - 1));
196 trim_endline(version);
198 if (strcmp(version, "1") == 0) {
199 if (line_stream_next(level_stream) == NULL)
201 } else if (strcmp(version, "2") == 0) {
204 log_fail("Version `%s` is not supported. Expected version `%s`.\n",
209 if (background_layer_read_from_line_stream(
210 &level_editor->background_layer,
215 level_editor->player_layer =
216 create_player_layer_from_line_stream(level_stream);
218 level_editor->platforms_layer =
221 create_rect_layer_from_line_stream(level_stream, "platform", cursor),
223 if (level_editor->platforms_layer == NULL) {
227 level_editor->goals_layer = PUSH_LT(
229 create_point_layer_from_line_stream(level_stream, "goal"),
230 destroy_point_layer);
231 if (level_editor->goals_layer == NULL) {
235 level_editor->lava_layer =
238 create_rect_layer_from_line_stream(level_stream, "lava", cursor),
240 if (level_editor->lava_layer == NULL) {
244 level_editor->back_platforms_layer =
247 create_rect_layer_from_line_stream(level_stream, "back_platform", cursor),
249 if (level_editor->back_platforms_layer == NULL) {
253 level_editor->boxes_layer =
256 create_rect_layer_from_line_stream(level_stream, "box", cursor),
258 if (level_editor->boxes_layer == NULL) {
262 level_editor->label_layer =
265 create_label_layer_from_line_stream(level_stream, "label"),
266 destroy_label_layer);
267 if (level_editor->label_layer == NULL) {
271 level_editor->regions_layer =
274 create_rect_layer_from_line_stream(level_stream, "region", cursor),
276 if (level_editor->regions_layer == NULL) {
280 level_editor->layers[LAYER_PICKER_BOXES] = rect_layer_as_layer(level_editor->boxes_layer);
281 level_editor->layers[LAYER_PICKER_PLATFORMS] = rect_layer_as_layer(level_editor->platforms_layer);
282 level_editor->layers[LAYER_PICKER_BACK_PLATFORMS] = rect_layer_as_layer(level_editor->back_platforms_layer);
283 level_editor->layers[LAYER_PICKER_GOALS] = point_layer_as_layer(level_editor->goals_layer);
284 level_editor->layers[LAYER_PICKER_PLAYER] = player_layer_as_layer(&level_editor->player_layer);
285 level_editor->layers[LAYER_PICKER_LAVA] = rect_layer_as_layer(level_editor->lava_layer);
286 level_editor->layers[LAYER_PICKER_REGIONS] = rect_layer_as_layer(level_editor->regions_layer);
287 level_editor->layers[LAYER_PICKER_BACKGROUND] = background_layer_as_layer(&level_editor->background_layer);
288 level_editor->layers[LAYER_PICKER_LABELS] = label_layer_as_layer(level_editor->label_layer);
290 level_editor->drag = false;
292 level_editor->notice = (FadingWigglyText) {
294 .text = "Level saved",
295 .color = rgba(0.0f, 0.0f, 0.0f, 0.0f),
296 .scale = LEVEL_EDITOR_NOTICE_SCALE
298 .duration = LEVEL_EDITOR_NOTICE_DURATION,
301 level_editor->camera_scale = 1.0f;
306 void destroy_level_editor(LevelEditor *level_editor)
308 trace_assert(level_editor);
309 destroy_undo_history(level_editor->undo_history);
310 RETURN_LT0(level_editor->lt);
313 int level_editor_render(const LevelEditor *level_editor,
314 const Camera *camera)
316 trace_assert(level_editor);
317 trace_assert(camera);
319 if (camera_clear_background(camera, color_picker_rgba(&level_editor->background_layer.color_picker)) < 0) {
323 const Rect world_viewport = camera_view_port(camera);
325 if (PLAYER_DEATH_LEVEL < world_viewport.y + world_viewport.h) {
326 if (camera_fill_rect(
329 world_viewport.x, PLAYER_DEATH_LEVEL,
330 world_viewport.w, world_viewport.h + fmaxf(0.0f, world_viewport.y - PLAYER_DEATH_LEVEL)),
331 LEVEL_EDITOR_DETH_LEVEL_COLOR) < 0) {
336 for (size_t i = 0; i < LAYER_PICKER_N; ++i) {
338 level_editor->layers[i],
340 i == level_editor->layer_picker) < 0) {
345 if (layer_picker_render(&level_editor->layer_picker, camera) < 0) {
349 if (level_editor->state == LEVEL_EDITOR_SAVEAS) {
351 const Vec2f size = LEVEL_EDITOR_EDIT_FIELD_SIZE;
352 const char *save_as_text = "Save as: ";
353 const Vec2f position = vec(200.0f, 200.0f);
354 const float save_as_width =
355 (float) strlen(save_as_text) * FONT_CHAR_WIDTH * size.x;
358 camera_render_text_screen(
361 LEVEL_EDITOR_EDIT_FIELD_SIZE,
362 LEVEL_EDITOR_EDIT_FIELD_COLOR,
365 if (edit_field_render_screen(
366 level_editor->edit_field_filename,
368 vec(position.x + save_as_width, position.y)) < 0) {
373 const Rect screen_viewport = camera_view_port_screen(camera);
374 const Vec2f text_size = fading_wiggly_text_size(&level_editor->notice);
376 fading_wiggly_text_render(
377 &level_editor->notice, camera,
378 vec(screen_viewport.w * 0.5f - text_size.x * 0.5f,
379 LEVEL_EDITOR_NOTICE_PADDING_TOP));
385 int level_editor_saveas_event(LevelEditor *level_editor,
386 const SDL_Event *event,
387 const Camera *camera)
389 trace_assert(level_editor);
391 trace_assert(camera);
393 switch (event->type) {
395 if (event->key.keysym.sym == SDLK_RETURN) {
396 trace_assert(level_editor->file_name == NULL);
397 char path[LEVEL_FOLDER_MAX_LENGTH];
400 LEVEL_FOLDER_MAX_LENGTH,
401 "./assets/levels/%s.txt",
402 edit_field_as_text(level_editor->edit_field_filename));
403 level_editor->file_name = PUSH_LT(
405 string_duplicate(path, NULL),
407 level_editor_dump(level_editor);
409 level_editor->state = LEVEL_EDITOR_IDLE;
415 return edit_field_event(level_editor->edit_field_filename, event);
419 int level_editor_idle_event(LevelEditor *level_editor,
420 const SDL_Event *event,
423 trace_assert(level_editor);
425 trace_assert(camera);
427 switch (event->type) {
429 switch(event->key.keysym.sym) {
431 if (!SDL_IsTextInputActive()) {
432 if (level_editor->file_name) {
433 level_editor_dump(level_editor);
434 log_info("Saving level to `%s`\n", level_editor->file_name);
436 SDL_StartTextInput();
437 level_editor->state = LEVEL_EDITOR_SAVEAS;
443 if (event->key.keysym.mod & KMOD_CTRL) {
444 if (undo_history_empty(&level_editor->undo_history)) {
445 level_editor->bell = 1;
447 undo_history_pop(&level_editor->undo_history);
453 case SDL_MOUSEWHEEL: {
455 SDL_GetMouseState(&x, &y);
457 Vec2f position = camera_map_screen(camera, x, y);
458 if (event->wheel.y > 0) {
459 level_editor->camera_scale += 0.1f;
460 } else if (event->wheel.y < 0) {
461 level_editor->camera_scale = fmaxf(0.1f, level_editor->camera_scale - 0.1f);
463 camera_scale(camera, level_editor->camera_scale);
464 Vec2f zoomed_position = camera_map_screen(camera, x, y);
466 level_editor->camera_position =
468 level_editor->camera_position,
469 vec_sub(position, zoomed_position));
470 camera_center_at(camera, level_editor->camera_position);
473 case SDL_MOUSEBUTTONUP:
474 case SDL_MOUSEBUTTONDOWN: {
475 if (event->type == SDL_MOUSEBUTTONDOWN && event->button.button == SDL_BUTTON_MIDDLE) {
476 level_editor->drag = true;
479 if (event->type == SDL_MOUSEBUTTONUP && event->button.button == SDL_BUTTON_MIDDLE) {
480 level_editor->drag = false;
484 case SDL_MOUSEMOTION: {
485 if (level_editor->drag) {
486 const Vec2f next_position = camera_map_screen(camera, event->motion.x, event->motion.y);
487 const Vec2f prev_position = camera_map_screen(
489 event->motion.x + event->motion.xrel,
490 event->motion.y + event->motion.yrel);
492 vec_add(&level_editor->camera_position,
493 vec_sub(next_position, prev_position));
494 camera_center_at(camera, level_editor->camera_position);
500 bool selected = false;
501 if (layer_picker_event(
502 &level_editor->layer_picker,
511 level_editor->layers[level_editor->layer_picker],
514 &level_editor->undo_history) < 0) {
518 level_editor->click = 1;
525 int level_editor_event(LevelEditor *level_editor,
526 const SDL_Event *event,
529 trace_assert(level_editor);
531 trace_assert(camera);
533 switch (level_editor->state) {
534 case LEVEL_EDITOR_IDLE:
535 return level_editor_idle_event(level_editor, event, camera);
537 case LEVEL_EDITOR_SAVEAS:
538 return level_editor_saveas_event(level_editor, event, camera);
544 int level_editor_focus_camera(LevelEditor *level_editor,
547 camera_center_at(camera, level_editor->camera_position);
548 camera_scale(camera, level_editor->camera_scale);
552 static LayerPicker level_format_layer_order[LAYER_PICKER_N] = {
553 LAYER_PICKER_BACKGROUND,
555 LAYER_PICKER_PLATFORMS,
558 LAYER_PICKER_BACK_PLATFORMS,
564 /* TODO(#904): LevelEditor does not check that the saved level file is modified by external program */
565 static int level_editor_dump(LevelEditor *level_editor)
567 trace_assert(level_editor);
569 FILE *filedump = PUSH_LT(
571 fopen(level_editor->file_name, "w"),
574 if (fprintf(filedump, "%s\n", VERSION) < 0) {
578 for (size_t i = 0; i < LAYER_PICKER_N; ++i) {
579 if (layer_dump_stream(
580 level_editor->layers[level_format_layer_order[i]],
586 fclose(RELEASE_LT(level_editor->lt, filedump));
588 fading_wiggly_text_reset(&level_editor->notice);
589 level_editor->save = 1;
594 int level_editor_update(LevelEditor *level_editor, float delta_time)
596 return fading_wiggly_text_update(&level_editor->notice, delta_time);
599 void level_editor_sound(LevelEditor *level_editor, Sound_samples *sound_samples)
601 trace_assert(sound_samples);
604 if (level_editor->bell) {
605 level_editor->bell = 0;
606 sound_samples_play_sound(sound_samples, 2);
609 if (level_editor->click) {
610 level_editor->click = 0;
611 sound_samples_play_sound(sound_samples, 3);
614 if (level_editor->save) {
615 level_editor->save = 0;
616 sound_samples_play_sound(sound_samples, 4);