3 #include "game/camera.h"
4 #include "game/level_metadata.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"
20 #include "level_editor.h"
22 #define LEVEL_LINE_MAX_LENGTH 512
23 #define LEVEL_EDITOR_EDIT_FIELD_SIZE vec(5.0f, 5.0f)
24 #define LEVEL_EDITOR_EDIT_FIELD_COLOR COLOR_BLACK
26 #define LEVEL_EDITOR_NOTICE_SCALE vec(10.0f, 10.0f)
27 #define LEVEL_EDITOR_NOTICE_DURATION 1.0f
28 #define LEVEL_EDITOR_NOTICE_PADDING_TOP 100.0f
30 static int level_editor_dump(LevelEditor *level_editor);
32 // TODO(#994): too much duplicate code between create_level_editor and create_level_editor_from_file
34 LevelEditor *create_level_editor(void)
37 LevelEditor *level_editor = PUSH_LT(
39 nth_calloc(1, sizeof(LevelEditor)),
41 if (level_editor == NULL) {
44 level_editor->lt = lt;
46 level_editor->edit_field_filename = PUSH_LT(
49 LEVEL_EDITOR_EDIT_FIELD_SIZE,
50 LEVEL_EDITOR_EDIT_FIELD_COLOR),
52 if (level_editor->edit_field_filename == NULL) {
56 level_editor->metadata = PUSH_LT(
58 create_level_metadata("New Level"),
59 destroy_level_metadata);
60 if (level_editor->metadata == NULL) {
64 level_editor->background_layer = create_background_layer(hexstr("fffda5"));
66 level_editor->player_layer =
67 create_player_layer(vec(0.0f, 0.0f), hexstr("ff8080"));
69 level_editor->platforms_layer = PUSH_LT(
71 create_rect_layer("platform"),
73 if (level_editor->platforms_layer == NULL) {
77 level_editor->goals_layer = PUSH_LT(
79 create_point_layer("goal"),
81 if (level_editor->goals_layer == NULL) {
85 level_editor->lava_layer = PUSH_LT(
87 create_rect_layer("lava"),
89 if (level_editor->lava_layer == NULL) {
93 level_editor->back_platforms_layer = PUSH_LT(
95 create_rect_layer("back_platform"),
97 if (level_editor->back_platforms_layer == NULL) {
101 level_editor->boxes_layer = PUSH_LT(
103 create_rect_layer("box"),
105 if (level_editor->boxes_layer == NULL) {
109 level_editor->label_layer = PUSH_LT(
111 create_label_layer("label"),
112 destroy_label_layer);
113 if (level_editor->label_layer == NULL) {
117 level_editor->regions_layer = PUSH_LT(
119 create_rect_layer("region"),
121 if (level_editor->regions_layer == NULL) {
125 level_editor->supa_script_source =
126 PUSH_LT(lt, string_duplicate("", NULL), free);
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->supa_script_source =
275 line_stream_collect_until_end(level_stream),
277 if (level_editor->supa_script_source == NULL) {
281 level_editor->layers[LAYER_PICKER_BOXES] = rect_layer_as_layer(level_editor->boxes_layer);
282 level_editor->layers[LAYER_PICKER_PLATFORMS] = rect_layer_as_layer(level_editor->platforms_layer);
283 level_editor->layers[LAYER_PICKER_BACK_PLATFORMS] = rect_layer_as_layer(level_editor->back_platforms_layer);
284 level_editor->layers[LAYER_PICKER_GOALS] = point_layer_as_layer(level_editor->goals_layer);
285 level_editor->layers[LAYER_PICKER_PLAYER] = player_layer_as_layer(&level_editor->player_layer);
286 level_editor->layers[LAYER_PICKER_LAVA] = rect_layer_as_layer(level_editor->lava_layer);
287 level_editor->layers[LAYER_PICKER_REGIONS] = rect_layer_as_layer(level_editor->regions_layer);
288 level_editor->layers[LAYER_PICKER_BACKGROUND] = background_layer_as_layer(&level_editor->background_layer);
289 level_editor->layers[LAYER_PICKER_LABELS] = label_layer_as_layer(level_editor->label_layer);
291 level_editor->drag = false;
293 level_editor->notice = (FadingWigglyText) {
295 .text = "Level saved",
296 .color = rgba(0.0f, 0.0f, 0.0f, 0.0f),
297 .scale = LEVEL_EDITOR_NOTICE_SCALE
299 .duration = LEVEL_EDITOR_NOTICE_DURATION,
302 level_editor->camera_scale = 1.0f;
307 void destroy_level_editor(LevelEditor *level_editor)
309 trace_assert(level_editor);
310 destroy_undo_history(level_editor->undo_history);
311 RETURN_LT0(level_editor->lt);
314 int level_editor_render(const LevelEditor *level_editor,
315 const Camera *camera)
317 trace_assert(level_editor);
318 trace_assert(camera);
320 if (camera_clear_background(camera, color_picker_rgba(&level_editor->background_layer.color_picker)) < 0) {
324 for (size_t i = 0; i < LAYER_PICKER_N; ++i) {
326 level_editor->layers[i],
328 i == level_editor->layer_picker) < 0) {
333 if (layer_picker_render(&level_editor->layer_picker, camera) < 0) {
337 if (level_editor->state == LEVEL_EDITOR_SAVEAS) {
339 const Point size = LEVEL_EDITOR_EDIT_FIELD_SIZE;
340 const char *save_as_text = "Save as: ";
341 const Point position = vec(200.0f, 200.0f);
342 const float save_as_width =
343 (float) strlen(save_as_text) * FONT_CHAR_WIDTH * size.x;
346 if (camera_render_text_screen(
349 LEVEL_EDITOR_EDIT_FIELD_SIZE,
350 LEVEL_EDITOR_EDIT_FIELD_COLOR,
355 if (edit_field_render_screen(
356 level_editor->edit_field_filename,
358 vec(position.x + save_as_width, position.y)) < 0) {
363 const Rect viewport = camera_view_port_screen(camera);
364 const Vec text_size = fading_wiggly_text_size(
365 &level_editor->notice,
368 fading_wiggly_text_render(
369 &level_editor->notice, camera,
370 vec(viewport.w * 0.5f - text_size.x * 0.5f,
371 LEVEL_EDITOR_NOTICE_PADDING_TOP));
377 int level_editor_saveas_event(LevelEditor *level_editor,
378 const SDL_Event *event,
379 const Camera *camera)
381 trace_assert(level_editor);
383 trace_assert(camera);
385 switch (event->type) {
387 if (event->key.keysym.sym == SDLK_RETURN) {
388 trace_assert(level_editor->file_name == NULL);
389 level_editor->file_name = PUSH_LT(
393 level_editor->edit_field_filename),
396 level_editor_dump(level_editor);
398 level_editor->state = LEVEL_EDITOR_IDLE;
404 return edit_field_event(level_editor->edit_field_filename, event);
408 int level_editor_idle_event(LevelEditor *level_editor,
409 const SDL_Event *event,
412 trace_assert(level_editor);
414 trace_assert(camera);
416 switch (event->type) {
418 switch(event->key.keysym.sym) {
420 if (!SDL_IsTextInputActive()) {
421 if (level_editor->file_name) {
422 level_editor_dump(level_editor);
423 log_info("Saving level to `%s`\n", level_editor->file_name);
425 SDL_StartTextInput();
426 level_editor->state = LEVEL_EDITOR_SAVEAS;
432 if (event->key.keysym.mod & KMOD_CTRL) {
434 undo_history_pop(&level_editor->undo_history);
440 case SDL_MOUSEWHEEL: {
442 SDL_GetMouseState(&x, &y);
444 Vec position = camera_map_screen(camera, x, y);
445 if (event->wheel.y > 0) {
446 level_editor->camera_scale += 0.1f;
447 } else if (event->wheel.y < 0) {
448 level_editor->camera_scale = fmaxf(0.1f, level_editor->camera_scale - 0.1f);
450 camera_scale(camera, level_editor->camera_scale);
451 Vec zoomed_position = camera_map_screen(camera, x, y);
453 level_editor->camera_position =
455 level_editor->camera_position,
456 vec_sub(position, zoomed_position));
457 camera_center_at(camera, level_editor->camera_position);
460 case SDL_MOUSEBUTTONUP:
461 case SDL_MOUSEBUTTONDOWN: {
462 if (event->type == SDL_MOUSEBUTTONDOWN && event->button.button == SDL_BUTTON_MIDDLE) {
463 level_editor->drag = true;
466 if (event->type == SDL_MOUSEBUTTONUP && event->button.button == SDL_BUTTON_MIDDLE) {
467 level_editor->drag = false;
471 case SDL_MOUSEMOTION: {
472 if (level_editor->drag) {
473 const Vec next_position = camera_map_screen(camera, event->motion.x, event->motion.y);
474 const Vec prev_position = camera_map_screen(
476 event->motion.x + event->motion.xrel,
477 event->motion.y + event->motion.yrel);
479 vec_add(&level_editor->camera_position,
480 vec_sub(next_position, prev_position));
481 camera_center_at(camera, level_editor->camera_position);
487 bool selected = false;
488 if (layer_picker_event(
489 &level_editor->layer_picker,
498 level_editor->layers[level_editor->layer_picker],
501 &level_editor->undo_history) < 0) {
510 int level_editor_event(LevelEditor *level_editor,
511 const SDL_Event *event,
514 trace_assert(level_editor);
516 trace_assert(camera);
518 switch (level_editor->state) {
519 case LEVEL_EDITOR_IDLE:
520 return level_editor_idle_event(level_editor, event, camera);
522 case LEVEL_EDITOR_SAVEAS:
523 return level_editor_saveas_event(level_editor, event, camera);
529 int level_editor_focus_camera(LevelEditor *level_editor,
532 camera_center_at(camera, level_editor->camera_position);
533 camera_scale(camera, level_editor->camera_scale);
537 /* TODO(#904): LevelEditor does not check that the saved level file is modified by external program */
538 static int level_editor_dump(LevelEditor *level_editor)
540 trace_assert(level_editor);
542 FILE *filedump = PUSH_LT(
544 fopen(level_editor->file_name, "w"),
547 if (fprintf(filedump, "%s\n", level_metadata_title(level_editor->metadata)) < 0) {
551 for (size_t i = 0; i < LAYER_PICKER_N; ++i) {
552 if (layer_dump_stream(
553 level_editor->layers[i],
559 fprintf(filedump, "%s", level_editor->supa_script_source);
561 fclose(RELEASE_LT(level_editor->lt, filedump));
563 fading_wiggly_text_reset(&level_editor->notice);
568 int level_editor_update(LevelEditor *level_editor, float delta_time)
570 return fading_wiggly_text_update(&level_editor->notice, delta_time);