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"
23 #include "level_editor.h"
25 #define LEVEL_LINE_MAX_LENGTH 512
26 #define LEVEL_EDITOR_EDIT_FIELD_SIZE vec(5.0f, 5.0f)
27 #define LEVEL_EDITOR_EDIT_FIELD_COLOR COLOR_BLACK
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
33 static int level_editor_dump(LevelEditor *level_editor);
35 // TODO(#994): too much duplicate code between create_level_editor and create_level_editor_from_file
37 LevelEditor *create_level_editor(void)
40 LevelEditor *level_editor = PUSH_LT(
42 nth_calloc(1, sizeof(LevelEditor)),
44 if (level_editor == NULL) {
47 level_editor->lt = lt;
49 level_editor->edit_field_filename = PUSH_LT(
52 LEVEL_EDITOR_EDIT_FIELD_SIZE,
53 LEVEL_EDITOR_EDIT_FIELD_COLOR),
55 if (level_editor->edit_field_filename == NULL) {
59 level_editor->metadata = PUSH_LT(
61 create_level_metadata("New Level"),
62 destroy_level_metadata);
63 if (level_editor->metadata == NULL) {
67 level_editor->background_layer = create_background_layer(hexstr("fffda5"));
69 level_editor->player_layer =
70 create_player_layer(vec(0.0f, 0.0f), hexstr("ff8080"));
72 level_editor->platforms_layer = PUSH_LT(
74 create_rect_layer("platform"),
76 if (level_editor->platforms_layer == NULL) {
80 level_editor->goals_layer = PUSH_LT(
82 create_point_layer("goal"),
84 if (level_editor->goals_layer == NULL) {
88 level_editor->lava_layer = PUSH_LT(
90 create_rect_layer("lava"),
92 if (level_editor->lava_layer == NULL) {
96 level_editor->back_platforms_layer = PUSH_LT(
98 create_rect_layer("back_platform"),
100 if (level_editor->back_platforms_layer == NULL) {
104 level_editor->boxes_layer = PUSH_LT(
106 create_rect_layer("box"),
108 if (level_editor->boxes_layer == NULL) {
112 level_editor->label_layer = PUSH_LT(
114 create_label_layer("label"),
115 destroy_label_layer);
116 if (level_editor->label_layer == NULL) {
120 level_editor->regions_layer = PUSH_LT(
122 create_rect_layer("region"),
124 if (level_editor->regions_layer == NULL) {
128 level_editor->layers[LAYER_PICKER_BOXES] = rect_layer_as_layer(level_editor->boxes_layer);
129 level_editor->layers[LAYER_PICKER_PLATFORMS] = rect_layer_as_layer(level_editor->platforms_layer);
130 level_editor->layers[LAYER_PICKER_BACK_PLATFORMS] = rect_layer_as_layer(level_editor->back_platforms_layer);
131 level_editor->layers[LAYER_PICKER_GOALS] = point_layer_as_layer(level_editor->goals_layer);
132 level_editor->layers[LAYER_PICKER_PLAYER] = player_layer_as_layer(&level_editor->player_layer);
133 level_editor->layers[LAYER_PICKER_LAVA] = rect_layer_as_layer(level_editor->lava_layer);
134 level_editor->layers[LAYER_PICKER_REGIONS] = rect_layer_as_layer(level_editor->regions_layer);
135 level_editor->layers[LAYER_PICKER_BACKGROUND] = background_layer_as_layer(&level_editor->background_layer);
136 level_editor->layers[LAYER_PICKER_LABELS] = label_layer_as_layer(level_editor->label_layer);
138 level_editor->notice = (FadingWigglyText) {
140 .text = "Level saved",
141 .color = rgba(0.0f, 0.0f, 0.0f, 0.0f),
142 .scale = LEVEL_EDITOR_NOTICE_SCALE
144 .duration = LEVEL_EDITOR_NOTICE_DURATION,
147 level_editor->camera_scale = 1.0f;
152 LevelEditor *create_level_editor_from_file(const char *file_name)
154 trace_assert(file_name);
156 Lt *lt = create_lt();
157 LevelEditor *level_editor = PUSH_LT(
159 nth_calloc(1, sizeof(LevelEditor)),
161 if (level_editor == NULL) {
164 level_editor->lt = lt;
166 level_editor->edit_field_filename = PUSH_LT(
169 LEVEL_EDITOR_EDIT_FIELD_SIZE,
170 LEVEL_EDITOR_EDIT_FIELD_COLOR),
172 if (level_editor->edit_field_filename == NULL) {
176 level_editor->file_name =
179 string_duplicate(file_name, NULL),
182 LineStream *level_stream = PUSH_LT(
187 LEVEL_LINE_MAX_LENGTH),
188 destroy_line_stream);
189 if (level_stream == NULL) {
193 level_editor->metadata = PUSH_LT(
195 create_level_metadata_from_line_stream(level_stream),
196 destroy_level_metadata);
197 if (level_editor->metadata == NULL) {
201 if (background_layer_read_from_line_stream(
202 &level_editor->background_layer,
207 level_editor->player_layer =
208 create_player_layer_from_line_stream(level_stream);
210 level_editor->platforms_layer =
213 create_rect_layer_from_line_stream(level_stream, "platform"),
215 if (level_editor->platforms_layer == NULL) {
219 level_editor->goals_layer = PUSH_LT(
221 create_point_layer_from_line_stream(level_stream, "goal"),
222 destroy_point_layer);
223 if (level_editor->goals_layer == NULL) {
227 level_editor->lava_layer =
230 create_rect_layer_from_line_stream(level_stream, "lava"),
232 if (level_editor->lava_layer == NULL) {
236 level_editor->back_platforms_layer =
239 create_rect_layer_from_line_stream(level_stream, "back_platform"),
241 if (level_editor->back_platforms_layer == NULL) {
245 level_editor->boxes_layer =
248 create_rect_layer_from_line_stream(level_stream, "box"),
250 if (level_editor->boxes_layer == NULL) {
254 level_editor->label_layer =
257 create_label_layer_from_line_stream(level_stream, "label"),
258 destroy_label_layer);
259 if (level_editor->label_layer == NULL) {
263 level_editor->regions_layer =
266 create_rect_layer_from_line_stream(level_stream, "region"),
268 if (level_editor->regions_layer == NULL) {
272 level_editor->layers[LAYER_PICKER_BOXES] = rect_layer_as_layer(level_editor->boxes_layer);
273 level_editor->layers[LAYER_PICKER_PLATFORMS] = rect_layer_as_layer(level_editor->platforms_layer);
274 level_editor->layers[LAYER_PICKER_BACK_PLATFORMS] = rect_layer_as_layer(level_editor->back_platforms_layer);
275 level_editor->layers[LAYER_PICKER_GOALS] = point_layer_as_layer(level_editor->goals_layer);
276 level_editor->layers[LAYER_PICKER_PLAYER] = player_layer_as_layer(&level_editor->player_layer);
277 level_editor->layers[LAYER_PICKER_LAVA] = rect_layer_as_layer(level_editor->lava_layer);
278 level_editor->layers[LAYER_PICKER_REGIONS] = rect_layer_as_layer(level_editor->regions_layer);
279 level_editor->layers[LAYER_PICKER_BACKGROUND] = background_layer_as_layer(&level_editor->background_layer);
280 level_editor->layers[LAYER_PICKER_LABELS] = label_layer_as_layer(level_editor->label_layer);
282 level_editor->drag = false;
284 level_editor->notice = (FadingWigglyText) {
286 .text = "Level saved",
287 .color = rgba(0.0f, 0.0f, 0.0f, 0.0f),
288 .scale = LEVEL_EDITOR_NOTICE_SCALE
290 .duration = LEVEL_EDITOR_NOTICE_DURATION,
293 level_editor->camera_scale = 1.0f;
298 void destroy_level_editor(LevelEditor *level_editor)
300 trace_assert(level_editor);
301 destroy_undo_history(level_editor->undo_history);
302 RETURN_LT0(level_editor->lt);
305 int level_editor_render(const LevelEditor *level_editor,
306 const Camera *camera)
308 trace_assert(level_editor);
309 trace_assert(camera);
311 if (camera_clear_background(camera, color_picker_rgba(&level_editor->background_layer.color_picker)) < 0) {
315 const Rect world_viewport = camera_view_port(camera);
317 if (PLAYER_DEATH_LEVEL < world_viewport.y + world_viewport.h) {
318 if (camera_fill_rect(
321 world_viewport.x, PLAYER_DEATH_LEVEL,
322 world_viewport.w, world_viewport.h + fmaxf(0.0f, world_viewport.y - PLAYER_DEATH_LEVEL)),
323 LEVEL_EDITOR_DETH_LEVEL_COLOR) < 0) {
328 for (size_t i = 0; i < LAYER_PICKER_N; ++i) {
330 level_editor->layers[i],
332 i == level_editor->layer_picker) < 0) {
337 if (layer_picker_render(&level_editor->layer_picker, camera) < 0) {
341 if (level_editor->state == LEVEL_EDITOR_SAVEAS) {
343 const Vec2f size = LEVEL_EDITOR_EDIT_FIELD_SIZE;
344 const char *save_as_text = "Save as: ";
345 const Vec2f position = vec(200.0f, 200.0f);
346 const float save_as_width =
347 (float) strlen(save_as_text) * FONT_CHAR_WIDTH * size.x;
350 if (camera_render_text_screen(
353 LEVEL_EDITOR_EDIT_FIELD_SIZE,
354 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(
369 &level_editor->notice,
372 fading_wiggly_text_render(
373 &level_editor->notice, camera,
374 vec(screen_viewport.w * 0.5f - text_size.x * 0.5f,
375 LEVEL_EDITOR_NOTICE_PADDING_TOP));
377 action_picker_render(
378 &level_editor->action_picker,
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 level_editor->file_name = PUSH_LT(
401 level_editor->edit_field_filename),
404 level_editor_dump(level_editor);
406 level_editor->state = LEVEL_EDITOR_IDLE;
412 return edit_field_event(level_editor->edit_field_filename, event);
416 int level_editor_idle_event(LevelEditor *level_editor,
417 const SDL_Event *event,
420 trace_assert(level_editor);
422 trace_assert(camera);
424 switch (event->type) {
426 switch(event->key.keysym.sym) {
428 if (!SDL_IsTextInputActive()) {
429 if (level_editor->file_name) {
430 level_editor_dump(level_editor);
431 log_info("Saving level to `%s`\n", level_editor->file_name);
433 SDL_StartTextInput();
434 level_editor->state = LEVEL_EDITOR_SAVEAS;
440 if (event->key.keysym.mod & KMOD_CTRL) {
441 if (undo_history_empty(&level_editor->undo_history)) {
442 level_editor->bell = 1;
444 undo_history_pop(&level_editor->undo_history);
450 case SDL_MOUSEWHEEL: {
452 SDL_GetMouseState(&x, &y);
454 Vec2f position = camera_map_screen(camera, x, y);
455 if (event->wheel.y > 0) {
456 level_editor->camera_scale += 0.1f;
457 } else if (event->wheel.y < 0) {
458 level_editor->camera_scale = fmaxf(0.1f, level_editor->camera_scale - 0.1f);
460 camera_scale(camera, level_editor->camera_scale);
461 Vec2f zoomed_position = camera_map_screen(camera, x, y);
463 level_editor->camera_position =
465 level_editor->camera_position,
466 vec_sub(position, zoomed_position));
467 camera_center_at(camera, level_editor->camera_position);
470 case SDL_MOUSEBUTTONUP:
471 case SDL_MOUSEBUTTONDOWN: {
472 if (event->type == SDL_MOUSEBUTTONDOWN && event->button.button == SDL_BUTTON_MIDDLE) {
473 level_editor->drag = true;
476 if (event->type == SDL_MOUSEBUTTONUP && event->button.button == SDL_BUTTON_MIDDLE) {
477 level_editor->drag = false;
481 case SDL_MOUSEMOTION: {
482 if (level_editor->drag) {
483 const Vec2f next_position = camera_map_screen(camera, event->motion.x, event->motion.y);
484 const Vec2f prev_position = camera_map_screen(
486 event->motion.x + event->motion.xrel,
487 event->motion.y + event->motion.yrel);
489 vec_add(&level_editor->camera_position,
490 vec_sub(next_position, prev_position));
491 camera_center_at(camera, level_editor->camera_position);
497 bool selected = false;
498 if (layer_picker_event(
499 &level_editor->layer_picker,
508 level_editor->layers[level_editor->layer_picker],
511 &level_editor->undo_history) < 0) {
515 level_editor->click = 1;
522 int level_editor_event(LevelEditor *level_editor,
523 const SDL_Event *event,
526 trace_assert(level_editor);
528 trace_assert(camera);
530 level_editor->action_picker.position = vec(400.0f, 200.0f);
531 action_picker_event(&level_editor->action_picker, event);
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", level_metadata_title(level_editor->metadata)) < 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);