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));
381 int level_editor_saveas_event(LevelEditor *level_editor,
382 const SDL_Event *event,
383 const Camera *camera)
385 trace_assert(level_editor);
387 trace_assert(camera);
389 switch (event->type) {
391 if (event->key.keysym.sym == SDLK_RETURN) {
392 trace_assert(level_editor->file_name == NULL);
393 level_editor->file_name = PUSH_LT(
397 level_editor->edit_field_filename),
400 level_editor_dump(level_editor);
402 level_editor->state = LEVEL_EDITOR_IDLE;
408 return edit_field_event(level_editor->edit_field_filename, event);
412 int level_editor_idle_event(LevelEditor *level_editor,
413 const SDL_Event *event,
416 trace_assert(level_editor);
418 trace_assert(camera);
420 switch (event->type) {
422 switch(event->key.keysym.sym) {
424 if (!SDL_IsTextInputActive()) {
425 if (level_editor->file_name) {
426 level_editor_dump(level_editor);
427 log_info("Saving level to `%s`\n", level_editor->file_name);
429 SDL_StartTextInput();
430 level_editor->state = LEVEL_EDITOR_SAVEAS;
436 if (event->key.keysym.mod & KMOD_CTRL) {
437 if (undo_history_empty(&level_editor->undo_history)) {
438 level_editor->bell = 1;
440 undo_history_pop(&level_editor->undo_history);
446 case SDL_MOUSEWHEEL: {
448 SDL_GetMouseState(&x, &y);
450 Vec2f position = camera_map_screen(camera, x, y);
451 if (event->wheel.y > 0) {
452 level_editor->camera_scale += 0.1f;
453 } else if (event->wheel.y < 0) {
454 level_editor->camera_scale = fmaxf(0.1f, level_editor->camera_scale - 0.1f);
456 camera_scale(camera, level_editor->camera_scale);
457 Vec2f zoomed_position = camera_map_screen(camera, x, y);
459 level_editor->camera_position =
461 level_editor->camera_position,
462 vec_sub(position, zoomed_position));
463 camera_center_at(camera, level_editor->camera_position);
466 case SDL_MOUSEBUTTONUP:
467 case SDL_MOUSEBUTTONDOWN: {
468 if (event->type == SDL_MOUSEBUTTONDOWN && event->button.button == SDL_BUTTON_MIDDLE) {
469 level_editor->drag = true;
472 if (event->type == SDL_MOUSEBUTTONUP && event->button.button == SDL_BUTTON_MIDDLE) {
473 level_editor->drag = false;
477 case SDL_MOUSEMOTION: {
478 if (level_editor->drag) {
479 const Vec2f next_position = camera_map_screen(camera, event->motion.x, event->motion.y);
480 const Vec2f prev_position = camera_map_screen(
482 event->motion.x + event->motion.xrel,
483 event->motion.y + event->motion.yrel);
485 vec_add(&level_editor->camera_position,
486 vec_sub(next_position, prev_position));
487 camera_center_at(camera, level_editor->camera_position);
493 bool selected = false;
494 if (layer_picker_event(
495 &level_editor->layer_picker,
504 level_editor->layers[level_editor->layer_picker],
507 &level_editor->undo_history) < 0) {
511 level_editor->click = 1;
518 int level_editor_event(LevelEditor *level_editor,
519 const SDL_Event *event,
522 trace_assert(level_editor);
524 trace_assert(camera);
526 switch (level_editor->state) {
527 case LEVEL_EDITOR_IDLE:
528 return level_editor_idle_event(level_editor, event, camera);
530 case LEVEL_EDITOR_SAVEAS:
531 return level_editor_saveas_event(level_editor, event, camera);
537 int level_editor_focus_camera(LevelEditor *level_editor,
540 camera_center_at(camera, level_editor->camera_position);
541 camera_scale(camera, level_editor->camera_scale);
545 static LayerPicker level_format_layer_order[LAYER_PICKER_N] = {
546 LAYER_PICKER_BACKGROUND,
548 LAYER_PICKER_PLATFORMS,
551 LAYER_PICKER_BACK_PLATFORMS,
557 /* TODO(#904): LevelEditor does not check that the saved level file is modified by external program */
558 static int level_editor_dump(LevelEditor *level_editor)
560 trace_assert(level_editor);
562 FILE *filedump = PUSH_LT(
564 fopen(level_editor->file_name, "w"),
567 if (fprintf(filedump, "%s\n", level_metadata_title(level_editor->metadata)) < 0) {
571 for (size_t i = 0; i < LAYER_PICKER_N; ++i) {
572 if (layer_dump_stream(
573 level_editor->layers[level_format_layer_order[i]],
579 fclose(RELEASE_LT(level_editor->lt, filedump));
581 fading_wiggly_text_reset(&level_editor->notice);
582 level_editor->save = 1;
587 int level_editor_update(LevelEditor *level_editor, float delta_time)
589 return fading_wiggly_text_update(&level_editor->notice, delta_time);
592 void level_editor_sound(LevelEditor *level_editor, Sound_samples *sound_samples)
594 trace_assert(sound_samples);
597 if (level_editor->bell) {
598 level_editor->bell = 0;
599 sound_samples_play_sound(sound_samples, 2);
602 if (level_editor->click) {
603 level_editor->click = 0;
604 sound_samples_play_sound(sound_samples, 3);
607 if (level_editor->save) {
608 level_editor->save = 0;
609 sound_samples_play_sound(sound_samples, 4);