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"
22 #include "math/extrema.h"
24 #include "level_editor.h"
26 #define DEFAULT_LEVEL_TITLE "New Level"
28 #define LEVEL_FOLDER_MAX_LENGTH 512
29 #define LEVEL_LINE_MAX_LENGTH 512
30 #define LEVEL_EDITOR_EDIT_FIELD_SIZE vec(5.0f, 5.0f)
31 #define LEVEL_EDITOR_EDIT_FIELD_COLOR COLOR_BLACK
33 #define LEVEL_EDITOR_NOTICE_SCALE vec(10.0f, 10.0f)
34 #define LEVEL_EDITOR_NOTICE_DURATION 1.0f
35 #define LEVEL_EDITOR_NOTICE_PADDING_TOP 100.0f
37 static int level_editor_dump(LevelEditor *level_editor);
39 // TODO(#994): too much duplicate code between create_level_editor and create_level_editor_from_file
41 LevelEditor *create_level_editor(Cursor *cursor)
44 LevelEditor *level_editor = PUSH_LT(
46 nth_calloc(1, sizeof(LevelEditor)),
48 if (level_editor == NULL) {
51 level_editor->lt = lt;
53 level_editor->edit_field_filename = PUSH_LT(
56 LEVEL_EDITOR_EDIT_FIELD_SIZE,
57 LEVEL_EDITOR_EDIT_FIELD_COLOR),
59 if (level_editor->edit_field_filename == NULL) {
63 memset(level_editor->metadata.version, 0, METADATA_VERSION_MAX_SIZE);
64 memcpy(level_editor->metadata.version,
66 min_size_t(sizeof(VERSION), METADATA_VERSION_MAX_SIZE - 1));
68 memset(level_editor->metadata.title, 0, METADATA_TITLE_MAX_SIZE);
69 memcpy(level_editor->metadata.title,
71 min_size_t(sizeof(DEFAULT_LEVEL_TITLE), METADATA_TITLE_MAX_SIZE - 1));
73 level_editor->background_layer = create_background_layer(hexstr("fffda5"));
75 level_editor->player_layer =
76 create_player_layer(vec(0.0f, 0.0f), hexstr("ff8080"));
78 level_editor->platforms_layer = PUSH_LT(
80 create_rect_layer("platform", cursor),
82 if (level_editor->platforms_layer == NULL) {
86 level_editor->goals_layer = PUSH_LT(
88 create_point_layer("goal"),
90 if (level_editor->goals_layer == NULL) {
94 level_editor->lava_layer = PUSH_LT(
96 create_rect_layer("lava", cursor),
98 if (level_editor->lava_layer == NULL) {
102 level_editor->back_platforms_layer = PUSH_LT(
104 create_rect_layer("back_platform", cursor),
106 if (level_editor->back_platforms_layer == NULL) {
110 level_editor->boxes_layer = PUSH_LT(
112 create_rect_layer("box", cursor),
114 if (level_editor->boxes_layer == NULL) {
118 level_editor->label_layer = PUSH_LT(
120 create_label_layer("label"),
121 destroy_label_layer);
122 if (level_editor->label_layer == NULL) {
126 level_editor->regions_layer = PUSH_LT(
128 create_rect_layer("region", cursor),
130 if (level_editor->regions_layer == NULL) {
134 level_editor->layers[LAYER_PICKER_BOXES] = rect_layer_as_layer(level_editor->boxes_layer);
135 level_editor->layers[LAYER_PICKER_PLATFORMS] = rect_layer_as_layer(level_editor->platforms_layer);
136 level_editor->layers[LAYER_PICKER_BACK_PLATFORMS] = rect_layer_as_layer(level_editor->back_platforms_layer);
137 level_editor->layers[LAYER_PICKER_GOALS] = point_layer_as_layer(level_editor->goals_layer);
138 level_editor->layers[LAYER_PICKER_PLAYER] = player_layer_as_layer(&level_editor->player_layer);
139 level_editor->layers[LAYER_PICKER_LAVA] = rect_layer_as_layer(level_editor->lava_layer);
140 level_editor->layers[LAYER_PICKER_REGIONS] = rect_layer_as_layer(level_editor->regions_layer);
141 level_editor->layers[LAYER_PICKER_BACKGROUND] = background_layer_as_layer(&level_editor->background_layer);
142 level_editor->layers[LAYER_PICKER_LABELS] = label_layer_as_layer(level_editor->label_layer);
144 level_editor->notice = (FadingWigglyText) {
146 .text = "Level saved",
147 .color = rgba(0.0f, 0.0f, 0.0f, 0.0f),
148 .scale = LEVEL_EDITOR_NOTICE_SCALE
150 .duration = LEVEL_EDITOR_NOTICE_DURATION,
153 level_editor->camera_scale = 1.0f;
158 LevelEditor *create_level_editor_from_file(const char *file_name, Cursor *cursor)
160 trace_assert(file_name);
162 Lt *lt = create_lt();
163 LevelEditor *level_editor = PUSH_LT(
165 nth_calloc(1, sizeof(LevelEditor)),
167 if (level_editor == NULL) {
170 level_editor->lt = lt;
172 level_editor->edit_field_filename = PUSH_LT(
175 LEVEL_EDITOR_EDIT_FIELD_SIZE,
176 LEVEL_EDITOR_EDIT_FIELD_COLOR),
178 if (level_editor->edit_field_filename == NULL) {
182 level_editor->file_name =
185 string_duplicate(file_name, NULL),
188 LineStream *level_stream = PUSH_LT(
193 LEVEL_LINE_MAX_LENGTH),
194 destroy_line_stream);
195 if (level_stream == NULL) {
199 if (metadata_load_from_line_stream(&level_editor->metadata, level_stream, level_editor->file_name) < 0) {
203 if (background_layer_read_from_line_stream(
204 &level_editor->background_layer,
209 level_editor->player_layer =
210 create_player_layer_from_line_stream(level_stream);
212 level_editor->platforms_layer =
215 create_rect_layer_from_line_stream(level_stream, "platform", cursor),
217 if (level_editor->platforms_layer == NULL) {
221 level_editor->goals_layer = PUSH_LT(
223 create_point_layer_from_line_stream(level_stream, "goal"),
224 destroy_point_layer);
225 if (level_editor->goals_layer == NULL) {
229 level_editor->lava_layer =
232 create_rect_layer_from_line_stream(level_stream, "lava", cursor),
234 if (level_editor->lava_layer == NULL) {
238 level_editor->back_platforms_layer =
241 create_rect_layer_from_line_stream(level_stream, "back_platform", cursor),
243 if (level_editor->back_platforms_layer == NULL) {
247 level_editor->boxes_layer =
250 create_rect_layer_from_line_stream(level_stream, "box", cursor),
252 if (level_editor->boxes_layer == NULL) {
256 level_editor->label_layer =
259 create_label_layer_from_line_stream(level_stream, "label"),
260 destroy_label_layer);
261 if (level_editor->label_layer == NULL) {
265 level_editor->regions_layer =
268 create_rect_layer_from_line_stream(level_stream, "region", cursor),
270 if (level_editor->regions_layer == NULL) {
274 level_editor->layers[LAYER_PICKER_BOXES] = rect_layer_as_layer(level_editor->boxes_layer);
275 level_editor->layers[LAYER_PICKER_PLATFORMS] = rect_layer_as_layer(level_editor->platforms_layer);
276 level_editor->layers[LAYER_PICKER_BACK_PLATFORMS] = rect_layer_as_layer(level_editor->back_platforms_layer);
277 level_editor->layers[LAYER_PICKER_GOALS] = point_layer_as_layer(level_editor->goals_layer);
278 level_editor->layers[LAYER_PICKER_PLAYER] = player_layer_as_layer(&level_editor->player_layer);
279 level_editor->layers[LAYER_PICKER_LAVA] = rect_layer_as_layer(level_editor->lava_layer);
280 level_editor->layers[LAYER_PICKER_REGIONS] = rect_layer_as_layer(level_editor->regions_layer);
281 level_editor->layers[LAYER_PICKER_BACKGROUND] = background_layer_as_layer(&level_editor->background_layer);
282 level_editor->layers[LAYER_PICKER_LABELS] = label_layer_as_layer(level_editor->label_layer);
284 level_editor->drag = false;
286 level_editor->notice = (FadingWigglyText) {
288 .text = "Level saved",
289 .color = rgba(0.0f, 0.0f, 0.0f, 0.0f),
290 .scale = LEVEL_EDITOR_NOTICE_SCALE
292 .duration = LEVEL_EDITOR_NOTICE_DURATION,
295 level_editor->camera_scale = 1.0f;
300 void destroy_level_editor(LevelEditor *level_editor)
302 trace_assert(level_editor);
303 destroy_undo_history(level_editor->undo_history);
304 RETURN_LT0(level_editor->lt);
307 int level_editor_render(const LevelEditor *level_editor,
308 const Camera *camera)
310 trace_assert(level_editor);
311 trace_assert(camera);
313 if (camera_clear_background(camera, color_picker_rgba(&level_editor->background_layer.color_picker)) < 0) {
317 const Rect world_viewport = camera_view_port(camera);
319 if (PLAYER_DEATH_LEVEL < world_viewport.y + world_viewport.h) {
320 if (camera_fill_rect(
323 world_viewport.x, PLAYER_DEATH_LEVEL,
324 world_viewport.w, world_viewport.h + fmaxf(0.0f, world_viewport.y - PLAYER_DEATH_LEVEL)),
325 LEVEL_EDITOR_DETH_LEVEL_COLOR) < 0) {
330 for (size_t i = 0; i < LAYER_PICKER_N; ++i) {
332 level_editor->layers[i],
334 i == level_editor->layer_picker) < 0) {
339 if (layer_picker_render(&level_editor->layer_picker, camera) < 0) {
343 if (level_editor->state == LEVEL_EDITOR_SAVEAS) {
345 const Vec2f size = LEVEL_EDITOR_EDIT_FIELD_SIZE;
346 const char *save_as_text = "Save as: ";
347 const Vec2f position = vec(200.0f, 200.0f);
348 const float save_as_width =
349 (float) strlen(save_as_text) * FONT_CHAR_WIDTH * size.x;
352 camera_render_text_screen(
355 LEVEL_EDITOR_EDIT_FIELD_SIZE,
356 LEVEL_EDITOR_EDIT_FIELD_COLOR,
359 if (edit_field_render_screen(
360 level_editor->edit_field_filename,
362 vec(position.x + save_as_width, position.y)) < 0) {
367 const Rect screen_viewport = camera_view_port_screen(camera);
368 const Vec2f text_size = fading_wiggly_text_size(&level_editor->notice);
370 fading_wiggly_text_render(
371 &level_editor->notice, camera,
372 vec(screen_viewport.w * 0.5f - text_size.x * 0.5f,
373 LEVEL_EDITOR_NOTICE_PADDING_TOP));
379 int level_editor_saveas_event(LevelEditor *level_editor,
380 const SDL_Event *event,
381 const Camera *camera)
383 trace_assert(level_editor);
385 trace_assert(camera);
387 switch (event->type) {
389 if (event->key.keysym.sym == SDLK_RETURN) {
390 trace_assert(level_editor->file_name == NULL);
391 char path[LEVEL_FOLDER_MAX_LENGTH];
394 LEVEL_FOLDER_MAX_LENGTH,
395 "./assets/levels//%s.txt",
396 edit_field_as_text(level_editor->edit_field_filename));
397 level_editor->file_name = PUSH_LT(
399 string_duplicate(path, NULL),
401 level_editor_dump(level_editor);
403 level_editor->state = LEVEL_EDITOR_IDLE;
409 return edit_field_event(level_editor->edit_field_filename, event);
413 int level_editor_idle_event(LevelEditor *level_editor,
414 const SDL_Event *event,
417 trace_assert(level_editor);
419 trace_assert(camera);
421 switch (event->type) {
423 switch(event->key.keysym.sym) {
425 if (!SDL_IsTextInputActive()) {
426 if (level_editor->file_name) {
427 level_editor_dump(level_editor);
428 log_info("Saving level to `%s`\n", level_editor->file_name);
430 SDL_StartTextInput();
431 level_editor->state = LEVEL_EDITOR_SAVEAS;
437 if (event->key.keysym.mod & KMOD_CTRL) {
438 if (undo_history_empty(&level_editor->undo_history)) {
439 level_editor->bell = 1;
441 undo_history_pop(&level_editor->undo_history);
447 case SDL_MOUSEWHEEL: {
449 SDL_GetMouseState(&x, &y);
451 Vec2f position = camera_map_screen(camera, x, y);
452 if (event->wheel.y > 0) {
453 level_editor->camera_scale += 0.1f;
454 } else if (event->wheel.y < 0) {
455 level_editor->camera_scale = fmaxf(0.1f, level_editor->camera_scale - 0.1f);
457 camera_scale(camera, level_editor->camera_scale);
458 Vec2f zoomed_position = camera_map_screen(camera, x, y);
460 level_editor->camera_position =
462 level_editor->camera_position,
463 vec_sub(position, zoomed_position));
464 camera_center_at(camera, level_editor->camera_position);
467 case SDL_MOUSEBUTTONUP:
468 case SDL_MOUSEBUTTONDOWN: {
469 if (event->type == SDL_MOUSEBUTTONDOWN && event->button.button == SDL_BUTTON_MIDDLE) {
470 level_editor->drag = true;
473 if (event->type == SDL_MOUSEBUTTONUP && event->button.button == SDL_BUTTON_MIDDLE) {
474 level_editor->drag = false;
478 case SDL_MOUSEMOTION: {
479 if (level_editor->drag) {
480 const Vec2f next_position = camera_map_screen(camera, event->motion.x, event->motion.y);
481 const Vec2f prev_position = camera_map_screen(
483 event->motion.x + event->motion.xrel,
484 event->motion.y + event->motion.yrel);
486 vec_add(&level_editor->camera_position,
487 vec_sub(next_position, prev_position));
488 camera_center_at(camera, level_editor->camera_position);
494 bool selected = false;
495 if (layer_picker_event(
496 &level_editor->layer_picker,
505 level_editor->layers[level_editor->layer_picker],
508 &level_editor->undo_history) < 0) {
512 level_editor->click = 1;
519 int level_editor_event(LevelEditor *level_editor,
520 const SDL_Event *event,
523 trace_assert(level_editor);
525 trace_assert(camera);
527 switch (level_editor->state) {
528 case LEVEL_EDITOR_IDLE:
529 return level_editor_idle_event(level_editor, event, camera);
531 case LEVEL_EDITOR_SAVEAS:
532 return level_editor_saveas_event(level_editor, event, camera);
538 int level_editor_focus_camera(LevelEditor *level_editor,
541 camera_center_at(camera, level_editor->camera_position);
542 camera_scale(camera, level_editor->camera_scale);
546 static LayerPicker level_format_layer_order[LAYER_PICKER_N] = {
547 LAYER_PICKER_BACKGROUND,
549 LAYER_PICKER_PLATFORMS,
552 LAYER_PICKER_BACK_PLATFORMS,
558 /* TODO(#904): LevelEditor does not check that the saved level file is modified by external program */
559 static int level_editor_dump(LevelEditor *level_editor)
561 trace_assert(level_editor);
563 FILE *filedump = PUSH_LT(
565 fopen(level_editor->file_name, "w"),
568 if (fprintf(filedump, "%s\n", level_editor->metadata.version) < 0) {
572 if (fprintf(filedump, "%s\n", level_editor->metadata.title) < 0) {
576 for (size_t i = 0; i < LAYER_PICKER_N; ++i) {
577 if (layer_dump_stream(
578 level_editor->layers[level_format_layer_order[i]],
584 fclose(RELEASE_LT(level_editor->lt, filedump));
586 fading_wiggly_text_reset(&level_editor->notice);
587 level_editor->save = 1;
592 int level_editor_update(LevelEditor *level_editor, float delta_time)
594 return fading_wiggly_text_update(&level_editor->notice, delta_time);
597 void level_editor_sound(LevelEditor *level_editor, Sound_samples *sound_samples)
599 trace_assert(sound_samples);
602 if (level_editor->bell) {
603 level_editor->bell = 0;
604 sound_samples_play_sound(sound_samples, 2);
607 if (level_editor->click) {
608 level_editor->click = 0;
609 sound_samples_play_sound(sound_samples, 3);
612 if (level_editor->save) {
613 level_editor->save = 0;
614 sound_samples_play_sound(sound_samples, 4);