3 #include "game/camera.h"
4 #include "game/sound_samples.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/log.h"
16 #include "system/str.h"
18 #include "math/extrema.h"
19 #include "system/file.h"
21 #include "level_editor.h"
23 #define LEVEL_FOLDER_MAX_LENGTH 512
24 #define LEVEL_EDITOR_EDIT_FIELD_SIZE vec(5.0f, 5.0f)
25 #define LEVEL_EDITOR_EDIT_FIELD_COLOR COLOR_BLACK
27 #define LEVEL_EDITOR_NOTICE_SCALE vec(10.0f, 10.0f)
28 #define LEVEL_EDITOR_NOTICE_DURATION 1.0f
29 #define LEVEL_EDITOR_NOTICE_PADDING_TOP 100.0f
30 #define LEVEL_EDITOR_TMPMEM_CAPACITY (640 * KILO)
32 static int level_editor_dump(LevelEditor *level_editor);
34 // TODO(#994): too much duplicate code between create_level_editor and create_level_editor_from_file
36 void create_level_editor(LevelEditor *level_editor, Cursor *cursor)
38 memset(level_editor, 0, sizeof(*level_editor));
40 level_editor->edit_field_filename.font_size = LEVEL_EDITOR_EDIT_FIELD_SIZE;
41 level_editor->edit_field_filename.font_color = LEVEL_EDITOR_EDIT_FIELD_COLOR;
43 level_editor->background_layer = create_background_layer(hexstr("fffda5"));
44 level_editor->player_layer = create_player_layer(vec(0.0f, 0.0f), hexstr("ff8080"));
45 level_editor->platforms_layer = create_rect_layer("platform", cursor);
46 level_editor->goals_layer = create_point_layer("goal"),
47 level_editor->lava_layer = create_rect_layer("lava", cursor);
48 level_editor->back_platforms_layer = create_rect_layer("back_platform", cursor);
49 level_editor->boxes_layer = create_rect_layer("box", cursor);
50 level_editor->label_layer = create_label_layer("label");
51 level_editor->regions_layer = create_rect_layer("region", cursor),
53 level_editor->layers[LAYER_PICKER_BOXES] = rect_layer_as_layer(&level_editor->boxes_layer);
54 level_editor->layers[LAYER_PICKER_PLATFORMS] = rect_layer_as_layer(&level_editor->platforms_layer);
55 level_editor->layers[LAYER_PICKER_BACK_PLATFORMS] = rect_layer_as_layer(&level_editor->back_platforms_layer);
56 level_editor->layers[LAYER_PICKER_GOALS] = point_layer_as_layer(&level_editor->goals_layer);
57 level_editor->layers[LAYER_PICKER_PLAYER] = player_layer_as_layer(&level_editor->player_layer);
58 level_editor->layers[LAYER_PICKER_LAVA] = rect_layer_as_layer(&level_editor->lava_layer);
59 level_editor->layers[LAYER_PICKER_REGIONS] = rect_layer_as_layer(&level_editor->regions_layer);
60 level_editor->layers[LAYER_PICKER_BACKGROUND] = background_layer_as_layer(&level_editor->background_layer);
61 level_editor->layers[LAYER_PICKER_LABELS] = label_layer_as_layer(&level_editor->label_layer);
63 level_editor->notice = (FadingWigglyText) {
65 .text = "Level saved",
66 .color = rgba(0.0f, 0.0f, 0.0f, 0.0f),
67 .scale = LEVEL_EDITOR_NOTICE_SCALE
69 .duration = LEVEL_EDITOR_NOTICE_DURATION,
72 level_editor->camera_scale = 1.0f;
73 level_editor->undo_history = create_undo_history();
76 void level_editor_load_from_file(LevelEditor *level_editor, Memory *tmpmem, const char *file_name)
78 trace_assert(file_name);
80 if (level_editor->file_name) free(level_editor->file_name);
81 level_editor->file_name = string_duplicate(file_name, NULL);
83 String input = read_whole_file(tmpmem, file_name);
84 trace_assert(input.data);
86 String version = trim(chop_by_delim(&input, '\n'));
88 if (string_equal(version, STRING_LIT("1"))) {
89 chop_by_delim(&input, '\n');
90 } else if (string_equal(version, STRING_LIT("2"))) {
93 log_fail("Version `%s` is not supported. Expected version `%s`.\n",
94 string_to_cstr(tmpmem, version),
99 level_editor->background_layer = chop_background_layer(&input);
100 level_editor->player_layer = chop_player_layer(tmpmem, &input);
101 rect_layer_reload(&level_editor->platforms_layer, tmpmem, &input);
102 point_layer_reload(&level_editor->goals_layer, tmpmem, &input);
103 rect_layer_reload(&level_editor->lava_layer, tmpmem, &input);
104 rect_layer_reload(&level_editor->back_platforms_layer, tmpmem, &input);
105 rect_layer_reload(&level_editor->boxes_layer, tmpmem, &input);
106 label_layer_reload(&level_editor->label_layer, tmpmem, &input);
107 rect_layer_reload(&level_editor->regions_layer, tmpmem, &input);
108 undo_history_clean(&level_editor->undo_history);
111 void level_editor_clean(LevelEditor *level_editor)
113 level_editor->camera_scale = 1.0f;
114 level_editor->camera_position = vec(0.0f, 0.0f);
115 if (level_editor->file_name) {
116 free(level_editor->file_name);
117 level_editor->file_name = NULL;
119 level_editor->background_layer = create_background_layer(hexstr("fffda5"));
120 level_editor->player_layer = create_player_layer(vec(0.0f, 0.0f), hexstr("ff8080"));
121 rect_layer_clean(&level_editor->platforms_layer);
122 point_layer_clean(&level_editor->goals_layer);
123 rect_layer_clean(&level_editor->lava_layer);
124 rect_layer_clean(&level_editor->back_platforms_layer);
125 rect_layer_clean(&level_editor->boxes_layer);
126 label_layer_clean(&level_editor->label_layer);
127 rect_layer_clean(&level_editor->regions_layer);
128 undo_history_clean(&level_editor->undo_history);
131 void destroy_level_editor(LevelEditor *level_editor)
133 trace_assert(level_editor);
134 destroy_undo_history(level_editor->undo_history);
135 destroy_rect_layer(level_editor->boxes_layer);
136 destroy_rect_layer(level_editor->platforms_layer);
137 destroy_rect_layer(level_editor->back_platforms_layer);
138 destroy_point_layer(level_editor->goals_layer);
139 destroy_rect_layer(level_editor->lava_layer);
140 destroy_rect_layer(level_editor->regions_layer);
141 destroy_label_layer(level_editor->label_layer);
143 if (level_editor->file_name) {
144 free(level_editor->file_name);
148 int level_editor_render(const LevelEditor *level_editor,
149 const Camera *camera)
151 trace_assert(level_editor);
152 trace_assert(camera);
154 if (camera_clear_background(camera, color_picker_rgba(&level_editor->background_layer.color_picker)) < 0) {
158 const Rect world_viewport = camera_view_port(camera);
160 if (PLAYER_DEATH_LEVEL < world_viewport.y + world_viewport.h) {
161 if (camera_fill_rect(
164 world_viewport.x, PLAYER_DEATH_LEVEL,
165 world_viewport.w, world_viewport.h + fmaxf(0.0f, world_viewport.y - PLAYER_DEATH_LEVEL)),
166 LEVEL_EDITOR_DETH_LEVEL_COLOR) < 0) {
171 for (size_t i = 0; i < LAYER_PICKER_N; ++i) {
173 level_editor->layers[i],
175 i == level_editor->layer_picker) < 0) {
180 if (layer_picker_render(&level_editor->layer_picker, camera) < 0) {
184 if (level_editor->state == LEVEL_EDITOR_SAVEAS) {
186 const Vec2f size = LEVEL_EDITOR_EDIT_FIELD_SIZE;
187 const char *save_as_text = "Save as: ";
188 const Vec2f position = vec(200.0f, 200.0f);
189 const float save_as_width =
190 (float) strlen(save_as_text) * FONT_CHAR_WIDTH * size.x;
193 camera_render_text_screen(
196 LEVEL_EDITOR_EDIT_FIELD_SIZE,
197 LEVEL_EDITOR_EDIT_FIELD_COLOR,
200 if (edit_field_render_screen(
201 &level_editor->edit_field_filename,
203 vec(position.x + save_as_width, position.y)) < 0) {
208 const Rect screen_viewport = camera_view_port_screen(camera);
209 const Vec2f text_size = fading_wiggly_text_size(&level_editor->notice);
211 fading_wiggly_text_render(
212 &level_editor->notice, camera,
213 vec(screen_viewport.w * 0.5f - text_size.x * 0.5f,
214 LEVEL_EDITOR_NOTICE_PADDING_TOP));
220 int level_editor_saveas_event(LevelEditor *level_editor,
221 const SDL_Event *event,
222 const Camera *camera)
224 trace_assert(level_editor);
226 trace_assert(camera);
228 switch (event->type) {
230 if (event->key.keysym.sym == SDLK_RETURN) {
231 trace_assert(level_editor->file_name == NULL);
232 char path[LEVEL_FOLDER_MAX_LENGTH];
235 LEVEL_FOLDER_MAX_LENGTH,
236 "./assets/levels/%s.txt",
237 edit_field_as_text(&level_editor->edit_field_filename));
238 level_editor->file_name = string_duplicate(path, NULL);
239 level_editor_dump(level_editor);
241 level_editor->state = LEVEL_EDITOR_IDLE;
247 return edit_field_event(&level_editor->edit_field_filename, event);
251 int level_editor_idle_event(LevelEditor *level_editor,
252 const SDL_Event *event,
255 trace_assert(level_editor);
257 trace_assert(camera);
259 switch (event->type) {
261 switch(event->key.keysym.sym) {
263 if (!SDL_IsTextInputActive()) {
264 if (level_editor->file_name) {
265 level_editor_dump(level_editor);
266 log_info("Saving level to `%s`\n", level_editor->file_name);
268 SDL_StartTextInput();
269 level_editor->state = LEVEL_EDITOR_SAVEAS;
275 if (event->key.keysym.mod & KMOD_CTRL) {
276 if (undo_history_empty(&level_editor->undo_history)) {
277 level_editor->bell = 1;
279 undo_history_pop(&level_editor->undo_history);
285 case SDL_MOUSEWHEEL: {
287 SDL_GetMouseState(&x, &y);
289 Vec2f position = camera_map_screen(camera, x, y);
290 if (event->wheel.y > 0) {
291 level_editor->camera_scale += 0.1f;
292 } else if (event->wheel.y < 0) {
293 level_editor->camera_scale = fmaxf(0.1f, level_editor->camera_scale - 0.1f);
295 camera_scale(camera, level_editor->camera_scale);
296 Vec2f zoomed_position = camera_map_screen(camera, x, y);
298 level_editor->camera_position =
300 level_editor->camera_position,
301 vec_sub(position, zoomed_position));
302 camera_center_at(camera, level_editor->camera_position);
305 case SDL_MOUSEBUTTONUP:
306 case SDL_MOUSEBUTTONDOWN: {
307 if (event->type == SDL_MOUSEBUTTONDOWN && event->button.button == SDL_BUTTON_MIDDLE) {
308 level_editor->drag = true;
311 if (event->type == SDL_MOUSEBUTTONUP && event->button.button == SDL_BUTTON_MIDDLE) {
312 level_editor->drag = false;
316 case SDL_MOUSEMOTION: {
317 if (level_editor->drag) {
318 const Vec2f next_position = camera_map_screen(camera, event->motion.x, event->motion.y);
319 const Vec2f prev_position = camera_map_screen(
321 event->motion.x + event->motion.xrel,
322 event->motion.y + event->motion.yrel);
324 vec_add(&level_editor->camera_position,
325 vec_sub(next_position, prev_position));
326 camera_center_at(camera, level_editor->camera_position);
332 bool selected = false;
333 if (layer_picker_event(
334 &level_editor->layer_picker,
343 level_editor->layers[level_editor->layer_picker],
346 &level_editor->undo_history) < 0) {
350 level_editor->click = 1;
357 int level_editor_event(LevelEditor *level_editor,
358 const SDL_Event *event,
361 trace_assert(level_editor);
363 trace_assert(camera);
365 switch (level_editor->state) {
366 case LEVEL_EDITOR_IDLE:
367 return level_editor_idle_event(level_editor, event, camera);
369 case LEVEL_EDITOR_SAVEAS:
370 return level_editor_saveas_event(level_editor, event, camera);
376 int level_editor_focus_camera(LevelEditor *level_editor,
379 camera_center_at(camera, level_editor->camera_position);
380 camera_scale(camera, level_editor->camera_scale);
384 static LayerPicker level_format_layer_order[LAYER_PICKER_N] = {
385 LAYER_PICKER_BACKGROUND,
387 LAYER_PICKER_PLATFORMS,
390 LAYER_PICKER_BACK_PLATFORMS,
396 /* TODO(#904): LevelEditor does not check that the saved level file is modified by external program */
397 static int level_editor_dump(LevelEditor *level_editor)
399 trace_assert(level_editor);
401 FILE *filedump = fopen(level_editor->file_name, "w");
402 trace_assert(filedump);
404 if (fprintf(filedump, "%s\n", VERSION) < 0) {
408 for (size_t i = 0; i < LAYER_PICKER_N; ++i) {
409 if (layer_dump_stream(
410 level_editor->layers[level_format_layer_order[i]],
418 fading_wiggly_text_reset(&level_editor->notice);
419 level_editor->save = 1;
424 int level_editor_update(LevelEditor *level_editor, float delta_time)
426 return fading_wiggly_text_update(&level_editor->notice, delta_time);
429 void level_editor_sound(LevelEditor *level_editor, Sound_samples *sound_samples)
431 trace_assert(sound_samples);
434 if (level_editor->bell) {
435 level_editor->bell = 0;
436 sound_samples_play_sound(sound_samples, 2);
439 if (level_editor->click) {
440 level_editor->click = 0;
441 sound_samples_play_sound(sound_samples, 3);
444 if (level_editor->save) {
445 level_editor->save = 0;
446 sound_samples_play_sound(sound_samples, 4);