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"
22 #include "system/file.h"
24 #include "level_editor.h"
26 #define LEVEL_FOLDER_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
33 #define LEVEL_EDITOR_TMPMEM_CAPACITY (640 * KILO)
35 static int level_editor_dump(LevelEditor *level_editor);
37 // TODO(#994): too much duplicate code between create_level_editor and create_level_editor_from_file
39 LevelEditor *create_level_editor(Cursor *cursor)
42 LevelEditor *level_editor = PUSH_LT(
44 nth_calloc(1, sizeof(LevelEditor)),
46 if (level_editor == NULL) {
49 level_editor->lt = lt;
51 level_editor->edit_field_filename = PUSH_LT(
54 LEVEL_EDITOR_EDIT_FIELD_SIZE,
55 LEVEL_EDITOR_EDIT_FIELD_COLOR),
57 if (level_editor->edit_field_filename == NULL) {
61 level_editor->background_layer = create_background_layer(hexstr("fffda5"));
63 level_editor->player_layer =
64 create_player_layer(vec(0.0f, 0.0f), hexstr("ff8080"));
66 level_editor->platforms_layer = PUSH_LT(
68 create_rect_layer("platform", cursor),
70 if (level_editor->platforms_layer == NULL) {
74 level_editor->goals_layer = PUSH_LT(
76 create_point_layer("goal"),
78 if (level_editor->goals_layer == NULL) {
82 level_editor->lava_layer = PUSH_LT(
84 create_rect_layer("lava", cursor),
86 if (level_editor->lava_layer == NULL) {
90 level_editor->back_platforms_layer = PUSH_LT(
92 create_rect_layer("back_platform", cursor),
94 if (level_editor->back_platforms_layer == NULL) {
98 level_editor->boxes_layer = PUSH_LT(
100 create_rect_layer("box", cursor),
102 if (level_editor->boxes_layer == NULL) {
106 level_editor->label_layer = PUSH_LT(
108 create_label_layer("label"),
109 destroy_label_layer);
110 if (level_editor->label_layer == NULL) {
114 level_editor->regions_layer = PUSH_LT(
116 create_rect_layer("region", cursor),
118 if (level_editor->regions_layer == NULL) {
122 level_editor->layers[LAYER_PICKER_BOXES] = rect_layer_as_layer(level_editor->boxes_layer);
123 level_editor->layers[LAYER_PICKER_PLATFORMS] = rect_layer_as_layer(level_editor->platforms_layer);
124 level_editor->layers[LAYER_PICKER_BACK_PLATFORMS] = rect_layer_as_layer(level_editor->back_platforms_layer);
125 level_editor->layers[LAYER_PICKER_GOALS] = point_layer_as_layer(level_editor->goals_layer);
126 level_editor->layers[LAYER_PICKER_PLAYER] = player_layer_as_layer(&level_editor->player_layer);
127 level_editor->layers[LAYER_PICKER_LAVA] = rect_layer_as_layer(level_editor->lava_layer);
128 level_editor->layers[LAYER_PICKER_REGIONS] = rect_layer_as_layer(level_editor->regions_layer);
129 level_editor->layers[LAYER_PICKER_BACKGROUND] = background_layer_as_layer(&level_editor->background_layer);
130 level_editor->layers[LAYER_PICKER_LABELS] = label_layer_as_layer(level_editor->label_layer);
132 level_editor->notice = (FadingWigglyText) {
134 .text = "Level saved",
135 .color = rgba(0.0f, 0.0f, 0.0f, 0.0f),
136 .scale = LEVEL_EDITOR_NOTICE_SCALE
138 .duration = LEVEL_EDITOR_NOTICE_DURATION,
141 level_editor->camera_scale = 1.0f;
143 level_editor->undo_history = create_undo_history();
148 LevelEditor *create_level_editor_from_file(const char *file_name, Cursor *cursor)
150 trace_assert(file_name);
152 Lt *lt = create_lt();
153 LevelEditor *level_editor = PUSH_LT(
155 nth_calloc(1, sizeof(LevelEditor)),
157 if (level_editor == NULL) {
160 level_editor->lt = lt;
162 level_editor->edit_field_filename = PUSH_LT(
165 LEVEL_EDITOR_EDIT_FIELD_SIZE,
166 LEVEL_EDITOR_EDIT_FIELD_COLOR),
168 if (level_editor->edit_field_filename == NULL) {
172 level_editor->file_name =
175 string_duplicate(file_name, NULL),
179 .capacity = LEVEL_EDITOR_TMPMEM_CAPACITY,
180 .buffer = malloc(LEVEL_EDITOR_TMPMEM_CAPACITY),
182 trace_assert(tmpmem.buffer);
184 String input = read_whole_file(&tmpmem, file_name);
185 trace_assert(input.data);
187 String version = trim(chop_by_delim(&input, '\n'));
189 if (string_equal(version, STRING_LIT("1"))) {
190 chop_by_delim(&input, '\n');
191 } else if (string_equal(version, STRING_LIT("2"))) {
194 log_fail("Version `%s` is not supported. Expected version `%s`.\n",
195 string_to_cstr(&tmpmem, version),
200 level_editor->background_layer = chop_background_layer(&input);
201 level_editor->player_layer = chop_player_layer(&tmpmem, &input);
202 level_editor->platforms_layer = chop_rect_layer(&tmpmem, &input, "platform", cursor);
203 level_editor->goals_layer = chop_point_layer(&tmpmem, &input, "goal");
204 level_editor->lava_layer = chop_rect_layer(&tmpmem, &input, "lava", cursor);
205 level_editor->back_platforms_layer = chop_rect_layer(&tmpmem, &input, "back_platform", cursor);
206 level_editor->boxes_layer = chop_rect_layer(&tmpmem, &input, "box", cursor);
207 level_editor->label_layer = chop_label_layer(&tmpmem, &input, "label");
208 level_editor->regions_layer = chop_rect_layer(&tmpmem, &input, "region", cursor),
210 level_editor->layers[LAYER_PICKER_BOXES] = rect_layer_as_layer(level_editor->boxes_layer);
211 level_editor->layers[LAYER_PICKER_PLATFORMS] = rect_layer_as_layer(level_editor->platforms_layer);
212 level_editor->layers[LAYER_PICKER_BACK_PLATFORMS] = rect_layer_as_layer(level_editor->back_platforms_layer);
213 level_editor->layers[LAYER_PICKER_GOALS] = point_layer_as_layer(level_editor->goals_layer);
214 level_editor->layers[LAYER_PICKER_PLAYER] = player_layer_as_layer(&level_editor->player_layer);
215 level_editor->layers[LAYER_PICKER_LAVA] = rect_layer_as_layer(level_editor->lava_layer);
216 level_editor->layers[LAYER_PICKER_REGIONS] = rect_layer_as_layer(level_editor->regions_layer);
217 level_editor->layers[LAYER_PICKER_BACKGROUND] = background_layer_as_layer(&level_editor->background_layer);
218 level_editor->layers[LAYER_PICKER_LABELS] = label_layer_as_layer(level_editor->label_layer);
220 level_editor->drag = false;
222 level_editor->notice = (FadingWigglyText) {
224 .text = "Level saved",
225 .color = rgba(0.0f, 0.0f, 0.0f, 0.0f),
226 .scale = LEVEL_EDITOR_NOTICE_SCALE
228 .duration = LEVEL_EDITOR_NOTICE_DURATION,
231 level_editor->camera_scale = 1.0f;
233 level_editor->undo_history = create_undo_history();
240 void destroy_level_editor(LevelEditor *level_editor)
242 trace_assert(level_editor);
243 destroy_undo_history(level_editor->undo_history);
244 RETURN_LT0(level_editor->lt);
247 int level_editor_render(const LevelEditor *level_editor,
248 const Camera *camera)
250 trace_assert(level_editor);
251 trace_assert(camera);
253 if (camera_clear_background(camera, color_picker_rgba(&level_editor->background_layer.color_picker)) < 0) {
257 const Rect world_viewport = camera_view_port(camera);
259 if (PLAYER_DEATH_LEVEL < world_viewport.y + world_viewport.h) {
260 if (camera_fill_rect(
263 world_viewport.x, PLAYER_DEATH_LEVEL,
264 world_viewport.w, world_viewport.h + fmaxf(0.0f, world_viewport.y - PLAYER_DEATH_LEVEL)),
265 LEVEL_EDITOR_DETH_LEVEL_COLOR) < 0) {
270 for (size_t i = 0; i < LAYER_PICKER_N; ++i) {
272 level_editor->layers[i],
274 i == level_editor->layer_picker) < 0) {
279 if (layer_picker_render(&level_editor->layer_picker, camera) < 0) {
283 if (level_editor->state == LEVEL_EDITOR_SAVEAS) {
285 const Vec2f size = LEVEL_EDITOR_EDIT_FIELD_SIZE;
286 const char *save_as_text = "Save as: ";
287 const Vec2f position = vec(200.0f, 200.0f);
288 const float save_as_width =
289 (float) strlen(save_as_text) * FONT_CHAR_WIDTH * size.x;
292 camera_render_text_screen(
295 LEVEL_EDITOR_EDIT_FIELD_SIZE,
296 LEVEL_EDITOR_EDIT_FIELD_COLOR,
299 if (edit_field_render_screen(
300 level_editor->edit_field_filename,
302 vec(position.x + save_as_width, position.y)) < 0) {
307 const Rect screen_viewport = camera_view_port_screen(camera);
308 const Vec2f text_size = fading_wiggly_text_size(&level_editor->notice);
310 fading_wiggly_text_render(
311 &level_editor->notice, camera,
312 vec(screen_viewport.w * 0.5f - text_size.x * 0.5f,
313 LEVEL_EDITOR_NOTICE_PADDING_TOP));
319 int level_editor_saveas_event(LevelEditor *level_editor,
320 const SDL_Event *event,
321 const Camera *camera)
323 trace_assert(level_editor);
325 trace_assert(camera);
327 switch (event->type) {
329 if (event->key.keysym.sym == SDLK_RETURN) {
330 trace_assert(level_editor->file_name == NULL);
331 char path[LEVEL_FOLDER_MAX_LENGTH];
334 LEVEL_FOLDER_MAX_LENGTH,
335 "./assets/levels/%s.txt",
336 edit_field_as_text(level_editor->edit_field_filename));
337 level_editor->file_name = PUSH_LT(
339 string_duplicate(path, NULL),
341 level_editor_dump(level_editor);
343 level_editor->state = LEVEL_EDITOR_IDLE;
349 return edit_field_event(level_editor->edit_field_filename, event);
353 int level_editor_idle_event(LevelEditor *level_editor,
354 const SDL_Event *event,
357 trace_assert(level_editor);
359 trace_assert(camera);
361 switch (event->type) {
363 switch(event->key.keysym.sym) {
365 if (!SDL_IsTextInputActive()) {
366 if (level_editor->file_name) {
367 level_editor_dump(level_editor);
368 log_info("Saving level to `%s`\n", level_editor->file_name);
370 SDL_StartTextInput();
371 level_editor->state = LEVEL_EDITOR_SAVEAS;
377 if (event->key.keysym.mod & KMOD_CTRL) {
378 if (undo_history_empty(&level_editor->undo_history)) {
379 level_editor->bell = 1;
381 undo_history_pop(&level_editor->undo_history);
387 case SDL_MOUSEWHEEL: {
389 SDL_GetMouseState(&x, &y);
391 Vec2f position = camera_map_screen(camera, x, y);
392 if (event->wheel.y > 0) {
393 level_editor->camera_scale += 0.1f;
394 } else if (event->wheel.y < 0) {
395 level_editor->camera_scale = fmaxf(0.1f, level_editor->camera_scale - 0.1f);
397 camera_scale(camera, level_editor->camera_scale);
398 Vec2f zoomed_position = camera_map_screen(camera, x, y);
400 level_editor->camera_position =
402 level_editor->camera_position,
403 vec_sub(position, zoomed_position));
404 camera_center_at(camera, level_editor->camera_position);
407 case SDL_MOUSEBUTTONUP:
408 case SDL_MOUSEBUTTONDOWN: {
409 if (event->type == SDL_MOUSEBUTTONDOWN && event->button.button == SDL_BUTTON_MIDDLE) {
410 level_editor->drag = true;
413 if (event->type == SDL_MOUSEBUTTONUP && event->button.button == SDL_BUTTON_MIDDLE) {
414 level_editor->drag = false;
418 case SDL_MOUSEMOTION: {
419 if (level_editor->drag) {
420 const Vec2f next_position = camera_map_screen(camera, event->motion.x, event->motion.y);
421 const Vec2f prev_position = camera_map_screen(
423 event->motion.x + event->motion.xrel,
424 event->motion.y + event->motion.yrel);
426 vec_add(&level_editor->camera_position,
427 vec_sub(next_position, prev_position));
428 camera_center_at(camera, level_editor->camera_position);
434 bool selected = false;
435 if (layer_picker_event(
436 &level_editor->layer_picker,
445 level_editor->layers[level_editor->layer_picker],
448 &level_editor->undo_history) < 0) {
452 level_editor->click = 1;
459 int level_editor_event(LevelEditor *level_editor,
460 const SDL_Event *event,
463 trace_assert(level_editor);
465 trace_assert(camera);
467 switch (level_editor->state) {
468 case LEVEL_EDITOR_IDLE:
469 return level_editor_idle_event(level_editor, event, camera);
471 case LEVEL_EDITOR_SAVEAS:
472 return level_editor_saveas_event(level_editor, event, camera);
478 int level_editor_focus_camera(LevelEditor *level_editor,
481 camera_center_at(camera, level_editor->camera_position);
482 camera_scale(camera, level_editor->camera_scale);
486 static LayerPicker level_format_layer_order[LAYER_PICKER_N] = {
487 LAYER_PICKER_BACKGROUND,
489 LAYER_PICKER_PLATFORMS,
492 LAYER_PICKER_BACK_PLATFORMS,
498 /* TODO(#904): LevelEditor does not check that the saved level file is modified by external program */
499 static int level_editor_dump(LevelEditor *level_editor)
501 trace_assert(level_editor);
503 FILE *filedump = PUSH_LT(
505 fopen(level_editor->file_name, "w"),
508 if (fprintf(filedump, "%s\n", VERSION) < 0) {
512 for (size_t i = 0; i < LAYER_PICKER_N; ++i) {
513 if (layer_dump_stream(
514 level_editor->layers[level_format_layer_order[i]],
520 fclose(RELEASE_LT(level_editor->lt, filedump));
522 fading_wiggly_text_reset(&level_editor->notice);
523 level_editor->save = 1;
528 int level_editor_update(LevelEditor *level_editor, float delta_time)
530 return fading_wiggly_text_update(&level_editor->notice, delta_time);
533 void level_editor_sound(LevelEditor *level_editor, Sound_samples *sound_samples)
535 trace_assert(sound_samples);
538 if (level_editor->bell) {
539 level_editor->bell = 0;
540 sound_samples_play_sound(sound_samples, 2);
543 if (level_editor->click) {
544 level_editor->click = 0;
545 sound_samples_play_sound(sound_samples, 3);
548 if (level_editor->save) {
549 level_editor->save = 0;
550 sound_samples_play_sound(sound_samples, 4);