summaryrefslogtreecommitdiff
path: root/src/scenes
diff options
context:
space:
mode:
authorAldrik Ramaekers <aldrikboy@gmail.com>2024-11-23 21:52:24 +0100
committerAldrik Ramaekers <aldrikboy@gmail.com>2024-11-23 21:52:24 +0100
commit6f7374c2fa58c8692b51018864b802e6b876d305 (patch)
treea7e8ead757e9f4de1920395336dcac1c8a989576 /src/scenes
A new start
Diffstat (limited to 'src/scenes')
-rw-r--r--src/scenes/error_scene.c74
-rw-r--r--src/scenes/loading_scene.c85
-rw-r--r--src/scenes/loading_world_scene.c116
-rw-r--r--src/scenes/menu_scene.c93
-rw-r--r--src/scenes/place_detail.c1771
-rw-r--r--src/scenes/save_state_select.c105
-rw-r--r--src/scenes/settings_scene.c152
-rw-r--r--src/scenes/world_map.c1094
8 files changed, 3490 insertions, 0 deletions
diff --git a/src/scenes/error_scene.c b/src/scenes/error_scene.c
new file mode 100644
index 0000000..8d4446a
--- /dev/null
+++ b/src/scenes/error_scene.c
@@ -0,0 +1,74 @@
+
+void error_scene_init()
+{
+
+}
+
+static void error_scene_draw_info(platform_window* window)
+{
+ s32 screen_center_x = area.x + (area.w/2);
+ s32 screen_center_y = area.y + (area.h/2);
+
+ float vertical_pad = 20 * scale;
+ float horizontal_pad = vertical_pad;
+ float spacing = 5 * scale;
+
+ s32 panel_h = 280 * scale;
+ s32 panel_item_size = (panel_h - (vertical_pad*2) - (spacing*1)) / 2;
+ s32 panel_w = (panel_item_size * 3) + (horizontal_pad*2) + (spacing*2);
+
+ s32 panel_x = screen_center_x - (panel_w/2);
+ s32 panel_y = screen_center_y - (panel_h/2);
+ panel_render(scale, panel_x, panel_y, panel_w, panel_h);
+
+ // info text
+ {
+ {
+ char* title = "ERROR";
+ font* font_title = FONT_REGULAR(SIZE_RD(area.w, 32));
+ s32 text_w = renderer->calculate_text_width(font_title, title);
+ s32 text_x = screen_center_x - (text_w/2);
+ s32 text_y = panel_y + (vertical_pad*2);
+ renderer->render_text(font_title, text_x+2, text_y+2, title, COLOR_TEXT_SHADOW);
+ renderer->render_text(font_title, text_x, text_y, title, COLOR_TEXT);
+ }
+ {
+ char* text = "An error occured, please verify\nyour game files.";
+ font* font_info = FONT_REGULAR(SIZE_RD(area.w, 28));
+ s32 text_w = panel_w - (horizontal_pad*4);
+ s32 text_x = screen_center_x - (text_w/2);
+ s32 text_y = screen_center_y - (font_info->px_h/2);
+ renderer->render_text_cutoff(font_info, text_x+2, text_y+2, text, COLOR_TEXT_SHADOW, 9999);
+ renderer->render_text_cutoff(font_info, text_x, text_y, text, COLOR_TEXT, 9999);
+ }
+
+ }
+
+ // back button
+ {
+ s32 back_h = img_back->height * scale/2;
+ s32 back_w = img_back->width * scale/2;
+ s32 back_x = panel_x + (panel_item_size/3);
+ s32 back_y = panel_y + panel_h - (back_h/2) - 1;
+
+ if (push_back_button(scale, back_x, back_y, back_w, back_h)) {
+ game_set_active_scene(GAME_STATE_MENU);
+ }
+ }
+}
+
+void error_scene_render(platform_window* window)
+{
+ menu_draw_background(window);
+ error_scene_draw_info(window);
+}
+
+void error_scene_update(platform_window* window)
+{
+
+}
+
+void error_scene_destroy()
+{
+
+} \ No newline at end of file
diff --git a/src/scenes/loading_scene.c b/src/scenes/loading_scene.c
new file mode 100644
index 0000000..2240aa8
--- /dev/null
+++ b/src/scenes/loading_scene.c
@@ -0,0 +1,85 @@
+
+#define MAX_CREDITED_NAMES 5
+#define MAX_CREDIT_NAME_LENGTH 30
+#define COMPLETE_CREDIT_LENGTH (MAX_CREDIT_NAME_LENGTH*MAX_CREDITED_NAMES)+20
+char complete_credit_text[COMPLETE_CREDIT_LENGTH];
+
+void loading_scene_init()
+{
+ strcpy(complete_credit_text, "Music by ");
+
+ // Load names to credit.
+ {
+ platform_set_active_directory(binary_path);
+
+ array files = array_create(sizeof(found_file));
+ array filters = string_split("AUTHOR.txt");
+ bool is_cancelled = false;
+ platform_list_files_block(&files, "data/music/", filters, true, 0, true, &is_cancelled, 0);
+ log_assert(files.length <= MAX_CREDITED_NAMES, "Not enough space for credited names.");
+ for (s32 i = 0; i < files.length; i++)
+ {
+ found_file *file = array_at(&files, i);
+
+ if (platform_file_exists(file->path))
+ {
+ file_content name = platform_read_file_content(file->path, "rb");
+ if (name.file_error) continue;
+
+ string_appendn(complete_credit_text, name.content, COMPLETE_CREDIT_LENGTH);
+ if (i != files.length-1) {
+ string_appendn(complete_credit_text, ", ", COMPLETE_CREDIT_LENGTH);
+ }
+ platform_destroy_file_content(&name);
+ }
+
+ mem_free(file->matched_filter);
+ mem_free(file->path);
+ }
+
+ array_destroy(&files);
+ array_destroy(&filters);
+ }
+}
+
+void loading_scene_render(platform_window* window)
+{
+ renderer->render_rectangle(area.x, area.y, area.w, area.h, COLOR_WHITE);
+
+ font* font_reg = FONT_REGULAR(SIZE_RD(area.w, 36));
+ s32 target_size = area.h/5;
+ s32 logo_text_pad = 20;
+ s32 total_height = target_size + logo_text_pad + font_reg->px_h;
+
+ s32 screen_center_x = area.x + (area.w/2);
+ s32 screen_center_y = area.y + (area.h/2);
+ s32 logo_x = screen_center_x - (target_size/2);
+ s32 logo_y = screen_center_y - (total_height/2);
+ renderer->render_image(img_logo, logo_x, logo_y, target_size, target_size);
+
+ s32 text_y = logo_y + target_size + logo_text_pad;
+ char* company_name = "Tar Software";
+ s32 company_name_width = renderer->calculate_text_width(font_reg, company_name);
+ s32 text_x = screen_center_x - (company_name_width/2);
+
+ renderer->render_text(font_reg, text_x, text_y, company_name, COLOR_TITLE);
+
+ // Credits
+ font* font_s = FONT_REGULAR(SIZE_RD(area.w, 20));
+ s32 credit_pad = 30*scale;
+ renderer->render_text(font_s, area.x + credit_pad, area.y+area.h-credit_pad-font_s->px_h, complete_credit_text, COLOR_TITLE);
+}
+
+void loading_scene_update(platform_window* window)
+{
+ platform_set_cursor(window, CURSOR_LOADING);
+
+ if (global_asset_collection.done_loading_assets) {
+ game_set_active_scene(GAME_STATE_MENU);
+ }
+}
+
+void loading_scene_destroy()
+{
+
+} \ No newline at end of file
diff --git a/src/scenes/loading_world_scene.c b/src/scenes/loading_world_scene.c
new file mode 100644
index 0000000..3d15909
--- /dev/null
+++ b/src/scenes/loading_world_scene.c
@@ -0,0 +1,116 @@
+
+void loading_world_scene_init()
+{
+
+}
+
+u64 load_start_stamp = 0;
+
+static void* start_loading_world_t(void* arg)
+{
+ load_start_stamp = platform_get_time(TIME_FULL, TIME_US); // Used for displaying info texts.
+ #ifdef MODE_DEBUG
+ // thread_sleep(1000*200);
+ #endif
+
+ char* path = (char*)arg;
+ world* world_to_load = 0;
+
+ if (path) {
+ // Load from file here
+ }
+ else {
+ world_to_load = world_create_new();
+ }
+
+ #ifdef MODE_DEBUG
+ u64 load_end_stamp = platform_get_time(TIME_FULL, TIME_US);
+ u64 elapsed_ns = load_end_stamp - load_start_stamp;
+ char info_msg[50];
+ sprintf(info_msg, "Loaded world in %.2fms", elapsed_ns/1000.0f);
+ log_info(info_msg);
+ #endif
+
+ if (!world_to_load) {
+ log_info("Failed to load world");
+ game_set_active_scene(GAME_STATE_ERROR);
+ }
+ else {
+ world_map_set_active_world(world_to_load);
+ game_set_active_scene(GAME_STATE_WORLD_MAP);
+ }
+
+ return 0;
+}
+
+void start_loading_world(char* saved_file_path)
+{
+ game_set_active_scene(GAME_STATE_LOADING_WORLD);
+ thread_start(start_loading_world_t, saved_file_path);
+}
+
+static void loading_world_draw_info_texts(font* font, s32 center_x, s32 y)
+{
+ char* texts[5] = {
+ "(Building cities)",
+ "(Connecting roads)",
+ "(Manufacturing trucks)",
+ "(Finding truck drivers)",
+ "(This is taking very long..)",
+ };
+
+ u64 load_end_stamp = platform_get_time(TIME_FULL, TIME_US);
+ u64 elapsed_ns = load_end_stamp - load_start_stamp;
+ float elapsed_sec = elapsed_ns / 1000.0f;
+ int text_index = elapsed_sec / 2000.0f; // 2 sec per text.
+ if (text_index >= 5) text_index = 4;
+
+ s32 text_len = renderer->calculate_text_width(font, texts[text_index]);
+ renderer->render_text(font, center_x - (text_len/2), y, texts[text_index], COLOR_TEXT);
+}
+
+static void loading_world_draw_animation(platform_window* window)
+{
+ s32 screen_center_x = area.x + (area.w/2);
+ s32 screen_center_y = area.y + (area.h/2);
+
+ float vertical_pad = 20 * scale;
+ float horizontal_pad = vertical_pad;
+ float spacing = 5 * scale;
+
+ s32 panel_h = 280 * scale;
+ s32 panel_item_size = (panel_h - (vertical_pad*2) - (spacing*1)) / 2;
+ s32 panel_w = (panel_item_size * 3) + (horizontal_pad*2) + (spacing*2);
+
+ s32 panel_x = screen_center_x - (panel_w/2);
+ s32 panel_y = screen_center_y - (panel_h/2);
+ panel_render(scale, panel_x, panel_y, panel_w, panel_h);
+
+ s32 carwheel_size = panel_h/4;
+
+ static float rotation = 0.0f;
+ rotation -= 0.05f;
+ gl_render_set_rotation(rotation);
+ renderer->render_image(img_carwheel, screen_center_x - (carwheel_size/2), screen_center_y - (carwheel_size/2), carwheel_size, carwheel_size);
+ gl_render_set_rotation(0.0f);
+
+
+ font* font_info = FONT_REGULAR(SIZE_RD(area.w, 28));
+ loading_world_draw_info_texts(font_info, screen_center_x, panel_y + panel_h - font_info->px_h - vertical_pad*2);
+}
+
+void loading_world_scene_render(platform_window* window)
+{
+ menu_draw_background(window);
+ loading_world_draw_animation(window);
+}
+
+void loading_world_scene_update(platform_window* window)
+{
+
+}
+
+void loading_world_scene_destroy()
+{
+
+} \ No newline at end of file
diff --git a/src/scenes/menu_scene.c b/src/scenes/menu_scene.c
new file mode 100644
index 0000000..73ec22d
--- /dev/null
+++ b/src/scenes/menu_scene.c
@@ -0,0 +1,93 @@
+
+void menu_scene_init()
+{
+
+}
+
+static void menu_draw_background(platform_window* window)
+{
+ vec4 area = camera_get_target_rectangle(window);
+ renderer->render_rectangle(area.x, area.y, area.w, area.h, COLOR_WORLD_MAP_BACKGROUND);
+ renderer->render_image(img_world_map, area.x, area.y, area.w, area.h);
+}
+
+static void menu_draw_options(platform_window* window)
+{
+ s32 screen_center_x = area.x + (area.w/2);
+ s32 screen_center_y = area.y + (area.h/2);
+
+ s32 panel_w = 198 * scale;
+ s32 panel_h = 193 * scale;
+ s32 panel_x = screen_center_x - (panel_w/2);
+ s32 panel_y = screen_center_y - (panel_h/2);
+ panel_render(scale, panel_x, panel_y, panel_w, panel_h);
+
+ s32 button_w = 178 * scale;
+ s32 button_h = 37 * scale;
+ s32 vertical_pad = 10 * scale;
+ s32 pad_x = (panel_w - button_w)/2;
+ float pad_y = (panel_h - (vertical_pad*2) - button_h*4)/5.0f;
+
+ if (button_render(scale, BUTTON_ENABLED, "New Game", panel_x + pad_x, vertical_pad + panel_y + pad_y*1, button_w, button_h))
+ {
+ start_loading_world(0); // Start new world
+ }
+
+ if (button_render(scale, BUTTON_ENABLED, "Continue", panel_x + pad_x, vertical_pad + panel_y + pad_y*2 + button_h*1, button_w, button_h))
+ {
+ game_set_active_scene(GAME_STATE_SELECT_SAVE);
+ }
+
+ if (button_render(scale, BUTTON_ENABLED, "Settings", panel_x + pad_x, vertical_pad + panel_y + pad_y*3 + button_h*2, button_w, button_h))
+ {
+ game_set_active_scene(GAME_STATE_SETTINGS);
+ }
+
+ if (button_render(scale, BUTTON_ENABLED, "Quit", panel_x + pad_x, vertical_pad + panel_y + pad_y*4 + button_h*3, button_w, button_h))
+ {
+ window->is_open = false;
+ }
+}
+
+static void menu_draw_title(platform_window* window)
+{
+ s32 panel_w = 198 * scale;
+ s32 panel_h = 70 * scale;
+ s32 panel_pad = 50 * scale;
+ s32 panel_x = area.x + panel_pad;
+ s32 panel_y = area.y + area.h - panel_h - panel_pad;
+ panel_render(scale, panel_x, panel_y, panel_w, panel_h);
+
+ font* font_reg = FONT_REGULAR(SIZE_RD(area.w, 44));
+ font* font_sml = FONT_REGULAR(SIZE_RD(area.w, 20));
+ s32 text_pad = 5*scale;
+ s32 total_text_h = font_reg->px_h + text_pad + font_sml->px_h;
+ s32 text_y = panel_y + (panel_h/2) - (total_text_h/2);
+ char* game_title = "TruckerX";
+ char* game_version = "rev "GAME_VERSION;
+ s32 game_title_width = renderer->calculate_text_width(font_reg, game_title);
+ s32 text_x = panel_x + (panel_w/2) - (game_title_width/2);
+
+ renderer->render_text(font_reg, text_x+2, text_y+2, game_title, COLOR_TEXT_SHADOW);
+ renderer->render_text(font_reg, text_x, text_y, game_title, COLOR_TEXT);
+
+ renderer->render_text(font_sml, 10*scale + text_x+2,font_reg->px_h + text_pad + text_y+2, game_version, COLOR_TEXT_SHADOW);
+ renderer->render_text(font_sml, 10*scale + text_x, font_reg->px_h + text_pad + text_y, game_version, COLOR_TEXT);
+}
+
+void menu_scene_render(platform_window* window)
+{
+ menu_draw_background(window);
+ menu_draw_options(window);
+ menu_draw_title(window);
+}
+
+void menu_scene_update(platform_window* window)
+{
+
+}
+
+void menu_scene_destroy()
+{
+
+} \ No newline at end of file
diff --git a/src/scenes/place_detail.c b/src/scenes/place_detail.c
new file mode 100644
index 0000000..699fe0e
--- /dev/null
+++ b/src/scenes/place_detail.c
@@ -0,0 +1,1771 @@
+typedef enum t_place_detail_state
+{
+ PLACE_DETAIL_SHOW_MAIN,
+ PLACE_DETAIL_SHOW_RESUMES,
+ PLACE_DETAIL_SHOW_DEALERS,
+ PLACE_DETAIL_SHOW_EMPLOYEE,
+ PLACE_DETAIL_SHOW_SCHEDULE,
+} place_detail_state;
+
+typedef enum t_place_detail_info_state
+{
+ PLACE_DETAIL_EMPLOYEES = 0,
+ PLACE_DETAIL_JOBOFFERS = 1,
+ PLACE_DETAIL_SCHEDULE = 2,
+ PLACE_DETAIL_GARAGE = 3,
+} place_detail_info_state;
+
+typedef enum t_schedule_state
+{
+ SCHEDULING_JOB,
+ RESCHEDULING_JOB,
+ VIEWING,
+} schedule_state;
+
+// Animations
+#define TAG_ANIMATION_DURATION 100
+#define EMPLOYEE_SELECTOR_ANIMATION_DURATION 100
+#define TRUCK_SWAP_ANIMATION_DURATION 200
+animation tag_animation = {0,0,0,1};
+animation employee_selector_animation = {0,0,0,1};
+animation truck_swap_animation = {0,0,0,1};
+
+// States
+world_location* _active_location;
+employee* _active_employee;
+scheduled_job _active_scheduling_job;
+scheduled_job* _active_selected_scheduled_job;
+schedule_state _active_schedule_state = VIEWING;
+s32 _active_schedule_selected_job_index = 0;
+place_detail_info_state selected_tab_index = PLACE_DETAIL_EMPLOYEES;
+place_detail_state current_detail_state = PLACE_DETAIL_SHOW_MAIN;
+static s16 selected_truck_index = 0;
+static u8 active_dealer_index = 0;
+#define INVALID_VAL -999
+static s32 index_of_truck = INVALID_VAL;
+
+typedef struct t_tab
+{
+ s32 x;
+ s32 y;
+ s32 w;
+ s32 h;
+ float scale;
+} tab;
+
+void _goto_default_detail_state()
+{
+ index_of_truck = INVALID_VAL;
+ selected_truck_index = 0;
+ active_dealer_index = 0;
+ _active_employee = 0;
+ _active_schedule_state = VIEWING;
+ current_detail_state = PLACE_DETAIL_SHOW_MAIN;
+ _active_schedule_selected_job_index = 0;
+ _active_selected_scheduled_job = 0;
+ keyboard_set_input_text("");
+ _global_keyboard.take_input = false;
+}
+
+void place_detail_set_active_location(world_location* location)
+{
+ _active_location = location;
+ selected_tab_index = PLACE_DETAIL_EMPLOYEES;
+ current_detail_state = PLACE_DETAIL_SHOW_MAIN;
+}
+
+void place_detail_scene_init()
+{
+
+}
+
+static char* get_shipday_list_string(job_offer* offer, char* buf, s32 len)
+{
+ memset(buf, 0, len);
+ bool first = true;
+ if (job_offer_has_ship_day(offer, MONDAY)) { string_appendn(buf, "Mon", len); first = false; }
+ if (job_offer_has_ship_day(offer, TUESDAY)) { if (!first) string_appendn(buf, ", ", len); string_appendn(buf, "Tue", len); first = false; }
+ if (job_offer_has_ship_day(offer, WEDNESDAY)) { if (!first) string_appendn(buf, ", ", len); string_appendn(buf, "Wed", len); first = false; }
+ if (job_offer_has_ship_day(offer, THURSDAY)) { if (!first) string_appendn(buf, ", ", len); string_appendn(buf, "Thu", len); first = false; }
+ if (job_offer_has_ship_day(offer, FRIDAY)) { if (!first) string_appendn(buf, ", ", len); string_appendn(buf, "Fri", len); first = false; }
+ if (job_offer_has_ship_day(offer, SATURDAY)) { if (!first) string_appendn(buf, ", ", len); string_appendn(buf, "Sat", len); first = false; }
+ if (job_offer_has_ship_day(offer, SUNDAY)) { if (!first) string_appendn(buf, ", ", len); string_appendn(buf, "Sun", len); first = false; }
+
+ return buf;
+}
+
+static scheduled_job* get_scheduled_job_at_time(s32 day, s32 timeslot)
+{
+ for (s32 i = 0; i < _active_location->schedule.jobs.length; i++) {
+ scheduled_job* slot = array_at(&_active_location->schedule.jobs, i);
+ for (s32 x = 0; x < slot->offer.shipday_count; x++) {
+ if (slot->timeslots[x].day == day && slot->timeslots[x].timeslot == timeslot) return slot;
+ }
+ }
+ return 0;
+}
+
+static s32 find_empty_timeslot_for_day(s32 day)
+{
+ log_assert(day >= 0 && day < NUM_DAYS, "Invalid day");
+ for (s32 i = 0; i < TIME_SLOTS_PER_DAY; i++) {
+ scheduled_job* job = get_scheduled_job_at_time(day, i);
+ if (!job || (_active_schedule_state == RESCHEDULING_JOB && job == _active_selected_scheduled_job)) return i;
+ }
+ return -1;
+}
+
+static s32 find_empty_timeslot_for_day_right_to_left(s32 day)
+{
+ log_assert(day >= 0 && day < NUM_DAYS, "Invalid day");
+ for (s32 i = TIME_SLOTS_PER_DAY-1; i >= 0; i--) {
+ scheduled_job* job = get_scheduled_job_at_time(day, i);
+ if (!job || (_active_schedule_state == RESCHEDULING_JOB && job == _active_selected_scheduled_job)) return i;
+ }
+ return -1;
+}
+
+
+static scheduled_job create_empty_job_schedule(job_offer* job)
+{
+ scheduled_job new_job;
+ new_job.location = _active_location;
+ new_job.offer = *job;
+ new_job.trust = 1.0f;
+ for (s32 i = 0; i < MAX_SHIPDAYS; i++) {
+ new_job.timeslots[i] = (scheduled_job_time){-1, -1, 0, 0};
+ }
+ for (s32 i = 0; i < job->shipday_count; i++) {
+ new_job.timeslots[i] = (scheduled_job_time){job->shipdays[i], find_empty_timeslot_for_day(job->shipdays[i]), 0, 0};
+ }
+ return new_job;
+}
+
+#define scroll_speed 15
+#define HANDLE_TAB_SCROLL\
+ bool hovering_tab = (_global_mouse.y >= orig_y && _global_mouse.y <= orig_y + h \
+ && _global_mouse.x >= x && _global_mouse.x <= x + w);\
+ if (scroll_h > 0) {\
+ if (hovering_tab) {\
+ if (_global_mouse.scroll_state < 0) current_scroll += scroll_speed;\
+ if (_global_mouse.scroll_state > 0) current_scroll -= scroll_speed;\
+ }\
+ if (current_scroll > scroll_h) current_scroll = scroll_h;\
+ if (current_scroll < 0) current_scroll = 0;\
+ y -= current_scroll;\
+ }
+
+#define HANDLE_TAB_START(_count, _parts)\
+ s32 item_h = 34 * tab.scale;\
+ s32 total_h = (item_h+1) * (_count) + (10 * tab.scale);\
+ s32 scroll_h = total_h - h;\
+ s32 orig_y = y;\
+ static s32 current_scroll = 0;\
+ font* fnt = fnt_rd16;\
+ s32 info_text_h = fnt->px_h*4;\
+ scroll_h += info_text_h;\
+ s32 item_part_w = (w-item_h) / _parts;\
+ (void)item_part_w;
+
+#define HANDLE_TAB_INFO(_info, _btn_name, _next_state)\
+ s32 btn_h = info_text_h/1.3;\
+ s32 btn_w = btn_h*4;\
+ total_h += info_text_h;\
+ s32 info_text_y = y + (btn_h/2)-(fnt->px_h/2);\
+ renderer->render_text(fnt, x+1, info_text_y+1, _info, COLOR_TEXT_SHADOW);\
+ renderer->render_text(fnt, x, info_text_y, _info, COLOR_TEXT);\
+ if (_btn_name && button_render(tab.scale, true, _btn_name, x+w-btn_w-1, y, btn_w, btn_h)) {\
+ current_detail_state = _next_state;\
+ }\
+ y += info_text_h;
+
+#define HANDLE_TAB_ITEM_INTERACTION(_take) HANDLE_TAB_ITEM_INTERACTIONX(_take, i);
+#define HANDLE_TAB_ITEM_INTERACTIONX(_take, _count)\
+ s32 item_y = y + ((item_h+1) * _count);\
+ bool hovered = (_global_mouse.x >= x && _global_mouse.x <= x + w\
+ && _global_mouse.y >= item_y && _global_mouse.y <= item_y + item_h\
+ && hovering_tab);\
+ color tint = COLOR_LIST_ENTRY_BACKGROUND;\
+ if (_take && hovered) {\
+ tint = COLOR_LIST_ENTRY_BACKGROUND_ACTIVE;\
+ platform_set_cursor(window, CURSOR_POINTER);\
+ }\
+ renderer->render_rectangle(x, item_y, w, item_h, tint);
+
+#define TAB_ITEM_PUSH_TEXT(_buf,_width,_total)\
+ s32 text_w = renderer->calculate_text_width(fnt, _buf);\
+ s32 x_start = (x + item_h*2 - text_pad) + _total;\
+ s32 text_x = x_start + (_width/2)-(text_w/2);\
+ s32 text_y = item_y + (item_h/2) - (fnt->px_h/2);\
+ renderer->render_text(fnt, text_x+1, text_y+1, _buf, COLOR_TEXT_SHADOW);\
+ renderer->render_text(fnt, text_x, text_y,_buf, COLOR_TEXT);\
+ renderer->render_rectangle(x_start + _width, item_y+(5*tab.scale), 1, item_h-+(10*tab.scale), COLOR_BUTTON);\
+ _total += _width;
+
+#define PUSH_NOT_AVAILABLE_TEXT(_cond, _text)\
+ if (_cond) {\
+ font* fnt = fnt_rd20;\
+ char* txt = _text;\
+ s32 textw = renderer->calculate_text_width(fnt, txt);\
+ renderer->render_text(fnt, x+(w/2)-(textw/2), y+(tab.h/2)-(fnt->px_h/2)-(30*scale), txt, COLOR_TEXT);\
+ }
+
+static void place_detail_draw_job_offers(platform_window* window, tab tab, float x, float y, float w, float h)
+{
+ HANDLE_TAB_START(_active_location->job_offers.length, 2);
+ HANDLE_TAB_SCROLL;
+
+ char info_buf[50];
+ sprintf(info_buf, "%d Job offers open.", _active_location->job_offers.length);
+ HANDLE_TAB_INFO(info_buf, 0, 0);
+
+ PUSH_NOT_AVAILABLE_TEXT(!_active_location->job_offers.length, "No job offers available.");
+
+ for (s32 i = 0; i < _active_location->job_offers.length; i++) {
+ job_offer* offer = array_at(&_active_location->job_offers, i);
+ HANDLE_TAB_ITEM_INTERACTION(true);
+
+ if (hovered && is_left_clicked()) {
+ current_detail_state = PLACE_DETAIL_SHOW_SCHEDULE;
+ _active_schedule_state = SCHEDULING_JOB;
+ _active_schedule_selected_job_index = 0;
+ _active_selected_scheduled_job = 0;
+ _active_scheduling_job = create_empty_job_schedule(offer);
+
+ tag_animation = animation_create(TAG_ANIMATION_DURATION);
+ tag_animation.started = true;
+ audio_play_sound(snd_click, AUDIO_CHANNEL_SFX_1);
+ }
+
+ float icon_pad = item_h * 0.15;
+ float text_pad = icon_pad*2;
+ float icon_s = item_h - (icon_pad*2);
+ // icon
+ {
+ renderer->render_image(offer->company->logo, x + icon_pad, item_y + icon_pad, icon_s*2, icon_s);
+ renderer->render_rectangle(x + icon_pad + icon_s*2 + icon_pad, item_y+(5*tab.scale), 1, item_h-+(10*tab.scale), COLOR_BUTTON);
+ }
+
+ float width_of_piece = 80 * tab.scale;
+ float price_text_w = 0;
+
+ // price
+ {
+ char pricebuf[25];
+ sprintf(pricebuf, "$%d/trip", offer->reward);
+ TAB_ITEM_PUSH_TEXT(pricebuf, width_of_piece, price_text_w);
+ }
+
+ // distance
+ {
+ char pricebuf[25];
+ sprintf(pricebuf, "%.0fkm", offer->total_distance);
+ TAB_ITEM_PUSH_TEXT(pricebuf, width_of_piece, price_text_w);
+ }
+
+ // dur
+ {
+ char pricebuf[25];
+ sprintf(pricebuf, "%.0fh-%.0fh", offer->duration_sec_min/3600.0f, offer->duration_sec_max/3600.0f);
+ TAB_ITEM_PUSH_TEXT(pricebuf, width_of_piece, price_text_w);
+ }
+
+ // Name
+ {
+ char daybuf[50];
+ char buf[200];
+ sprintf(buf, "%s wants you to ship %s to %s every %s", offer->company->name, offer->product->name,
+ (*(world_location**)array_at(&offer->connections, offer->connections.length-1))->name, get_shipday_list_string(offer, daybuf, 50));
+ s32 text_x = x + item_h*2 + price_text_w + icon_pad;
+ s32 text_y = item_y + (item_h/2) - (fnt->px_h/2);
+ renderer->render_text(fnt, text_x+1, text_y+1, buf, COLOR_TEXT_SHADOW);
+ renderer->render_text(fnt, text_x, text_y, buf, COLOR_TEXT);
+ }
+ }
+}
+
+
+static void place_detail_draw_trucks(platform_window* window, tab tab, float x, float y, float w, float h)
+{
+ HANDLE_TAB_START(_active_location->trucks.length, 5);
+ HANDLE_TAB_SCROLL;
+
+ char info_buf[50];
+ sprintf(info_buf, "%d Trucks in garage.", _active_location->trucks.length);
+ HANDLE_TAB_INFO(info_buf, "Purchase", PLACE_DETAIL_SHOW_DEALERS);
+
+ PUSH_NOT_AVAILABLE_TEXT(!_active_location->trucks.length, "No trucks in garage.");
+
+ for (s32 i = 0; i < _active_location->trucks.length; i++) {
+ truck* emp = array_at(&_active_location->trucks, i);
+ HANDLE_TAB_ITEM_INTERACTION(false);
+
+ float icon_pad = item_h * 0.15;
+ float text_pad = icon_pad*2;
+ float icon_s = item_h - (icon_pad*2);
+ // icon
+ {
+ renderer->render_image(emp->logo, x + icon_pad, item_y + icon_pad, icon_s, icon_s);
+ renderer->render_rectangle(x + icon_pad + icon_s + icon_pad, item_y+(5*tab.scale), 1, item_h-+(10*tab.scale), COLOR_BUTTON);
+ }
+
+ // Name
+ {
+ char buffer[MAX_WORLD_LOCATION_NAME_LENGTH + 20];
+ sprintf(buffer, "%s, ID: #%d", emp->name, emp->id);
+
+ s32 text_x = x + item_h + text_pad;
+ s32 text_y = item_y + (item_h/2) - (fnt->px_h/2);
+ renderer->render_text(fnt, text_x+1, text_y+1, buffer, COLOR_TEXT_SHADOW);
+ renderer->render_text(fnt, text_x, text_y, buffer, COLOR_TEXT);
+ renderer->render_rectangle(x + item_h + item_part_w, item_y+(5*tab.scale), 1, item_h-+(10*tab.scale), COLOR_BUTTON);
+ }
+
+ // current location
+ {
+ char buffer[MAX_EMPLOYEE_NAME_LENGTH + 20];
+ if (emp->assigned_employee) sprintf(buffer, "Assigned to %s", emp->assigned_employee->name);
+ else strcpy(buffer, "No assignee");
+ s32 text_x = x + item_h + item_part_w*1 + text_pad;
+ s32 text_y = item_y + (item_h/2) - (fnt->px_h/2);
+ renderer->render_text(fnt, text_x+1, text_y+1, buffer, COLOR_TEXT_SHADOW);
+ renderer->render_text(fnt, text_x, text_y, buffer, COLOR_TEXT);
+ }
+ }
+}
+
+static void place_detail_draw_employees(platform_window* window, tab tab, float x, float y, float w, float h)
+{
+ HANDLE_TAB_START(_active_location->employees.length+2, 4);
+ HANDLE_TAB_SCROLL;
+
+ char info_buf[50];
+ sprintf(info_buf, "%d Employees currently on site.", _active_location->employees.length);
+ HANDLE_TAB_INFO(info_buf, "Hire", PLACE_DETAIL_SHOW_RESUMES);
+
+ s32 current_index = 0;
+ bool show_internal = true;
+ do_again:;
+
+ for (s32 i = 0; i <= _active_location->employees.length; i++) {
+ employee* emp = 0;
+ if (i > 0) {
+ emp = *(employee**)array_at(&_active_location->employees, i-1);
+ bool is_internal = (emp->original_location_id == _active_location->id);
+ if (show_internal != is_internal) continue;
+ }
+
+ HANDLE_TAB_ITEM_INTERACTIONX(i != 0, current_index);
+ if (i != 0 && hovered && is_left_clicked()) {
+ place_detail_show_employee_detail(emp);
+ audio_play_sound(snd_click, AUDIO_CHANNEL_SFX_1);
+ }
+ current_index++;
+
+ if (i == 0) {
+ char* buffer = show_internal ? "Internal" : "External";
+ s32 text_x = x + item_h;
+ s32 text_y = item_y + (item_h/2) - (fnt->px_h/2);
+ renderer->render_text(fnt, text_x+1, text_y+1, buffer, COLOR_TEXT_SHADOW);
+ renderer->render_text(fnt, text_x, text_y, buffer, COLOR_TEXT);
+ continue;
+ }
+
+ float icon_pad = item_h * 0.15;
+ float text_pad = icon_pad*2;
+ float icon_s = item_h - (icon_pad*2);
+ // icon
+ {
+ draw_employee_portrait(emp, x + icon_pad, item_y + icon_pad, icon_s, icon_s);
+ renderer->render_rectangle(x + icon_pad + icon_s + icon_pad, item_y+(5*tab.scale), 1, item_h-+(10*tab.scale), COLOR_BUTTON);
+ }
+
+ // Name
+ {
+ char buffer[MAX_EMPLOYEE_NAME_LENGTH + 20];
+ sprintf(buffer, "%s, ID: #%d", emp->name, emp->id);
+ s32 text_x = x + item_h + text_pad;
+ s32 text_y = item_y + (item_h/2) - (fnt->px_h/2);
+ renderer->render_text(fnt, text_x+1, text_y+1, buffer, COLOR_TEXT_SHADOW);
+ renderer->render_text(fnt, text_x, text_y, buffer, COLOR_TEXT);
+ renderer->render_rectangle(x + item_h + item_part_w, item_y+(5*tab.scale), 1, item_h-(10*tab.scale), COLOR_BUTTON);
+ }
+
+ // Name
+ {
+ char buffer[MAX_EMPLOYEE_NAME_LENGTH + 20];
+ if (emp->assigned_truck) sprintf(buffer, "Truck: %s, #%d", emp->assigned_truck->name, emp->assigned_truck->id);
+ else strcpy(buffer, "No assigned truck");
+ s32 text_x = x + item_h + item_part_w*1 + text_pad;
+ s32 text_y = item_y + (item_h/2) - (fnt->px_h/2);
+ renderer->render_text(fnt, text_x+1, text_y+1, buffer, COLOR_TEXT_SHADOW);
+ renderer->render_text(fnt, text_x, text_y, buffer, COLOR_TEXT);
+ renderer->render_rectangle(x + item_h + item_part_w*2, item_y+(5*tab.scale), 1, item_h-(10*tab.scale), COLOR_BUTTON);
+ }
+
+ // current location
+ {
+ char buffer[MAX_WORLD_LOCATION_NAME_LENGTH + 20];
+ if (emp->active_job_id != INVALID_ID) {
+ active_job* j = get_active_job_by_id(_active_world, emp->active_job_id);
+ job_endpoints endpoints = job_offer_get_endpoints(&j->offer);
+ sprintf(buffer, "Driving to %s", endpoints.dest->name);
+ }
+ else if (emp->current_location_id != _active_location->id) { // employee located at other location
+ sprintf(buffer, "Located at %s", get_world_location_by_id(_active_world, emp->current_location_id)->name);
+ }
+ else { // At original location
+ sprintf(buffer, "%s", "Idling");
+ }
+
+ s32 text_x = x + item_h + item_part_w*2 + text_pad;
+ s32 text_y = item_y + (item_h/2) - (fnt->px_h/2);
+ renderer->render_text(fnt, text_x+1, text_y+1, buffer, COLOR_TEXT_SHADOW);
+ renderer->render_text(fnt, text_x, text_y, buffer, COLOR_TEXT);
+ renderer->render_rectangle(x + item_h + item_part_w*3, item_y+(5*tab.scale), 1, item_h-+(10*tab.scale), COLOR_BUTTON);
+ }
+
+ // Happiness
+ {
+ s32 text_x = x + item_h + item_part_w*3 + text_pad;
+ s32 text_y = item_y + (item_h/2) - (fnt->px_h/2);
+
+ s32 stars = employee_calculate_happiness_stars(emp);
+ s32 textw = renderer->render_text(fnt, text_x, text_y, "Happiness: ", COLOR_TEXT);
+ text_x += (scale*5);
+ for (s32 i = 0; i < 5; i++)
+ {
+ if (i < stars) renderer->render_image(img_star, text_x + textw + (i*(fnt->px_h+2)), text_y, fnt->px_h, fnt->px_h);
+ else renderer->render_image_tint(img_star, text_x + textw + (i*(fnt->px_h+2)), text_y, fnt->px_h, fnt->px_h, rgba(255,255,255,30));
+ }
+
+ //renderer->render_rectangle(x + item_h + item_part_w*4, item_y+(5*tab.scale), 1, item_h-+(10*tab.scale), COLOR_BUTTON);
+ }
+ }
+
+ if (show_internal) {
+ show_internal = false;
+ goto do_again;
+ }
+}
+
+static void render_logo_at(image* img, s32 pad, s32 x, s32 y, s32 w, s32 h)
+{
+ if (img->width >= img->height)
+ {
+ float s_scale = w/(float)img->width;
+ float new_w = w - (pad*2);
+ float new_h = (img->height * s_scale) - (pad*2);
+ float new_x = x + w/2 - new_w/2;
+ float new_y = y + h/2 - new_h/2;
+ renderer->render_image(img,new_x,new_y,new_w,new_h);
+ }
+}
+
+static void place_detail_draw_active_dealer(platform_window* window, truck_dealer* dealer, float scale, s32 x, s32 y, s32 w, s32 h)
+{
+ button_render(scale, false, 0, x, y, w, h);
+ static truck_dealer* prev_dealer = 0;
+ if (prev_dealer != dealer) {
+ prev_dealer = dealer;
+ selected_truck_index = 0;
+ }
+
+ if (selected_truck_index < 0) selected_truck_index = 0;
+ if (selected_truck_index >= dealer->trucks.length) selected_truck_index = dealer->trucks.length-1;
+ truck* tr_curr = (selected_truck_index >= 0 && selected_truck_index <= dealer->trucks.length-1) ? array_at(&dealer->trucks, selected_truck_index) : 0;
+ truck* tr_prev = (selected_truck_index-1 >= 0) ? array_at(&dealer->trucks, selected_truck_index-1) : 0;
+ truck* tr_next = (selected_truck_index+1 <= dealer->trucks.length-1) ? array_at(&dealer->trucks, selected_truck_index+1) : 0;
+ if (!tr_curr) return;
+ s32 total_img_space = (w)/3 * 2;
+ s32 img_w_center = total_img_space/10*5;
+ s32 img_w_side = total_img_space/10*2.5;
+ s32 pad = 40 * scale;
+ s32 img_x = x;
+ s32 img_y = y;
+
+ static animation swap_animation = {0,0,0,0};
+ static animation purchase_animation = {0,0,0,0};
+ if (swap_animation.started) animation_update(&swap_animation);
+ if (purchase_animation.started) animation_update(&purchase_animation);
+ static s32 target_truck_index = 0;
+
+ truck* tn_prev = (target_truck_index-1 >= 0) ? array_at(&dealer->trucks, target_truck_index-1) : 0;
+ truck* tn_next = (target_truck_index+1 <= dealer->trucks.length-1) ? array_at(&dealer->trucks, target_truck_index+1) : 0;
+
+ // Truck images
+ {
+ s32 overlap = 50 * scale;
+
+ s32 y_side = img_y + (h / 2) - (img_w_side/2);
+ s32 x_left = img_x + overlap;
+ s32 y_left = y_side;
+ s32 w_left = img_w_side;
+ s32 h_left = img_w_side;
+
+ s32 x_right = img_x+img_w_side+img_w_center - overlap;
+ s32 y_right = y_side;
+ s32 w_right = img_w_side;
+ s32 h_right = img_w_side;
+
+ s32 x_center = img_x+img_w_side;
+ s32 y_center = img_y + (h / 2) - (img_w_center/2);
+ s32 w_center = img_w_center;
+ s32 h_center = img_w_center;
+
+ #define LI(_cx,_dx) (_cx + (_dx-_cx)*swap_animation.percentage)
+ #define LIP(_cx,_dx) (_cx + (_dx-_cx)*purchase_animation.percentage)
+ #define LI_TINT(_c,_d) rgba(255,255,255,_c + (_d-_c)*swap_animation.percentage)
+ #define LI_TINTP(_c,_d) rgba(255,255,255,_c + (_d-_c)*purchase_animation.percentage)
+
+ if (tr_prev) {
+ if (target_truck_index < selected_truck_index) {
+ color tint = LI_TINT(50, 255);
+ renderer->render_image_tint(tr_prev->logo,LI(x_left,x_center),LI(y_left,y_center),LI(w_left,w_center),LI(h_left,h_center), tint);
+
+ if (swap_animation.started && tn_prev) {
+ color tint = LI_TINT(0, 50);
+ renderer->render_image_tint(tn_prev->logo,x_left,y_left,w_left,h_left, tint);
+ }
+ }
+ else {
+ color tint = LI_TINT(50, 0);
+ renderer->render_image_tint(tr_prev->logo,x_left,y_left,w_left,h_left, tint);
+ }
+ }
+ if (tr_next) {
+ if (target_truck_index < selected_truck_index) {
+ color tint = LI_TINT(50, 0);
+ renderer->render_image_tint(tr_next->logo,x_right,y_right,w_right,h_right, tint);
+ }
+ else {
+ color tint = LI_TINT(50, 255);
+ renderer->render_image_tint(tr_next->logo,LI(x_right,x_center),LI(y_right,y_center),LI(w_right,w_center),LI(h_right,h_center), tint);
+
+ if (swap_animation.started && tn_next) {
+ color tint = LI_TINT(0, 50);
+ renderer->render_image_tint(tn_next->logo,x_right,y_right,w_right,h_right, tint);
+ }
+ }
+ }
+ if (!purchase_animation.started)
+ {
+ color tint = LI_TINT(255, 50);
+ if (target_truck_index < selected_truck_index)
+ renderer->render_image_tint(tr_curr->logo,LI(x_center,x_right),LI(y_center,y_right),LI(w_center,w_right),LI(h_center,h_right), tint);
+ else
+ renderer->render_image_tint(tr_curr->logo,LI(x_center,x_left),LI(y_center,y_left),LI(w_center,w_left),LI(h_center,h_left), tint);
+ }
+ else {
+ vec4 area = camera_get_target_rectangle(window);
+ s32 x_off = area.x;
+ s32 h_off = area.h/2;
+ s32 y_off = area.y+(area.h/2)-(h_off/2);
+ s32 w_off = h_off;
+
+ color tint = LI_TINTP(255, 0);
+ renderer->render_image_tint(tr_curr->logo,LIP(x_center,x_off),LIP(y_center,y_off),LIP(w_center,w_off),LIP(h_center,h_off), tint);
+ }
+ }
+
+ font* fnt = fnt_rd24;
+ s32 text_y = img_y + pad;
+ s32 text_x = x + total_img_space;
+
+ #define PUSH_TEXT(_str, _data)\
+ {char buf[50]; snprintf(buf,50,_str,_data);\
+ renderer->render_text(fnt, text_x+1, text_y+1, buf, COLOR_TEXT_SHADOW);\
+ renderer->render_text(fnt, text_x, text_y, buf, COLOR_TEXT);}\
+ text_y+=fnt->px_h+(10*scale);
+
+ PUSH_TEXT("Name: %s", tr_curr->name);
+ PUSH_TEXT("Power: %dhp", tr_curr->hp);
+ PUSH_TEXT("Price: $%d", tr_curr->price);
+ PUSH_TEXT("Fuel Capacity: %dL", tr_curr->fuelcapacity);
+ PUSH_TEXT("Torque %drpm", tr_curr->torque);
+ PUSH_TEXT("Fuel Usage %.1fL per 100Km", tr_curr->fuelusage);
+
+ #define TRUCK_SWAP_DELAY 200
+ #define TRUCK_PURCHASE_DELAY 800
+ s32 btn_size = 30*scale;
+ s32 btn_x = text_x;
+
+ button_type type1 = purchase_animation.started || swap_animation.started ? BUTTON_STATIC : (selected_truck_index > 0) ? BUTTON_ENABLED : BUTTON_DISABLED;
+ if (button_render(scale, type1, "<", btn_x, text_y, btn_size, btn_size)) {
+ if (!swap_animation.started) {
+ swap_animation = animation_create(TRUCK_SWAP_DELAY);
+ swap_animation.started = true;
+ target_truck_index = selected_truck_index-1;
+ }
+ }
+ btn_x += btn_size;
+ s32 center_btn_w = 100*scale;
+ button_type type2 = purchase_animation.started || swap_animation.started ? BUTTON_STATIC : BUTTON_ENABLED;
+ if (button_render(scale, type2, "purchase", btn_x, text_y, center_btn_w, btn_size)) {
+ ADD_EXPENSE(_active_world, _active_location, expenses_from_trucks, tr_curr->price)
+ add_truck_to_world_location(_active_world, _active_location, tr_curr);
+ purchase_animation = animation_create(TRUCK_PURCHASE_DELAY);
+ purchase_animation.started = true;
+ }
+ btn_x += center_btn_w;
+ button_type type3 = purchase_animation.started || swap_animation.started ? BUTTON_STATIC : (selected_truck_index < dealer->trucks.length-1) ? BUTTON_ENABLED : BUTTON_DISABLED;
+ if (button_render(scale, type3, ">", btn_x, text_y, btn_size, btn_size)) {
+ if (!swap_animation.started) {
+ swap_animation = animation_create(TRUCK_SWAP_DELAY);
+ swap_animation.started = true;
+ target_truck_index = selected_truck_index+1;
+ }
+ }
+ if (swap_animation.percentage == 1.0f) {
+ swap_animation = (animation){0,0,0,0};
+ selected_truck_index = target_truck_index;
+ }
+ if (purchase_animation.percentage == 1.0f) {
+ purchase_animation = (animation){0,0,0,0};
+ current_detail_state = PLACE_DETAIL_SHOW_MAIN;
+ selected_truck_index = 0;
+ active_dealer_index = 0;
+ }
+}
+
+void place_detail_show_schedule_with_highlighted_job(world_location* loc, scheduled_job* job, scheduled_job_time job_time) {
+ _goto_default_detail_state();
+ place_detail_set_active_location(loc);
+ game_set_active_scene(GAME_STATE_PLACE_DETAIL);
+
+ if (job) {
+ for (s32 i = 0; i < MAX_SHIPDAYS; i++) {
+ if (job_time.day == job->timeslots[i].day) {
+ _active_schedule_selected_job_index = i;
+ break;
+ }
+ }
+ }
+
+ _active_schedule_state = VIEWING;
+ _active_selected_scheduled_job = job;
+ selected_tab_index = PLACE_DETAIL_SHOW_MAIN;
+ current_detail_state = PLACE_DETAIL_SHOW_SCHEDULE;
+ tag_animation = animation_create(TAG_ANIMATION_DURATION);
+ tag_animation.started = true;
+}
+
+void place_detail_show_employee_detail(employee* emp) {
+ _goto_default_detail_state();
+ world_location* original_loc = get_world_location_by_id(_active_world, emp->original_location_id);
+ place_detail_set_active_location(original_loc);
+ game_set_active_scene(GAME_STATE_PLACE_DETAIL);
+ current_detail_state = PLACE_DETAIL_SHOW_EMPLOYEE;
+ _active_employee = emp;
+ tag_animation = animation_create(TAG_ANIMATION_DURATION);
+ tag_animation.started = true;
+}
+
+static s32 place_detail_draw_selected_employee_truck_selector(platform_window* window, s32 x, s32 y, s32 w, s32 h)
+{
+ s32 btn_w = 40*scale;
+ s32 pad = 30*scale;
+ s32 truck_h = h / 2;
+ s32 truck_w = truck_h;
+ s32 truck_x = x + (pad*2) + btn_w;
+ s32 truck_y = y + pad;
+
+ world_location* original_location = get_world_location_by_id(_active_world, _active_employee->original_location_id);
+
+ if (index_of_truck == INVALID_VAL) {
+ index_of_truck = _active_employee->assigned_truck ? ((void*)_active_employee->assigned_truck - (void*)original_location->trucks.data) / sizeof(truck) : -1;
+ }
+
+ s32 bg_h = h;
+ s32 bg_w = truck_w + (btn_w*2) + (pad*4);
+ button_draw_background(scale, x,y,bg_w,bg_h, COLOR_WHITE, COLOR_BUTTON);
+
+ static bool animation_going_left = false;
+
+ // Draw truck info
+ {
+ #define TRUCK_ANIMATION_OFFSET (animation_going_left ? -60*scale : 60*scale)
+ s32 truck_offset_x = TRUCK_ANIMATION_OFFSET - (TRUCK_ANIMATION_OFFSET*truck_swap_animation.percentage);
+ color truck_tint = AN_LI_TINT(COLOR_WHITE, truck_swap_animation);
+
+ truck* selected_truck = 0;
+ if (index_of_truck != -1) selected_truck = array_at(&original_location->trucks, index_of_truck);
+ renderer->render_image_tint(selected_truck ? selected_truck->logo : img_truck_unknown, truck_x+truck_offset_x, truck_y, truck_w, truck_h, truck_tint);
+ if (selected_truck) {
+ font* fnt = fnt_rd24;
+ char buf[40];
+ sprintf(buf, "%s, ID: #%d", selected_truck->name, selected_truck->id);
+ s32 text_w = renderer->calculate_text_width(fnt, buf);
+ s32 text_x = truck_x + (truck_w/2)-(text_w/2);
+ renderer->render_text(fnt, text_x+1+truck_offset_x, truck_y+truck_h+1, buf, COLOR_TEXT_SHADOW);
+ renderer->render_text(fnt, text_x+truck_offset_x,truck_y+truck_h, buf, COLOR_TEXT);
+ }
+ }
+
+ // Change truck
+ {
+ if (button_render(scale, BUTTON_ENABLED, "<", truck_x-btn_w-pad,truck_y, btn_w, truck_h)) {
+ s32 prev_index = index_of_truck;
+ index_of_truck = -1;
+
+ try_again:
+ for (s32 i = prev_index-1; i >= 0; i--) {
+ truck* emp = array_at(&original_location->trucks, i);
+ if (emp->assigned_employee == 0 || emp->assigned_employee == _active_employee) {
+ index_of_truck = i;
+ break;
+ }
+ }
+
+ if (prev_index == -1) {
+ prev_index = original_location->trucks.length;
+ goto try_again;
+ }
+
+ truck_swap_animation = animation_create(TRUCK_SWAP_ANIMATION_DURATION);
+ truck_swap_animation.started = true;
+ animation_going_left = true;
+ }
+ if (button_render(scale, BUTTON_ENABLED, ">", truck_x+truck_w+pad,truck_y, btn_w, truck_h)) {
+ s32 prev_index = index_of_truck;
+ index_of_truck = -1;
+ for (s32 i = prev_index+1; i < original_location->trucks.length; i++) {
+ truck* emp = array_at(&original_location->trucks, i);
+ if (emp->assigned_employee == 0 || emp->assigned_employee == _active_employee) {
+ index_of_truck = i;
+ break;
+ }
+ }
+
+ truck_swap_animation = animation_create(TRUCK_SWAP_ANIMATION_DURATION);
+ truck_swap_animation.started = true;
+ animation_going_left = false;
+ }
+ }
+
+
+ // Save changes button
+ {
+ s32 btn_h = 30*scale;
+ btn_w = 160*scale;
+ s32 btn_y = y + h - pad - btn_h;
+ s32 btn_x = x + (bg_w/2) - (btn_w/2);
+ if (button_render(scale, BUTTON_ENABLED, "Save Changes", btn_x, btn_y, btn_w, btn_h)) {
+ if (index_of_truck != -1) {
+ if (_active_employee->assigned_truck) _active_employee->assigned_truck->assigned_employee = 0;
+ _active_employee->assigned_truck = array_at(&_active_location->trucks, index_of_truck);
+ log_assert(_active_employee->assigned_truck->assigned_employee == 0 &&
+ _active_employee->assigned_truck->assigned_employee != _active_employee, "Truck already has assignee");
+
+ _active_employee->assigned_truck->assigned_employee = _active_employee;
+ }
+ else {
+ if (_active_employee->assigned_truck) _active_employee->assigned_truck->assigned_employee = 0;
+ _active_employee->assigned_truck = 0;
+ }
+
+ index_of_truck = INVALID_VAL;
+ current_detail_state = PLACE_DETAIL_SHOW_MAIN;
+ }
+ }
+
+ animation_update(&truck_swap_animation);
+ return bg_w;
+}
+
+static void place_detail_draw_selected_employee(platform_window* window)
+{
+ float offset = scale * 40.0;
+ s32 item_h = 82 * 0.4 * scale;
+ s32 h = (area.h * 0.6 - (offset*2)) + item_h;
+ s32 w = (area.w * 0.9 - (offset*2));
+ s32 x = offset + area.x + (area.w*0.05);
+ s32 pos_y = area.y + (area.w*0.12);
+ s32 y = pos_y;
+
+ s32 truck_selector_w = place_detail_draw_selected_employee_truck_selector(window, x, y, w, h);
+
+ {
+ font* fnt = fnt_rd24;
+
+ s32 info_panel_x = x + truck_selector_w+offset;
+ s32 info_panel_w = w - offset - truck_selector_w;
+ s32 pad = 30*scale;
+
+ s32 text_y = y + pad;
+ s32 text_x = info_panel_x + pad;
+ s32 text_offset = fnt->px_h + (10*scale);
+ s32 info_h = (h/2)-(pad/2);
+ button_draw_background(scale, info_panel_x, y, info_panel_w, info_h, COLOR_WHITE, COLOR_BUTTON);
+
+ s32 portrait_h = (info_h)-(pad*2);
+ draw_employee_portrait(_active_employee, text_x, text_y, portrait_h, portrait_h);
+
+ text_x += portrait_h + pad;
+
+ #define PUSH_INFO_TEXT(_fmt, _data)\
+ {\
+ char buf[100];\
+ sprintf(buf, _fmt, _data);\
+ renderer->render_text(fnt, text_x, text_y, buf, COLOR_TEXT);\
+ text_y += text_offset;\
+ }
+
+ PUSH_INFO_TEXT("Name: %s", _active_employee->name);
+ PUSH_INFO_TEXT("Age: %d", _active_employee->age);
+ PUSH_INFO_TEXT("Experience: %d years", _active_employee->experience);
+ PUSH_INFO_TEXT("Salary: $%.2f/month", _active_employee->salary);
+
+ // Happiness
+ {
+ s32 stars = employee_calculate_happiness_stars(_active_employee);
+ s32 textw = renderer->render_text(fnt, text_x, text_y, "Happiness: ", COLOR_TEXT);
+ for (s32 i = 0; i < 5; i++)
+ {
+ if (i < stars) renderer->render_image(img_star, text_x + textw + (i*(fnt->px_h+2)), text_y, fnt->px_h, fnt->px_h);
+ else renderer->render_image_tint(img_star, text_x + textw + (i*(fnt->px_h+2)), text_y, fnt->px_h, fnt->px_h, rgba(255,255,255,30));
+ }
+ text_y += text_offset;
+ }
+
+ #define SALARY_RAISE_INCREASE 70
+ // Buttons
+ {
+ s32 pad_between_items = 10 * scale;
+ s32 button_w = 164 * scale;
+ s32 button_h = 37 * scale;
+ s32 btn_y = y+info_h+pad+info_h-button_h;
+ s32 btn_x = x + w - button_w;
+ if (button_render(scale, BUTTON_ENABLED, "End Contract", btn_x, btn_y, button_w, button_h)) {
+ audio_play_sound(snd_click3, AUDIO_CHANNEL_SFX_1);
+ end_contract_with_employee(_active_world, _active_employee);
+ _active_employee = 0;
+ _goto_default_detail_state();
+ }
+ btn_y -= (button_h+pad_between_items);
+
+ if (button_render(scale, BUTTON_ENABLED, "Give Raise", btn_x, btn_y, button_w, button_h)) {
+ audio_play_sound(snd_click2, AUDIO_CHANNEL_SFX_1);
+ _active_employee->salary += SALARY_RAISE_INCREASE;
+ }
+ btn_y -= (button_h+pad_between_items);
+ }
+ }
+}
+
+static void place_detail_draw_schedule_employee_options(platform_window* window, s32 pad, float scale, s32 x, s32 y, s32 w, s32 h)
+{
+ bool viewing = (_active_schedule_state == VIEWING && !_active_selected_scheduled_job);
+ if (viewing) return;
+
+ job_endpoints endpoints = job_offer_get_endpoints(&_active_scheduling_job.offer);
+ bool enabled = endpoints.dest->is_owned;
+
+ if (enabled) button_draw_background(scale, x,y,w,h, COLOR_WHITE, COLOR_BUTTON);
+ else button_draw_background(scale, x,y,w,h, COLOR_BUTTON_DISABLED_TINT, COLOR_BUTTON_DISABLED);
+
+ s32 checkbox_s = h/3;
+ scheduled_job_time selected_timeslot = _active_scheduling_job.timeslots[_active_schedule_selected_job_index];
+ if (_active_schedule_state == VIEWING) selected_timeslot = _active_selected_scheduled_job->timeslots[_active_schedule_selected_job_index];
+
+ char* check_txt = selected_timeslot.stay_at_destination ? "X" : "";
+ if (button_render(scale, enabled && _active_schedule_state != VIEWING ? BUTTON_ENABLED : BUTTON_DISABLED, check_txt, x+pad,y+(h/2)-(checkbox_s/2),checkbox_s,checkbox_s)) {
+ _active_scheduling_job.timeslots[_active_schedule_selected_job_index].stay_at_destination = !selected_timeslot.stay_at_destination;
+ }
+
+ font* fnt = fnt_rd20;
+ s32 textx = x + pad + checkbox_s + pad/2;
+ s32 texty = y+(h/2)-(fnt->px_h/2);
+ renderer->render_text(fnt, textx, texty, "Stay at destination", COLOR_TEXT);
+
+ if (!enabled) {
+ s32 icon_s = h/2;
+ renderer->render_image(img_lock, x + (w/2)-(icon_s/2), y + (h/2)-(icon_s/2), icon_s, icon_s);
+ }
+}
+
+static void place_detail_draw_schedule_employee_selector(platform_window* window, s32 pad, float scale, s32 x, s32 y, s32 w, s32 h)
+{
+ if (_active_schedule_state == VIEWING && !_active_selected_scheduled_job) {
+ button_render(scale, BUTTON_STATIC, 0, x,y,w,h);
+ font* fnt = fnt_rd20;
+ s32 texty = y + (h/2)-(fnt->px_h/2);
+ s32 textx = x + pad;
+ renderer->render_text(fnt, textx, texty, "Select a timeslot for details.", COLOR_TEXT);
+ }
+ else {
+ scheduled_job_time selected_timeslot = _active_scheduling_job.timeslots[_active_schedule_selected_job_index];
+
+ employee* selected_employee = selected_timeslot.assignee;
+ bool is_viewing_existing_timeslot = _active_selected_scheduled_job && _active_schedule_state == VIEWING;
+ if (is_viewing_existing_timeslot) {
+ selected_employee = _active_selected_scheduled_job->timeslots[_active_schedule_selected_job_index].assignee;
+ }
+
+ // When timeslot changes, set selected employee id as keyboard input text.
+ static s32 prev_selected_job_index = 0;
+ if (is_viewing_existing_timeslot) prev_selected_job_index = -1;
+ if (prev_selected_job_index != _active_schedule_selected_job_index) {
+ prev_selected_job_index = _active_schedule_selected_job_index;
+ if (selected_employee) {
+ char idbuf[20];
+ sprintf(idbuf, "%d", selected_employee->id);
+ keyboard_set_input_text(idbuf);
+ }
+ else {
+ keyboard_set_input_text("");
+ }
+ }
+
+ _active_scheduling_job.timeslots[_active_schedule_selected_job_index].assignee =
+ employee_selector_render(window, scale, !is_viewing_existing_timeslot, selected_employee,x,y,w,h, employee_selector_animation, &_active_scheduling_job);
+ animation_update(&employee_selector_animation);
+ }
+}
+
+static bool scheduling_job_is_correct()
+{
+ for (s32 i = 0; i < _active_scheduling_job.offer.shipday_count; i++) {
+ scheduled_job_time scheduled_time = _active_scheduling_job.timeslots[i];
+ if (!scheduled_time.assignee) { return false; }
+ if (scheduled_time.timeslot == -1) { return false; }
+ }
+ return true;
+}
+
+static void draw_duration_of_scheduled_job_entry(scheduled_job_time scheduled_time, s32 hour_w, s32 tx, s32 ty, s32 timeslot_w, s32 x, s32 y, s32 timeslot_h, color tile_color)
+{
+ #define TILE_X(_x) (x+hour_w+(timeslot_w*_x)+1)
+ #define TILE_Y(_y) (y+(timeslot_h*(CDAYTORDAY(_y)))+1)
+
+ s32 duration_of_job_measured_in_timeslots_max = ceil((_active_scheduling_job.offer.duration_sec_max/60.0f) / (60.0f/TIME_SLOTS_PER_HOUR));
+ if (!scheduled_time.stay_at_destination) duration_of_job_measured_in_timeslots_max*=2;
+ log_assert(duration_of_job_measured_in_timeslots_max < 36*TIME_SLOTS_PER_HOUR, "Multi-day trip not supported yet in schedule preview.");
+
+ s32 slots_outside_of_schedule = (24-(WORK_HOUR_END-WORK_HOUR_START))*TIME_SLOTS_PER_HOUR;
+ s32 overflow_to_next_day = -(((TIME_SLOTS_PER_DAY-scheduled_time.timeslot) -
+ (duration_of_job_measured_in_timeslots_max)) +
+ slots_outside_of_schedule);
+ color tile_color_bg = tile_color;
+ tile_color_bg.a = 50;
+
+ s32 highlight_w = timeslot_w*duration_of_job_measured_in_timeslots_max;
+ if (tx+highlight_w > x+(timeslot_w*(TIME_SLOTS_PER_DAY+TIME_SLOTS_PER_HOUR)))
+ highlight_w -= (tx+highlight_w) - (x+(timeslot_w*(TIME_SLOTS_PER_DAY+TIME_SLOTS_PER_HOUR)));
+ renderer->render_rectangle(tx, ty, highlight_w, timeslot_h, tile_color_bg);
+ s32 highlight_y = ty+timeslot_h;
+ if (scheduled_time.day == SUNDAY) highlight_y = TILE_Y(MONDAY);
+ if (overflow_to_next_day > 0) renderer->render_rectangle(TILE_X(0), highlight_y, timeslot_w*overflow_to_next_day, timeslot_h, tile_color_bg);
+}
+
+static void place_detail_draw_schedule(platform_window* window)
+{
+ float offset = scale * 40.0;
+ s32 item_h = 82 * 0.4 * scale;
+ s32 h = (area.h * 0.6 - (offset*2)) + item_h;
+ s32 pad_between_items = 10 * scale;
+ s32 employee_select_h = (h/4);
+ h -= employee_select_h + pad_between_items;
+ s32 w = (area.w * 0.9 - (offset*2));
+ s32 x = offset + area.x + (area.w*0.05);
+ s32 pos_y = area.y + (area.w*0.12);
+ s32 y = pos_y;
+ s32 btn_pad = pad_between_items/2;
+
+ s32 button_w = 124 * scale;
+ s32 button_h = (employee_select_h-btn_pad)/2;
+ s32 schedule_pad = 20*scale;
+
+ #define COLS (TIME_SLOTS_PER_DAY+TIME_SLOTS_PER_HOUR)
+ #define ROWS 8
+ s32 timeslot_w = (w-schedule_pad*2)/COLS;
+ s32 timeslot_h = (h-schedule_pad*2)/ROWS;
+ s32 hour_w = timeslot_w*4;
+ s32 new_w = (timeslot_w)*COLS+schedule_pad*2;
+ s32 new_h = (timeslot_h)*ROWS+schedule_pad*2;
+ s32 off_x = w - new_w;
+ s32 off_y = h - new_h;
+ w = new_w;
+ h = new_h;
+ x += off_x/2;
+ y += off_y/2;
+
+ s32 employee_select_y = y + h + pad_between_items;
+ s32 btn_y = y+h+pad_between_items;
+ if (_active_schedule_state == SCHEDULING_JOB || _active_schedule_state == RESCHEDULING_JOB)
+ {
+ button_type type = scheduling_job_is_correct() ? BUTTON_ENABLED : BUTTON_DISABLED;
+ if (button_render(scale, type, "Accept", x-button_w+w, btn_y, button_w, button_h)) {
+ audio_play_sound(snd_click2, AUDIO_CHANNEL_SFX_1);
+ if (_active_schedule_state == SCHEDULING_JOB)
+ array_push(&_active_location->schedule.jobs, &_active_scheduling_job);
+ else if (_active_schedule_state == RESCHEDULING_JOB)
+ *_active_selected_scheduled_job = _active_scheduling_job;
+
+ job_offer* offer = get_job_offer_by_id(_active_location, _active_scheduling_job.offer.id);
+ if (offer) array_remove_by(&_active_location->job_offers, offer);
+
+ _goto_default_detail_state();
+ }
+ btn_y += btn_pad + button_h;
+ if (button_render(scale, BUTTON_ENABLED, "Cancel", x-button_w+w, btn_y, button_w, button_h)) {
+ _goto_default_detail_state();
+ }
+ }
+ else if (_active_schedule_state == VIEWING && _active_selected_scheduled_job) {
+ if (button_render(scale, BUTTON_ENABLED, "Reschedule", x-button_w+w, btn_y, button_w, button_h)) {
+ _active_schedule_state = RESCHEDULING_JOB;
+ _active_scheduling_job = *_active_selected_scheduled_job;
+ }
+ }
+
+ s32 options_w = 230*scale;
+ s32 selector_w = w-options_w-(pad_between_items*2)-button_w;
+
+ button_render(scale, BUTTON_STATIC, 0, x,y,w,h);
+
+ x+=schedule_pad;
+ y+=schedule_pad;
+ w-=schedule_pad*2;
+ h-=schedule_pad*2;
+
+ #define TILE_X(_x) (x+hour_w+(timeslot_w*_x)+1)
+ #define TILE_Y(_y) (y+(timeslot_h*(CDAYTORDAY(_y)))+1)
+
+
+ vec2f hovered_tile = (vec2f){(s32)((_global_mouse.x-x)/timeslot_w)-TIME_SLOTS_PER_HOUR, (s32)((_global_mouse.y-y)/timeslot_h)};
+ s32 hovered_day = RDAYTOCDAY((s32)hovered_tile.y);
+ scheduled_job* existing_job_at_hovered_tile = get_scheduled_job_at_time(hovered_day, (s32)hovered_tile.x);
+ font* fnt = fnt_rd24;
+
+ static bool dragging_tile = false;
+
+ // newly scheduling job
+ if (_active_schedule_state == SCHEDULING_JOB || _active_schedule_state == RESCHEDULING_JOB)
+ {
+ for (s32 i = 0; i < _active_scheduling_job.offer.shipday_count; i++) {
+ scheduled_job_time scheduled_time = _active_scheduling_job.timeslots[i];
+ s32 dday = CDAYTORDAY(scheduled_time.day);
+ renderer->render_rectangle(x+hour_w, y+(dday*timeslot_h), w-hour_w, timeslot_h, COLOR_SCHEDULE_ROW_ACTIVE);
+
+ bool being_hovered = false;
+ bool valid_tile = (!existing_job_at_hovered_tile || (_active_schedule_state == RESCHEDULING_JOB && existing_job_at_hovered_tile == _active_selected_scheduled_job));
+ if (valid_tile && hovered_tile.x >= 0 && hovered_tile.x < TIME_SLOTS_PER_DAY && hovered_tile.y == dday) { // If in current row.
+
+ // Interaction.
+ if (_active_scheduling_job.timeslots[i].timeslot == hovered_tile.x) {
+ platform_set_cursor(window, CURSOR_POINTER);
+ being_hovered = true;
+
+ // Check if player started to drag.
+ if (is_left_clicked_peak()) { // is_left_clicked_peak instead of is_left_clicked so employee selector can reset input.
+ dragging_tile = true;
+ _active_schedule_selected_job_index = i;
+ audio_play_sound(snd_click, AUDIO_CHANNEL_SFX_1);
+ }
+ }
+ }
+
+ s32 tx = TILE_X(scheduled_time.timeslot);
+ s32 ty = TILE_Y(scheduled_time.day);
+
+ bool is_dragging_current_tile = (dragging_tile && _active_schedule_selected_job_index == i);
+ color tile_color = (_active_schedule_selected_job_index == i) ? COLOR_SCHEDULE_TILE_HOVERED : COLOR_SCHEDULE_TILE_SELECTED;
+
+ // Handle dragging.
+ if (being_hovered || is_dragging_current_tile) {
+
+ // Get index of new timeslot.
+ s32 index_to_set = hovered_tile.x;
+ s32 possible_tile_left = find_empty_timeslot_for_day(scheduled_time.day);
+ s32 possible_tile_right = find_empty_timeslot_for_day_right_to_left(scheduled_time.day);
+ if (index_to_set < 0) index_to_set = possible_tile_left;
+ if (index_to_set >= TIME_SLOTS_PER_DAY) index_to_set = possible_tile_right;
+
+ // Set new timeslot.
+ if (is_dragging_current_tile) {
+ platform_set_cursor(window, CURSOR_DRAG);
+
+ scheduled_job* existing_job_at_tile = get_scheduled_job_at_time(scheduled_time.day, index_to_set);
+ if (existing_job_at_tile == _active_selected_scheduled_job) existing_job_at_tile = 0; // Make sure reschedule can be set at current location.
+ if (index_to_set != -1 && !existing_job_at_tile) _active_scheduling_job.timeslots[i].timeslot = index_to_set;
+
+ if (!is_left_down()) {
+ dragging_tile = false;
+ }
+
+ tx = TILE_X(_active_scheduling_job.timeslots[i].timeslot);
+ ty = TILE_Y(scheduled_time.day);
+ }
+
+ // Draw duration of job.
+ draw_duration_of_scheduled_job_entry(scheduled_time, hour_w, tx, ty, timeslot_w, x, y, timeslot_h, tile_color);
+
+ // Draw left and right arrow.
+ s32 icon_s = timeslot_w*0.7f;
+ s32 icon_y = ty + (timeslot_h-icon_s)/2;
+ s32 icon_pad = (3*scale);
+ if (possible_tile_left != -1 && possible_tile_left != index_to_set) {
+ renderer->render_image_tint(img_arrow_left_rounded, tx - icon_s - icon_pad-1, icon_y, icon_s+2, icon_s,COLOR_SCHEDULE_BORDER_THIN);
+ renderer->render_image_tint(img_arrow_left_rounded, tx - icon_s - icon_pad, icon_y, icon_s, icon_s,tile_color);
+ }
+ if (possible_tile_right != -1 && possible_tile_right != index_to_set) {
+ gl_render_set_rotation(M_PI);
+ renderer->render_image_tint(img_arrow_left_rounded, tx + timeslot_w + icon_pad-1, icon_y, icon_s+2, icon_s,COLOR_SCHEDULE_BORDER_THIN);
+ renderer->render_image_tint(img_arrow_left_rounded, tx + timeslot_w + icon_pad, icon_y, icon_s, icon_s,tile_color);
+ gl_render_set_rotation(0.0f);
+ }
+ }
+
+ // Draw tile.
+ renderer->render_rectangle(tx, ty, timeslot_w, timeslot_h, tile_color);
+
+ // Status icon if tile being scheduled.
+ s32 icon_size = timeslot_w*0.6f;
+ if (scheduled_time.assignee == 0) {
+ renderer->render_image(img_questionmark, tx+(timeslot_w/2)-(icon_size/2),ty+(timeslot_h/5),icon_size,icon_size);
+ }
+ else {
+ renderer->render_image(img_checkmark, tx+(timeslot_w/2)-(icon_size/2),ty+(timeslot_h/5),icon_size,icon_size);
+ }
+ }
+ }
+
+ // existing jobs
+ {
+ for (s32 i = 0; i < _active_location->schedule.jobs.length; i++) {
+ scheduled_job* job = array_at(&_active_location->schedule.jobs, i);
+
+ // if rescheduling, dont show the timeslots for the job being rescheduled.
+ if (_active_schedule_state == RESCHEDULING_JOB && _active_selected_scheduled_job == job) {
+ continue;
+ }
+
+ for (s32 t = 0; t < job->offer.shipday_count; t++) {
+ scheduled_job_time scheduled_time = job->timeslots[t];
+ s32 tx = TILE_X(scheduled_time.timeslot);
+ s32 ty = TILE_Y(scheduled_time.day);
+ bool is_selected_job = (_active_selected_scheduled_job == job);
+ bool is_selected = (is_selected_job && _active_schedule_selected_job_index == t);
+ bool is_inspecting_employee = false;
+
+ if (_active_selected_scheduled_job != 0 && scheduled_time.assignee == _active_selected_scheduled_job->timeslots[_active_schedule_selected_job_index].assignee && !is_selected) {
+ is_inspecting_employee = true;
+ }
+
+ color tc = COLOR_SCHEDULE_TILE_FIXED;
+ if (is_inspecting_employee) {
+ tc = COLOR_SCHEDULE_TILE_HIGHLIGHTED;
+ }
+ else if (is_selected) {
+ tc = COLOR_SCHEDULE_TILE_HOVERED;
+ }
+ if (scheduled_time.assignee == 0) tc = is_selected ? COLOR_SCHEDULE_TILE_HOVERED : COLOR_SCHEDULE_TILE_INVALID;
+ if (is_inspecting_employee || is_selected) {
+ // Draw duration of job.
+ draw_duration_of_scheduled_job_entry(scheduled_time, hour_w, tx, ty, timeslot_w, x, y, timeslot_h, tc);
+ }
+
+ renderer->render_rectangle(tx, ty, timeslot_w, timeslot_h, tc);
+ if (is_selected_job && !is_selected) {
+ s32 dotsize = timeslot_w/3;
+ s32 dotoffsetx = (timeslot_w/2)-(dotsize/2);
+ s32 dotoffsety = timeslot_h-(timeslot_h/4)-(dotsize/2);
+ renderer->render_image_tint(img_dot, tx+dotoffsetx,ty+dotoffsety,dotsize,dotsize,COLOR_SCHEDULE_TILE_HOVERED);
+ }
+
+ // Status icon
+ s32 icon_size = timeslot_w*0.6f;
+ if (scheduled_time.assignee == 0) {
+ renderer->render_image(img_questionmark, tx+(timeslot_w/2)-(icon_size/2),ty+(timeslot_h/5),icon_size,icon_size);
+ }
+
+ #if 0
+ color col = COLOR_SCHEDULE_TILE_FIXED;
+ if (is_selected_job) col = COLOR_SCHEDULE_TILE_HIGHLIGHTED;
+ if (is_selected) col = COLOR_SCHEDULE_TILE_HOVERED;
+ renderer->render_rectangle(tx, ty, timeslot_w, timeslot_h, col);
+ #endif
+
+ #if 0
+ // Viewing job info box.
+ if (is_selected) {
+ s32 tooltip_offset_from_tile = 15*scale;
+ s32 tooltip_pad = 15*scale;
+ s32 tooltip_h = fnt_s->px_h + (tooltip_pad*2);
+ s32 tooltip_x = tx + timeslot_w + tooltip_offset_from_tile;
+ s32 tooltip_y = ty + (timeslot_h/2)-(tooltip_h/2);
+ // if (tx > area.x + (area.w/2))
+
+ char* tooltip_text = "Maastricht -> Machester";
+ s32 tooltip_text_w = renderer->calculate_text_width(fnt_s, tooltip_text);
+ s32 tooltip_w = tooltip_text_w + (tooltip_pad*2);
+
+ renderer->set_render_depth(5);
+ button_draw_background(scale, tooltip_x,tooltip_y,tooltip_w,tooltip_h, COLOR_WHITE, COLOR_BUTTON);
+ renderer->render_text(fnt_s, tooltip_x+tooltip_pad, tooltip_y+tooltip_pad, tooltip_text, COLOR_TEXT);
+ renderer->set_render_depth(4);
+ }
+ #endif
+
+ if (_active_schedule_state == VIEWING && mouse_interacts(tx,ty,timeslot_w,timeslot_h)) {
+ platform_set_cursor(window, CURSOR_POINTER);
+
+ if (is_left_clicked()) {
+ scheduled_job* prev_selected_scheduled_job = _active_selected_scheduled_job;
+ _active_schedule_state = VIEWING;
+ _active_selected_scheduled_job = job;
+ _active_schedule_selected_job_index = t;
+ audio_play_sound(snd_click, AUDIO_CHANNEL_SFX_1);
+
+ if (_active_selected_scheduled_job != prev_selected_scheduled_job) {
+ tag_animation = animation_create(TAG_ANIMATION_DURATION);
+ tag_animation.started = true;
+ }
+
+ employee_selector_animation = animation_create(EMPLOYEE_SELECTOR_ANIMATION_DURATION);
+ employee_selector_animation.started = true;
+ }
+ }
+ }
+ }
+ }
+
+ // rows
+ for (s32 ty = 0; ty < ROWS; ty++)
+ {
+ s32 row_y = y + (ty*timeslot_h);
+
+ if (ty==0) {
+ // COLOR_SCHEDULE_ROW_ACTIVE
+ renderer->render_rectangle(x, y, timeslot_w*4, h, COLOR_SCHEDULE_BG);
+ renderer->render_rectangle(x, y, w, timeslot_h, COLOR_SCHEDULE_BG);
+ }
+ else {
+ // Days
+ {
+ char buf[20];
+ switch(ty) {
+ case 1: strcpy(buf, "Mon"); break;
+ case 2: strcpy(buf, "Tue"); break;
+ case 3: strcpy(buf, "Wed"); break;
+ case 4: strcpy(buf, "Thu"); break;
+ case 5: strcpy(buf, "Fri"); break;
+ case 6: strcpy(buf, "Sat"); break;
+ case 7: strcpy(buf, "Sun"); break;
+ }
+
+ s32 tw = renderer->calculate_text_width(fnt, buf);
+ s32 textx = x + (hour_w/2)-(tw/2);
+ s32 texty = row_y + (timeslot_h/2)-(fnt->px_h/2);
+ renderer->render_text(fnt, textx, texty, buf, COLOR_TEXT);
+ }
+ }
+ renderer->render_rectangle(x, row_y, w, 1, COLOR_SCHEDULE_BORDER);
+ }
+
+ // cols
+ for (s32 tx = 0; tx < COLS; tx++)
+ {
+ s32 row_x = x + (tx*timeslot_w);
+ bool is_big_line = tx % 4 == 0;
+ if (tx>=4 || is_big_line) {
+ s32 liney = y;
+ s32 lineh = h;
+ if (!is_big_line) {
+ liney = y+timeslot_h;
+ lineh = h-timeslot_h;
+ }
+ renderer->render_rectangle(row_x, liney, 1, lineh, (is_big_line) ? COLOR_SCHEDULE_BORDER : COLOR_SCHEDULE_BORDER_THIN);
+ }
+ if (tx!=0 && is_big_line) {
+
+ char buf[20];
+ sprintf(buf, "%d:00", WORK_HOUR_START+((tx-1)/TIME_SLOTS_PER_HOUR));
+ s32 tw = renderer->calculate_text_width(fnt, buf);
+ s32 textx = row_x + (hour_w/2)-(tw/2);
+ s32 texty = y + (timeslot_h/2)-(fnt->px_h/2);
+ renderer->render_text(fnt, textx, texty, buf, COLOR_TEXT);
+ }
+ }
+
+ // outline
+ renderer->render_rectangle_outline(x,y,w+1,h+1,1,COLOR_SCHEDULE_BORDER);
+
+ x -= schedule_pad;
+ place_detail_draw_schedule_employee_options(window, schedule_pad, scale, x + selector_w + pad_between_items, employee_select_y, options_w, employee_select_h);
+ place_detail_draw_schedule_employee_selector(window, schedule_pad, scale, x, employee_select_y, selector_w, employee_select_h);
+
+ // Deselect selected job.
+ if (mouse_interacts(x+schedule_pad,y,w+1,h+1) && is_left_clicked()) {
+ if (_active_schedule_state == VIEWING) {
+ _active_selected_scheduled_job = 0;
+ }
+ }
+}
+
+static void place_detail_draw_dealers(platform_window* window)
+{
+ float offset = scale * 40.0;
+ s32 item_h = 82 * 0.4 * scale;
+ s32 h = (area.h * 0.6 - (offset*2)) + item_h;
+ s32 w = (area.w * 0.9 - (offset*2));
+ s32 x = offset + area.x + (area.w*0.05);
+ s32 pos_y = area.y + (area.w*0.12);
+ s32 y = pos_y;
+
+ s32 item_count = _active_world->truck_dealers.length;
+
+ s32 spacing = 6;
+ s32 logo_space_w = w / 7.0f;
+ s32 logo_space_h = (h-(spacing*(item_count-1))) / 3.0f;
+
+ for (s32 i = 0; i < item_count; i++)
+ {
+ truck_dealer* d = array_at(&_active_world->truck_dealers, i);
+ if(button_render(scale, true, 0, x, y + (logo_space_h*i)+spacing*i, logo_space_w, logo_space_h)) active_dealer_index = i;
+ render_logo_at(d->logo, 40 * scale, x, y + (logo_space_h*i)+spacing*i, logo_space_w, logo_space_h);
+ }
+
+ if (active_dealer_index < _active_world->truck_dealers.length) {
+ truck_dealer* d = array_at(&_active_world->truck_dealers, active_dealer_index);
+
+ s32 panel_x = x + spacing + logo_space_w;
+ s32 panel_w = (x + w) - panel_x;
+ place_detail_draw_active_dealer(window, d, scale, panel_x, y, panel_w, h);
+ }
+}
+
+static void place_detail_draw_resumes(platform_window* window)
+{
+ s32 screen_center_x = area.x + (area.w/2);
+ s32 screen_center_y = area.y + (area.h/2);
+ s32 panel_w = area.w*0.75;
+ s32 panel_h = area.h*0.75;
+ s32 panel_x = screen_center_x - (panel_w/2);
+ s32 panel_y = screen_center_y - (panel_h/2);
+
+ bool resume_available = _active_location->resumes.length;
+
+ font* fntb = fnt_rd32;
+ font* fnt = fnt_rd16;
+
+ if (!resume_available) {
+ char* message = "No resumes available.";
+ s32 text_w = renderer->calculate_text_width(fnt, message);
+ s32 text_x = screen_center_x - (text_w/2);
+ s32 text_y = screen_center_y - (fnt->px_h/2);
+ renderer->render_text(fnt, text_x+1, text_y+1, message, COLOR_TEXT_SHADOW);
+ renderer->render_text(fnt, text_x, text_y, message, COLOR_TEXT);
+ }
+ else {
+ s32 panel_w = area.w*0.2;
+ s32 panel_h = area.h*0.3;
+ panel_x = screen_center_x - (panel_w/2);
+ panel_y = screen_center_y - (panel_h/2);
+ resume* resume = array_at(&_active_location->resumes, 0);
+
+ float opacity = 1.0f - resume->animation.percentage;
+ if (opacity < 0.0f) opacity = 0.0f;
+
+ color color_bg = COLOR_WHITE;
+ color_bg.a = opacity*255;
+
+ renderer->render_image_tint(img_resume, panel_x, panel_y, panel_w, panel_h, color_bg);
+
+ char buffer[100];
+ sprintf(buffer, "Resume");
+ s32 text_x = panel_x + (panel_w/2) - (renderer->calculate_text_width(fntb, buffer)/2);
+ s32 text_y = panel_y + (20*scale);
+
+ color t_shadow = COLOR_TEXT_SHADOW;
+ t_shadow.a = opacity*255;
+
+ renderer->render_text(fntb, text_x, text_y, buffer, t_shadow);
+
+ text_x = panel_x + (20*scale);
+ text_y += fntb->px_h+(15*scale);
+
+ sprintf(buffer, "Name: %s", resume->employee->name);
+ renderer->render_text(fnt, text_x, text_y, buffer, t_shadow);
+
+ text_y += fnt->px_h + 8*scale;
+ sprintf(buffer, "Age: %d", resume->employee->age);
+ renderer->render_text(fnt, text_x, text_y, buffer, t_shadow);
+
+ text_y += fnt->px_h + 8*scale;
+ sprintf(buffer, "Experience: %d years", resume->employee->experience);
+ renderer->render_text(fnt, text_x, text_y, buffer, t_shadow);
+
+ text_y += fnt->px_h + 8*scale;
+ sprintf(buffer, "Salary: $%.2f/month", resume->employee->salary);
+ renderer->render_text(fnt, text_x, text_y, buffer, t_shadow);
+
+ struct tm* curr_time = gmtime(&resume->expire_date);
+ text_y += fnt->px_h + 8*scale;
+ strftime(buffer, 50, "Offer open untill %d/%m/%Y", curr_time);
+ renderer->render_text(fnt, text_x, text_y, buffer, t_shadow);
+ text_y += fnt->px_h + 8*scale;
+
+ s32 signature_h = ((panel_y+panel_h)-text_y)*0.6;
+ text_y += (10 * scale);
+ renderer->render_image(img_signature, text_x, text_y, signature_h*2, signature_h);
+
+ if (!resume->animation.started) {
+ s32 button_w = 124 * scale;
+ s32 button_h = 37 * scale;
+ s32 pad = 20 * scale;
+ s32 btn_y = panel_y + panel_h + pad;
+ s32 btn_x = panel_x;
+ if (button_render(scale, true, "Hire", btn_x, btn_y, button_w, button_h)) {
+ audio_play_sound(snd_click2, AUDIO_CHANNEL_SFX_1);
+ resume->animation.started = true;
+ resume->hired = true;
+ resume->employee->id = _active_world->next_id++;
+ resume->employee->hire_date = _active_world->current_time;
+ add_employee_to_world_location(_active_location, resume->employee);
+ }
+ btn_x = panel_x + panel_w - button_w;
+ if (button_render(scale, true, "Decline", btn_x, btn_y, button_w, button_h)) {
+ audio_play_sound(snd_click3, AUDIO_CHANNEL_SFX_1);
+ resume->animation.started = true;
+ resume->hired = false;
+ }
+ }
+ else {
+ s32 pad = scale * 50;
+ s32 stamp_x = panel_x + pad;
+ s32 stamp_y = panel_y + pad;
+ s32 stamp_w = panel_w - (pad*2);
+ s32 stamp_h = panel_h - (pad*2);
+ if (resume->hired) {
+ renderer->render_image_tint(img_hired, stamp_x, stamp_y, stamp_w, stamp_h, color_bg);
+ }
+ else {
+ renderer->render_image_tint(img_denied, stamp_x, stamp_y, stamp_w, stamp_h, color_bg);
+ }
+
+ animation_update(&resume->animation);
+ if (resume->animation.percentage == 1.0f) {
+ array_remove_at(&_active_location->resumes, 0);
+ }
+ }
+ }
+}
+
+static void place_detail_draw_info(platform_window* window, tab tab)
+{
+ float pad = tab.scale * 10;
+ float x = tab.x+pad;
+ float y = tab.y+pad;
+ float w = tab.w-(pad*2);
+ float h = tab.h-(pad*2);
+
+ renderer->render_set_scissor(window, x, y, w, h);
+ if (selected_tab_index == PLACE_DETAIL_EMPLOYEES) {
+ place_detail_draw_employees(window, tab, x, y, w, h);
+ }
+ else if (selected_tab_index == PLACE_DETAIL_JOBOFFERS) {
+ place_detail_draw_job_offers(window, tab, x, y, w, h);
+ }
+ else if (selected_tab_index == PLACE_DETAIL_GARAGE) {
+ place_detail_draw_trucks(window, tab, x, y, w, h);
+ }
+
+ renderer->render_reset_scissor(window);
+}
+
+static tab place_detail_draw_tab_bg(platform_window* window)
+{
+ float offset = scale * 40.0;
+ s32 cornor_size = img_button_topleft->width*(scale/2);
+ s32 item_h = 82 * 0.4 * scale;
+ s32 h = (area.h * 0.6 - (offset*2));
+ s32 w = (area.w * 0.9 - (offset*2));
+ s32 top_width = w - (cornor_size*2);
+ s32 size_height = h - (cornor_size*2);
+ s32 x = offset + area.x + (area.w*0.05);
+ s32 pos_y = area.y + (area.w*0.12);
+ s32 y = pos_y + item_h;
+
+ color fill = COLOR_BUTTON_ACTIVE_TINT;
+
+ // button_render(scale, BUTTON_STATIC, 0, panel_x + pad_x, vertical_pad + panel_y + pad_y*2 + button_h*1, button_w, button_h);
+
+ // left
+ renderer->render_image_tint(img_button_left, x, y-1, cornor_size, size_height+2, fill);
+
+ // right
+ renderer->render_image_tint(img_button_right, x + cornor_size + top_width, y-1, cornor_size, size_height+2, fill);
+
+ // bottom
+ renderer->render_image_tint(img_button_bottomleft, x, y + size_height, cornor_size, cornor_size, fill);
+ renderer->render_image_tint(img_button_bottom, x + cornor_size, y + size_height, top_width, cornor_size, fill);
+ renderer->render_image_tint(img_button_bottomright, x + cornor_size + top_width, y + size_height, cornor_size, cornor_size, fill);
+
+ // fill
+ s32 pad = cornor_size-1;
+ fill = COLOR_BUTTON_ACTIVE;
+ renderer->render_rectangle(x+pad, y-1, w-(pad*2), h-(pad*2), fill);
+
+ return (tab){x,y,w,h, scale};
+}
+
+static void place_detail_push_tab(place_detail_info_state index, platform_window* window, char* text)
+{
+ float offset = scale * 40.0;
+ s32 pos_y = area.y + (area.w*0.12);
+ s32 item_w = 426 * 0.4 * scale;
+ s32 item_h = 82 * 0.4 * scale;
+ s32 pos_x = offset + area.x + (area.w*0.05) + (item_w*index);
+ bool hovered = mouse_interacts(pos_x,pos_y,item_w,item_h);
+
+ color tint = COLOR_WHITE;
+ if (selected_tab_index == index) {
+ tint = COLOR_BUTTON_ACTIVE_TINT;
+ }
+ if (hovered) {
+ platform_set_cursor(window, CURSOR_POINTER);
+ if (is_left_clicked()) {
+ if (selected_tab_index != index) audio_play_sound(snd_click, AUDIO_CHANNEL_SFX_1);
+ selected_tab_index = index;
+ }
+ }
+
+ renderer->render_image_tint(img_tabitem, pos_x, pos_y, item_w, item_h, tint);
+
+ {
+ font* fnt = fnt_rd24;
+ s32 text_x = pos_x + (item_w/2) - (renderer->calculate_text_width(fnt, text)/2);
+ s32 text_y = pos_y + (item_h/2) - (fnt->px_h/2);
+
+ renderer->render_text(fnt, text_x+1, text_y+1, text, COLOR_TEXT_SHADOW);
+ renderer->render_text(fnt, text_x, text_y, text, COLOR_TEXT);
+ }
+}
+
+static tab place_detail_draw_tabs(platform_window* window)
+{
+ place_detail_push_tab(PLACE_DETAIL_EMPLOYEES, window, "Employees");
+ place_detail_push_tab(PLACE_DETAIL_JOBOFFERS, window, "Job Offers");
+ place_detail_push_tab(PLACE_DETAIL_SCHEDULE, window, "Schedule");
+ place_detail_push_tab(PLACE_DETAIL_GARAGE, window, "Garage");
+ return place_detail_draw_tab_bg(window);
+}
+
+static void place_detail_draw_title(platform_window* window)
+{
+ char buf[200];
+ if (current_detail_state == PLACE_DETAIL_SHOW_MAIN) {
+ strcpy(buf, _active_location->name);
+ }
+ else if (current_detail_state == PLACE_DETAIL_SHOW_RESUMES) {
+ strcpy(buf, "Hire new employees");
+ }
+ else if (current_detail_state == PLACE_DETAIL_SHOW_DEALERS) {
+ strcpy(buf, "Dealers");
+ }
+ else if (current_detail_state == PLACE_DETAIL_SHOW_EMPLOYEE) {
+ sprintf(buf, "%s, #%d", _active_employee->name, _active_employee->id);
+ }
+ else if (current_detail_state == PLACE_DETAIL_SHOW_SCHEDULE) {
+ if (_active_schedule_state == VIEWING)
+ sprintf(buf, "Schedule for %s", _active_location->name);
+ else {
+ sprintf(buf, "%s wants you to ship %s to %s",
+ _active_scheduling_job.offer.company->name,
+ _active_scheduling_job.offer.product->name,
+ (*(world_location**)array_at(&_active_scheduling_job.offer.connections,
+ _active_scheduling_job.offer.connections.length-1))->name);
+ }
+ }
+
+ font* fnt = fnt_rd36;
+ float text_pad = scale * 40.0;
+ s32 text_x = text_pad + area.x + (area.w*0.05);
+ s32 text_y = text_pad + area.y + (area.w*0.05);
+
+ // Title
+ {
+ renderer->render_text(fnt, text_x+2, text_y+2, buf, COLOR_TEXT_SHADOW);
+ renderer->render_text(fnt, text_x, text_y, buf, COLOR_TEXT);
+ }
+
+ color tags_text_color = AN_LI_TINT(COLOR_TEXT, tag_animation);
+ color tag_icon_color = AN_LI_TINT(COLOR_WHITE, tag_animation);
+
+ // Tags
+ #define PUSH_TAG(_text, _icon){\
+ char* text = _text;\
+ s32 textw = renderer->calculate_text_width(fnt, text);\
+ text_x -= textw;\
+ renderer->render_text(fnt, text_x, text_y, text, tags_text_color);\
+ text_x -= icon_s + pad_between_icon_and_text;\
+ renderer->render_image_tint(_icon, text_x, text_y,icon_s,icon_s, tag_icon_color);\
+ text_x -= pad_between_tags;}
+
+ text_y += (fnt->px_h/2);
+ fnt = fnt_rd20;
+ text_y -= (fnt->px_h/2);
+ s32 icon_s = fnt->px_h;
+ s32 pad_between_icon_and_text = 5*scale;
+ s32 pad_between_tags = 20*scale;
+
+ #define ANIMATION_OFFSET (30*scale)
+ text_x = area.x + (area.w*0.05) + (area.w*0.9) - text_pad + (ANIMATION_OFFSET-(ANIMATION_OFFSET*tag_animation.percentage));
+ #undef ANIMATION_OFFSET
+
+ job_offer* job_to_inspect = 0;
+ if (current_detail_state == PLACE_DETAIL_SHOW_SCHEDULE && _active_schedule_state != VIEWING)
+ job_to_inspect = &_active_scheduling_job.offer;
+ if (current_detail_state == PLACE_DETAIL_SHOW_SCHEDULE && _active_schedule_state == VIEWING && _active_selected_scheduled_job != 0)
+ job_to_inspect = &_active_selected_scheduled_job->offer;
+
+ if (current_detail_state == PLACE_DETAIL_SHOW_SCHEDULE && job_to_inspect) {
+ // price
+ {
+ char pricebuf[25];
+ sprintf(pricebuf, "$%d/trip", job_to_inspect->reward);
+ PUSH_TAG(pricebuf, img_coins);
+ }
+
+ // distance
+ {
+ char pricebuf[25];
+ sprintf(pricebuf, "%.0fkm", job_to_inspect->total_distance);
+ PUSH_TAG(pricebuf, img_road);
+ }
+
+ // duration
+ {
+ char pricebuf[25];
+ sprintf(pricebuf, "%.0fh-%.0fh", job_to_inspect->duration_sec_min/3600.0f, job_to_inspect->duration_sec_max/3600.0f);
+ PUSH_TAG(pricebuf, img_timer);
+ }
+
+ if (_active_schedule_state == VIEWING) {
+ char namebuf[MAX_WORLD_LOCATION_NAME_LENGTH*3];
+ job_endpoints endpoints = job_offer_get_endpoints(job_to_inspect);
+ sprintf(namebuf, "%s to %s", endpoints.source->name, endpoints.dest->name);
+ PUSH_TAG(namebuf, img_globe);
+ }
+ }
+ else if (current_detail_state == PLACE_DETAIL_SHOW_EMPLOYEE) {
+ // Original location
+ {
+ char pricebuf[100];
+ sprintf(pricebuf, "From: %s", get_world_location_by_id(_active_world, _active_employee->original_location_id)->name);
+ PUSH_TAG(pricebuf, img_city);
+ }
+
+ // Current location or active trip destination
+ {
+ char pricebuf[100];
+ if (_active_employee->current_location_id != INVALID_ID) {
+ sprintf(pricebuf, "Currently at: %s", get_world_location_by_id(_active_world, _active_employee->current_location_id)->name);
+ }
+ else if (_active_employee->active_job_id != INVALID_ID) {
+ active_job* j = get_active_job_by_id(_active_world, _active_employee->active_job_id);
+ job_endpoints endpoints = job_offer_get_endpoints(&j->offer);
+ sprintf(pricebuf, "Driving to: %s", endpoints.dest->name);
+ }
+ PUSH_TAG(pricebuf, img_location_pin);
+ }
+ }
+
+ animation_update(&tag_animation);
+}
+
+static void place_detail_draw_panel(platform_window* window)
+{
+ s32 panel_w = area.w*0.9;
+ s32 panel_h = area.h*0.7;
+ s32 panel_x = area.x + area.w*0.05;
+ s32 panel_y = area.y + area.w*0.05;
+ panel_render(scale, panel_x, panel_y, panel_w, panel_h);
+
+ // back button
+ {
+ s32 back_h = img_back->height * scale/2;
+ s32 back_w = img_back->width * scale/2;
+ s32 back_x = panel_x + (panel_w/10.0f);
+ s32 back_y = panel_y + panel_h - (back_h/2) - 1;
+
+ if (push_back_button(scale, back_x, back_y, back_w, back_h)) {
+ if (current_detail_state == PLACE_DETAIL_SHOW_MAIN) {
+ game_set_active_scene(GAME_STATE_WORLD_MAP);
+ }
+ else {
+ current_detail_state = PLACE_DETAIL_SHOW_MAIN;
+ }
+ _goto_default_detail_state();
+ }
+ }
+
+ if (mouse_interacts(panel_x,panel_y,panel_w,panel_h)) {
+ reset_left_click();
+ }
+ else if (is_left_clicked()) {
+ _goto_default_detail_state();
+ audio_play_sound(snd_click, AUDIO_CHANNEL_SFX_1);
+ game_set_active_scene(GAME_STATE_WORLD_MAP);
+ }
+}
+
+void place_detail_scene_render(platform_window* window)
+{
+ renderer->set_render_depth(5);
+ world_map_draw_info_panel(window, false);
+
+ renderer->set_render_depth(4);
+ if (current_detail_state == PLACE_DETAIL_SHOW_MAIN) {
+ tab t = place_detail_draw_tabs(window);
+ place_detail_draw_info(window, t);
+
+ if (selected_tab_index == PLACE_DETAIL_SCHEDULE) {
+ selected_tab_index = PLACE_DETAIL_SHOW_MAIN;
+ current_detail_state = PLACE_DETAIL_SHOW_SCHEDULE;
+ }
+ }
+ if (current_detail_state == PLACE_DETAIL_SHOW_RESUMES) {
+ place_detail_draw_resumes(window);
+ }
+ if (current_detail_state == PLACE_DETAIL_SHOW_DEALERS) {
+ place_detail_draw_dealers(window);
+ }
+ if (current_detail_state == PLACE_DETAIL_SHOW_EMPLOYEE) {
+ place_detail_draw_selected_employee(window);
+ }
+ if (current_detail_state == PLACE_DETAIL_SHOW_SCHEDULE) {
+ place_detail_draw_schedule(window);
+ }
+
+ renderer->set_render_depth(3);
+ place_detail_draw_title(window);
+
+ renderer->set_render_depth(2);
+ place_detail_draw_panel(window);
+
+ renderer->set_render_depth(1);
+ menu_draw_background(window);
+}
+
+void place_detail_scene_update(platform_window* window)
+{
+ // world_update(window, _active_world, false);
+
+ if (keyboard_is_key_pressed(KEY_ESCAPE)) {
+ if (current_detail_state == PLACE_DETAIL_SHOW_MAIN) {
+ game_set_active_scene(GAME_STATE_WORLD_MAP);
+ }
+ else {
+ current_detail_state = PLACE_DETAIL_SHOW_MAIN;
+ }
+ _goto_default_detail_state();
+ }
+}
+
+void place_detail_scene_destroy()
+{
+
+} \ No newline at end of file
diff --git a/src/scenes/save_state_select.c b/src/scenes/save_state_select.c
new file mode 100644
index 0000000..1db5d11
--- /dev/null
+++ b/src/scenes/save_state_select.c
@@ -0,0 +1,105 @@
+
+void save_state_select_scene_init()
+{
+
+}
+
+static bool push_save_state_button(float scale, bool enabled, s32 x, s32 y, s32 size)
+{
+ if (enabled) {
+ button_render(scale, true, 0, x, y, size, size);
+ }
+ else {
+ button_render(scale, true, 0, x, y, size, size);
+ float close_size = size/3;
+ float close_x = x + (size/2)-(close_size/2);
+ float close_y = y + (size/2)-(close_size/2);
+ renderer->render_image(img_close, close_x, close_y, close_size, close_size);
+ }
+
+ return false;
+}
+
+static bool push_back_button(float scale, s32 back_x, s32 back_y, s32 back_w, s32 back_h)
+{
+ bool result = false;
+ color tint = COLOR_WHITE;
+ if (mouse_interacts(back_x,back_y,back_w,back_h)) {
+ tint = COLOR_BUTTON_ACTIVE_TINT;
+ platform_set_cursor(main_window, CURSOR_POINTER);
+ if (is_left_clicked()) {
+ result = true;
+ audio_play_sound(snd_click, AUDIO_CHANNEL_SFX_1);
+ }
+ }
+
+ renderer->render_image_tint(img_back, back_x, back_y, back_w, back_h, tint);
+
+ font* font_sml = fnt_rd20;
+ char* back_text = "Back";
+ s32 back_text_width = renderer->calculate_text_width(font_sml, back_text);
+ s32 text_x = back_x + (back_w/2) - (back_text_width/2) + (back_w/12);
+ s32 text_y = back_y + (back_h/2) - (font_sml->px_h/2);
+
+ renderer->render_text(font_sml, text_x+2, text_y+2, back_text, COLOR_TEXT_SHADOW);
+ renderer->render_text(font_sml, text_x, text_y, back_text, COLOR_TEXT);
+ return result;
+}
+
+static void save_state_draw_options(platform_window* window)
+{
+ s32 screen_center_x = area.x + (area.w/2);
+ s32 screen_center_y = area.y + (area.h/2);
+
+ float vertical_pad = 20 * scale;
+ float horizontal_pad = vertical_pad;
+ float spacing = 5 * scale;
+
+ s32 panel_h = 280 * scale;
+ s32 panel_item_size = (panel_h - (vertical_pad*2) - (spacing*1)) / 2;
+ s32 panel_w = (panel_item_size * 3) + (horizontal_pad*2) + (spacing*2);
+
+ s32 panel_x = screen_center_x - (panel_w/2);
+ s32 panel_y = screen_center_y - (panel_h/2);
+ panel_render(scale, panel_x, panel_y, panel_w, panel_h);
+
+ // top row
+ for (s32 i = 0; i < 3; i++)
+ push_save_state_button(scale, 0, panel_x + horizontal_pad + (panel_item_size*i) +
+ (spacing*i), panel_y + horizontal_pad, panel_item_size);
+
+ // bottom row
+ for (s32 i = 0; i < 3; i++)
+ push_save_state_button(scale, 0, panel_x + horizontal_pad + (panel_item_size*i) + (spacing*i),
+ panel_y + horizontal_pad + panel_item_size+spacing, panel_item_size);
+
+ // back button
+ {
+ s32 back_h = img_back->height * scale/2;
+ s32 back_w = img_back->width * scale/2;
+ s32 back_x = panel_x + (panel_item_size/3);
+ s32 back_y = panel_y + panel_h - (back_h/2) - 1;
+
+ if (push_back_button(scale, back_x, back_y, back_w, back_h)) {
+ game_set_active_scene(GAME_STATE_MENU);
+ }
+ }
+}
+
+void save_state_select_scene_render(platform_window* window)
+{
+ menu_draw_background(window);
+ save_state_draw_options(window);
+}
+
+void save_state_select_scene_update(platform_window* window)
+{
+ if (keyboard_is_key_pressed(KEY_ESCAPE)) {
+ game_set_active_scene(GAME_STATE_MENU);
+ }
+}
+
+void save_state_select_scene_destroy()
+{
+
+} \ No newline at end of file
diff --git a/src/scenes/settings_scene.c b/src/scenes/settings_scene.c
new file mode 100644
index 0000000..23655d3
--- /dev/null
+++ b/src/scenes/settings_scene.c
@@ -0,0 +1,152 @@
+enum settings_state
+{
+ SETTINGS_AUDIO,
+ SETTINGS_DISPLAY,
+ SETTINGS_KEYBINDINGS,
+};
+
+enum settings_state settings_state = SETTINGS_AUDIO;
+
+void settings_scene_init() {
+
+}
+
+static void draw_display_settings(s32 x, s32 y, s32 w, s32 h)
+{
+ s32 option_spacing = 20*scale;
+ s32 checkbox_s = 30*scale;
+ s32 checkbox_offset = ((checkbox_s-fnt_rd24->px_h)/2);
+ s32 text_y = y + option_spacing;
+ s32 text_x = x + checkbox_s + option_spacing;
+
+ #define PUSH_DISPLAY_OPTION(_str, _opt)\
+ renderer->render_text(fnt_rd24, text_x, text_y, _str, COLOR_TEXT);\
+ if (button_draw_background(scale, x,text_y-checkbox_offset, checkbox_s, checkbox_s, COLOR_WHITE, COLOR_BUTTON)) {\
+ if (is_left_clicked()) { \
+ _opt = !_opt;\
+ if (&_opt == &option_vsync) platform_toggle_vsync(main_window, option_vsync);\
+ if (&_opt == &option_fullscreen) platform_toggle_fullscreen(main_window, option_fullscreen);\
+ audio_play_sound(snd_click, AUDIO_CHANNEL_SFX_1);\
+ }\
+ }\
+ if (_opt) {\
+ s32 tw = renderer->calculate_text_width(fnt_rd24, "X");\
+ renderer->render_text(fnt_rd24, x + checkbox_s/2 - tw/2,text_y-checkbox_offset + checkbox_s/2 - fnt_rd24->px_h/2, "X", COLOR_TEXT);\
+ }\
+ text_y += (checkbox_s + option_spacing);
+
+ PUSH_DISPLAY_OPTION("Vsync", option_vsync);
+ PUSH_DISPLAY_OPTION("Fullscreen", option_fullscreen);
+}
+
+static void draw_audio_settings(s32 x, s32 y, s32 w, s32 h)
+{
+ s32 option_spacing = 20*scale;
+ s32 slider_h = 30*scale;
+ s32 slider_offset = ((slider_h-fnt_rd24->px_h)/2);
+ s32 slider_w = w/2;
+ s32 slider_x = x + slider_w;
+ s32 text_y = y + option_spacing;
+
+ // Music
+ #define PUSH_VOLUME_OPTION(_text, _opt)\
+ {\
+ static bool is_editing = false;\
+ renderer->render_text(fnt_rd24, x, text_y, _text, COLOR_TEXT);\
+ s32 slider_y = text_y - slider_offset;\
+ float percentage = is_editing ? ((_global_mouse.x - (slider_x+10*scale)) / (float)(slider_w-20*scale)) : _opt;\
+ if (percentage < 0.0f) percentage = 0.0f;\
+ if (percentage > 1.0f) percentage = 1.0f;\
+ button_draw_background_percentage(scale, slider_x,slider_y,slider_w,slider_h, COLOR_WHITE, COLOR_BUTTON, percentage, COLOR_WHITE);\
+ bool hovered = mouse_interacts(slider_x,slider_y,slider_w,slider_h);\
+ if (hovered && is_left_clicked()) is_editing = true;\
+ if (is_editing) {\
+ audio_set_mixer_volume(AUDIO_CHANNEL_SFX_1, volume_sfx*volume_global);\
+ audio_set_mixer_volume(AUDIO_CHANNEL_SFX_2, volume_sfx*volume_global);\
+ audio_set_music_volume(volume_music*volume_global);\
+ }\
+ if (is_editing) {\
+ if (!is_left_down()) { is_editing = false; audio_play_sound(snd_click, AUDIO_CHANNEL_SFX_1); }\
+ _opt = percentage;\
+ }\
+ text_y += (slider_h + option_spacing);\
+ }
+
+ PUSH_VOLUME_OPTION("Global", volume_global);
+ PUSH_VOLUME_OPTION("Music", volume_music);
+ PUSH_VOLUME_OPTION("Interface", volume_sfx);
+}
+
+static void settings_draw_options(platform_window* window)
+{
+ s32 screen_center_x = area.x + (area.w/2);
+ s32 screen_center_y = area.y + (area.h/2);
+
+ s32 panel_w = 400 * scale;
+ s32 panel_h = 500 * scale;
+ s32 panel_x = screen_center_x - (panel_w/2);
+ s32 panel_y = screen_center_y - (panel_h/2);
+ panel_render(scale, panel_x, panel_y, panel_w, panel_h);
+
+ // Buttons
+ s32 button_pad = 10*scale;
+ s32 button_w = (panel_w-(button_pad*4))/3;
+ s32 button_h = 37*scale;
+ s32 button_y = panel_y + button_pad*1.3f;
+ s32 button_x = panel_x + button_pad;
+
+ if (button_render(scale, BUTTON_ENABLED, "Audio", button_x, button_y, button_w, button_h))
+ {
+ settings_state = SETTINGS_AUDIO;
+ }
+
+ if (button_render(scale, BUTTON_ENABLED, "Display", button_x + (button_w + button_pad), button_y, button_w, button_h))
+ {
+ settings_state = SETTINGS_DISPLAY;
+ }
+
+ if (button_render(scale, BUTTON_ENABLED, "Keybindings", button_x + (button_w + button_pad)*2, button_y, button_w, button_h))
+ {
+ settings_state = SETTINGS_KEYBINDINGS;
+ }
+
+ s32 detail_pad = 25*scale;
+ s32 detail_w = panel_w - (detail_pad*2)-(button_pad*2);
+ s32 detail_h = panel_h - (detail_pad*2)-(button_pad*3)-button_h;
+ s32 detail_x = panel_x+detail_pad+button_pad;
+ s32 detail_y = button_y + button_h + detail_pad+button_pad;
+
+ if (settings_state == SETTINGS_AUDIO) {
+ draw_audio_settings(detail_x, detail_y, detail_w, detail_h);
+ }
+ else if (settings_state == SETTINGS_DISPLAY) {
+ draw_display_settings(detail_x, detail_y, detail_w, detail_h);
+ }
+
+ // back button
+ {
+ s32 back_h = img_back->height * scale/2;
+ s32 back_w = img_back->width * scale/2;
+ s32 back_x = panel_x + (panel_w/10);
+ s32 back_y = panel_y + panel_h - (back_h/2) - 1;
+
+ if (push_back_button(scale, back_x, back_y, back_w, back_h)) {
+ game_set_active_scene(GAME_STATE_MENU);
+ }
+ }
+}
+
+void settings_scene_render(platform_window* window) {
+ menu_draw_background(window);
+ settings_draw_options(window);
+}
+
+void settings_scene_update(platform_window* window) {
+ if (keyboard_is_key_pressed(KEY_ESCAPE)) {
+ game_set_active_scene(GAME_STATE_MENU);
+ }
+}
+
+void settings_scene_destroy() {
+
+} \ No newline at end of file
diff --git a/src/scenes/world_map.c b/src/scenes/world_map.c
new file mode 100644
index 0000000..eb106cb
--- /dev/null
+++ b/src/scenes/world_map.c
@@ -0,0 +1,1094 @@
+typedef enum t_world_map_scene_state
+{
+ WORLD_SCENE_STATE_IDLE,
+ WORLD_SCENE_STATE_PURCHASE_LOCATION,
+ WORLD_SCENE_STATE_LOG,
+ WORLD_SCENE_STATE_INSIGHTS,
+ WORLD_SCENE_STATE_INVEST,
+} world_map_scene_state;
+
+typedef union t_world_map_scene_data
+{
+ world_location* location_to_purchase;
+} world_map_scene_data;
+
+s32 insights_selected_year_index = 0; // years since first year.
+world* _active_world = 0;
+world_map_scene_state scene_state = WORLD_SCENE_STATE_IDLE;
+world_map_scene_data scene_data = {0};
+active_job_ref currently_viewing_active_job = {0,0,0};
+
+animation log_button_flash_animation = {0,0,0,1};
+
+void place_detail_show_employee_detail(employee* emp);
+void place_detail_show_schedule_with_highlighted_job(world_location* loc, scheduled_job* job, scheduled_job_time job_time);
+
+void world_map_set_active_world(world* world)
+{
+ _active_world = world;
+}
+
+void world_map_scene_init()
+{
+
+}
+
+static bool push_info_panel_button(float scale, image* img, s32 x, s32 y, s32 size, bool enabled, bool flashing)
+{
+ button_type type = (enabled ? ((flashing && log_button_flash_animation.percentage >= 0.5f) ? BUTTON_HIGHLIGHTED : BUTTON_ENABLED) : BUTTON_DISABLED);
+ bool result = button_render(scale, type, 0, x, y, size, size);
+
+ float img_size = size / 2;
+ renderer->render_image(img, x+(img_size/2), y + (img_size/2), img_size, img_size);
+ return result;
+}
+
+static void world_map_draw_speed_panel(platform_window* window, bool enabled)
+{
+ s32 screen_center_x = area.x + (area.w/2);
+
+ s32 panel_h = 60 * scale;
+ s32 panel_w = 180 * scale;
+
+ float btn_size = 23 * scale;
+ float pad_top = 6 * scale;
+ s32 panel_x = screen_center_x - (panel_w/2);
+ s32 panel_y = area.y + area.h - panel_h - (100 * scale);
+ s32 text_y = panel_y + pad_top;
+ float side_btn_offset = pad_top;
+
+ // Background panel
+ panel_render(scale, panel_x, panel_y, panel_w, panel_h);
+
+ // Button decrease simulation speed
+ if (push_info_panel_button(scale, img_arrow_left, screen_center_x - (panel_w/2) + side_btn_offset, text_y, btn_size, enabled, false)) {
+ _active_world->simulation_speed/=2;
+ }
+ // Button increase simulation speed
+ if (push_info_panel_button(scale, img_arrow_right, screen_center_x + (panel_w/2) - btn_size - side_btn_offset, text_y, btn_size, enabled, false)) {
+ _active_world->simulation_speed*=2;
+ if (_active_world->simulation_speed == 0) _active_world->simulation_speed = 1;
+ }
+
+ // Validate new simulation speed
+ if (_active_world->simulation_speed < MIN_SIMULATION_SPEED) _active_world->simulation_speed = MIN_SIMULATION_SPEED;
+ if (_active_world->simulation_speed > MAX_SIMULATION_SPEED) _active_world->simulation_speed = MAX_SIMULATION_SPEED;
+
+ // Draw the current speed, or pause icon when paused
+ if (_active_world->simulation_speed != 0) {
+ font* fnt = fnt_rd20;
+
+ char buf[10];
+ sprintf(buf, "%dx", _active_world->simulation_speed);
+
+ s32 tw = renderer->calculate_text_width(fnt, buf);
+ s32 textx = panel_x + (panel_w/2)-(tw/2);
+ renderer->render_text(fnt, textx, text_y+(5*scale), buf, COLOR_TEXT);
+ }
+ else {
+ s32 icon_pad = 4*scale;
+ s32 icon_s = btn_size-(icon_pad*2);
+ renderer->render_image(img_pause, panel_x+(panel_w/2)-(icon_s/2), text_y+icon_pad, icon_s, icon_s);
+ }
+
+ if (mouse_interacts(panel_x,panel_y,panel_w,panel_h)) {
+ reset_left_click();
+ }
+}
+
+static void world_map_draw_info_panel(platform_window* window, bool enabled)
+{
+ world_map_draw_speed_panel(window, enabled);
+
+ s32 screen_center_x = area.x + (area.w/2);
+
+ float vertical_pad = 20 * scale;
+
+ s32 panel_h = 120 * scale;
+ s32 panel_w = 280 * scale;
+ s32 panel_x = screen_center_x - (panel_w/2);
+ s32 panel_y = area.y + area.h - panel_h - (10 * scale);
+
+ // Draw background panel
+ panel_render(scale, panel_x, panel_y, panel_w, panel_h);
+
+ if (enabled) {
+ char txt_buf[50];
+ char* text = txt_buf;
+ s32 text_y;
+ s32 text_x;
+ font* font_big = fnt_rd32;
+ s32 game_title_width;
+
+ // Draw current date and time
+ {
+ strftime(txt_buf, 50, "%H:%M %d/%m/%Y", &_active_world->current_time);
+ game_title_width = renderer->calculate_text_width(font_big, text);
+ text_y = panel_y + vertical_pad;
+ text_x = screen_center_x - (game_title_width/2);
+
+ renderer->render_text(font_big, text_x+1, text_y+1, text, COLOR_TEXT_SHADOW);
+ renderer->render_text(font_big, text_x, text_y, text, COLOR_TEXT);
+ }
+
+ // Draw money
+ {
+ sprintf(txt_buf, "$%.0f", _active_world->money);
+ text = txt_buf;
+ font* font_medium = fnt_rd24;
+ game_title_width = renderer->calculate_text_width(font_medium, text);
+ text_y = text_y + font_big->px_h + (10 * scale);
+ text_x = screen_center_x - (game_title_width/2);
+
+ renderer->render_text(font_medium, text_x+1, text_y+1, text, COLOR_TEXT_SHADOW);
+ renderer->render_text(font_medium, text_x, text_y, text, COLOR_TEXT);
+ }
+
+ float btn_size = 35 * scale;
+ float btn_spacing = 2 * scale;
+ float btn_y = panel_y + panel_h - btn_size - vertical_pad/2;
+ float total_btn_w = (btn_size+btn_spacing)*4-btn_spacing;
+
+ if (log_button_flash_animation.percentage == 1.0f) log_button_flash_animation = animation_create(1000);
+ log_button_flash_animation.started = true;
+ animation_update(&log_button_flash_animation);
+
+ // Graph button
+ if (push_info_panel_button(scale, img_graph, screen_center_x - (total_btn_w/2) + (btn_size+btn_spacing)*0, btn_y, btn_size, enabled, false)) {
+ scene_state = (scene_state == WORLD_SCENE_STATE_INSIGHTS) ? WORLD_SCENE_STATE_IDLE : WORLD_SCENE_STATE_INSIGHTS;
+ }
+
+ // Event log button
+ if (push_info_panel_button(scale, img_list, screen_center_x - (total_btn_w/2) + (btn_size+btn_spacing)*1, btn_y, btn_size, enabled, _active_world->log.has_unread_messages)) {
+ scene_state = (scene_state == WORLD_SCENE_STATE_LOG) ? WORLD_SCENE_STATE_IDLE : WORLD_SCENE_STATE_LOG;
+ }
+
+ // Bank button
+ push_info_panel_button(scale, img_bank, screen_center_x - (total_btn_w/2) + (btn_size+btn_spacing)*2, btn_y, btn_size, enabled, false);
+
+ // Event log button
+ if (push_info_panel_button(scale, img_list, screen_center_x - (total_btn_w/2) + (btn_size+btn_spacing)*3, btn_y, btn_size, enabled, _active_world->log.has_unread_messages)) {
+ scene_state = (scene_state == WORLD_SCENE_STATE_INVEST) ? WORLD_SCENE_STATE_IDLE : WORLD_SCENE_STATE_INVEST;
+ }
+ }
+ else {
+ s32 icon_s = panel_h/2;
+ renderer->render_image(img_pause, panel_x+(panel_w/2)-(icon_s/2), panel_y+(panel_h/2)-(icon_s/2), icon_s, icon_s);
+ }
+
+ if (mouse_interacts(panel_x,panel_y,panel_w,panel_h)) {
+ reset_left_click();
+ }
+}
+
+static void _insights_draw_grid(float scale, bool invalid_location, money_data_collection* collection, s32 grid_rows, s32 grid_cols, s32 textpad, s32 x, s32 y, s32 w, s32 h, s32 width_per_item, s32 height_per_item)
+{
+ font* fnt = fnt_rd16;
+ font* fnt_title = fnt_rd24;
+
+ for (s32 i = 0; i < MONTHS_IN_YEAR; i++) {
+ if (!collection) break;
+ money_data data = collection->months[i];
+ if (isnan(data.total_income)) continue;
+
+ char textbuf[50];
+ #define PUSH_VAL(_index, _val, _neg)\
+ sprintf(textbuf, "$%.0f", fabs(_val));\
+ renderer->render_text(fnt, x + ((i+3)*width_per_item) + (textpad/2), y + (_index*height_per_item)+(height_per_item/2)-(fnt->px_h/2), textbuf, (_neg) ? COLOR_TEXT_NEGATIVE : COLOR_TEXT);
+
+ PUSH_VAL(1, data.income_from_trips, false);
+ PUSH_VAL(2, data.expenses_from_utility, true);
+ PUSH_VAL(3, data.expenses_from_healthcare, true);
+ PUSH_VAL(4, data.expenses_from_repairs, true);
+ PUSH_VAL(5, data.expenses_from_fuel, true);
+ PUSH_VAL(6, data.expenses_from_employees, true);
+ PUSH_VAL(7, data.expenses_from_trucks, true);
+ // ADD NEW ENTRY HERE
+ PUSH_VAL(9, data.total_income, false);
+ PUSH_VAL(10, data.total_expenses, true);
+ PUSH_VAL(11, data.total_profit, data.total_profit < 0.0f);
+ }
+
+ renderer->render_rectangle(x, y, width_per_item*3, h, COLOR_SCHEDULE_BG);
+ renderer->render_rectangle(x, y, w, height_per_item, COLOR_SCHEDULE_BG);
+
+ for (s32 tx = 3; tx < grid_cols; tx++) {
+ s32 posx = x + tx * width_per_item;
+ renderer->render_rectangle(posx,y,1,h, COLOR_SCHEDULE_BORDER_THIN);
+
+ char buf[50];
+ buf[0] = 0;
+
+ switch(tx-2) {
+ case 1: strcpy(buf, "Jan"); break;
+ case 2: strcpy(buf, "Feb"); break;
+ case 3: strcpy(buf, "Mar"); break;
+ case 4: strcpy(buf, "Apr"); break;
+ case 5: strcpy(buf, "May"); break;
+ case 6: strcpy(buf, "Jun"); break;
+ case 7: strcpy(buf, "Jul"); break;
+ case 8: strcpy(buf, "Aug"); break;
+ case 9: strcpy(buf, "Sep"); break;
+ case 10: strcpy(buf, "Oct"); break;
+ case 11: strcpy(buf, "Nov"); break;
+ case 12: strcpy(buf, "Dec"); break;
+ default: break;
+ }
+
+ s32 textw = renderer->calculate_text_width(fnt_title, buf);
+ renderer->render_text(fnt_title, posx + (width_per_item/2)-(textw/2), y+(height_per_item/2)-(fnt_title->px_h/2), buf, COLOR_TEXT);
+ }
+
+ for (s32 ty = 0; ty < grid_rows; ty++) {
+ s32 posy = y + ty * height_per_item;
+ renderer->render_rectangle(x,posy,w,1, COLOR_SCHEDULE_BORDER_THIN);
+
+ char buf[50];
+ buf[0] = 0;
+
+ switch(ty) {
+ case 1: strcpy(buf, "Income"); break;
+ case 2: strcpy(buf, "Utility"); break;
+ case 3: strcpy(buf, "Employee Benefits"); break;
+ case 4: strcpy(buf, "Repairs"); break;
+ case 5: strcpy(buf, "Fuel"); break;
+ case 6: strcpy(buf, "Salaries"); break;
+ case 7: strcpy(buf, "Trucks"); break;
+ // ADD NEW ENTRY HERE
+ case 9: strcpy(buf, "Total Income"); break;
+ case 10: strcpy(buf, "Total Expenses"); break;
+ case 11: strcpy(buf, "Total Profit"); break;
+ default: break;
+ }
+
+ renderer->render_text(fnt_title, x + textpad, posy+(height_per_item/2)-(fnt_title->px_h/2), buf, COLOR_TEXT);
+ }
+
+ if (!collection) {
+ char* txtbuf = invalid_location ? "Invalid location" : "No data for time period";
+ s32 textw = renderer->calculate_text_width(fnt_title, txtbuf);
+ s32 textx = x + (width_per_item*9) - (textw/2);
+ s32 texty = y + (h/2)-(fnt_title->px_h/2);
+ renderer->render_text(fnt_title, textx, texty, txtbuf, COLOR_TEXT);
+ }
+
+ renderer->render_rectangle_outline(x,y,w,h, 1, COLOR_SCHEDULE_BORDER);
+}
+
+static void _insights_draw_chart(platform_window*window, bool invalid_location, float scale, money_data_collection* collection, s32 grid_rows, s32 grid_cols, s32 textpad, s32 x, s32 y, s32 w, s32 h, s32 width_per_item, s32 height_per_item)
+{
+ font* fnt = fnt_rd16;
+ font* fnt_big = fnt_rd24;
+
+ s32 xaxis_height = fnt_big->px_h;
+ h -= xaxis_height;
+
+ s32 max_val = 0;
+ s32 min_val = INT_MAX;
+ for (s32 i = 0; i < MONTHS_IN_YEAR; i++) {
+ if (!collection) break;
+ money_data data = collection->months[i];
+
+ #define CHECK_MIN_MAX(_val)\
+ if (_val > max_val) max_val = _val;\
+ if (_val < min_val) min_val = _val;
+
+ CHECK_MIN_MAX(data.income_from_trips);
+ CHECK_MIN_MAX(data.expenses_from_utility);
+ CHECK_MIN_MAX(data.expenses_from_healthcare);
+ CHECK_MIN_MAX(data.expenses_from_repairs);
+ CHECK_MIN_MAX(data.expenses_from_fuel);
+ CHECK_MIN_MAX(data.expenses_from_employees);
+ CHECK_MIN_MAX(data.expenses_from_trucks);
+ // ADD NEW ENTRY HERE
+ CHECK_MIN_MAX(data.total_income);
+ CHECK_MIN_MAX(data.total_expenses);
+ CHECK_MIN_MAX(data.total_profit);
+ }
+
+ if (min_val == INT_MAX) min_val = 0;
+
+ s32 steps = 10;
+ s32 step_size = 0;
+
+ // Round min and max to nearest 10k
+ {
+ s32 max = max_val >= abs(min_val) ? max_val : abs(min_val);
+ s32 round_to = 10000;
+ if (max > 10000) round_to = 10000;
+ if (max > 100000) round_to = 100000;
+ if (max > 1000000) round_to = 1000000;
+ if (max > 10000000) round_to = 10000000;
+ if (max > 100000000) round_to = 100000000;
+
+ max_val = max_val + round_to - max_val % round_to;
+ if (min_val > round_to) min_val = min_val - min_val % round_to;
+ else min_val = min_val - round_to - (min_val%round_to);
+
+ // make sure negative and positive y-axis is symmetrical
+ if (max_val > abs(min_val)) {
+ min_val = -max_val;
+ }
+ else if (max_val < abs(min_val)) {
+ max_val = abs(min_val);
+ }
+
+ step_size = (max_val-min_val)/steps;
+ }
+
+ static bool enabled_categories[10] = {1,1,1,1,1,1,1,1,1,1};
+
+ color colors[] = {
+ rgb(87, 82, 208),
+ rgb(3, 118, 247),
+ rgb(54, 166, 214),
+ rgb(50, 140, 250),
+ rgb(90, 196, 248),
+ rgb(78, 213, 95),
+ rgb(252, 200, 3),
+ rgb(249, 146, 5),
+ rgb(249, 59, 47),
+ rgb(249, 45, 82),
+ };
+
+ char* legenda_items[] = {
+ "Income",
+ "Utility",
+ "Employee Benefits",
+ "Repairs",
+ "Fuel",
+ "Salaries",
+ "Trucks",
+ // ADD NEW ENTRY HERE
+ "Total Income",
+ "Total Expenses",
+ "Total Profit",
+ };
+
+ // Legenda
+ {
+ s32 text_spacing = 10*scale;
+ s32 outline_spacing = text_spacing/2;
+ s32 _index = 0;
+
+ #define PUSH_LEGENDA_ITEM(_text){\
+ s32 yy = y+((fnt_big->px_h+text_spacing)*_index);\
+ s32 totalw = width_per_item*3;\
+ bool hovered = (_global_mouse.x >= x-outline_spacing && _global_mouse.x <= x+totalw+outline_spacing\
+ && _global_mouse.y >= yy-outline_spacing && _global_mouse.y <= yy + fnt_big->px_h+outline_spacing);\
+ if (hovered) {platform_set_cursor(window, CURSOR_POINTER); if (is_left_clicked())\
+ { enabled_categories[_index] = !enabled_categories[_index]; audio_play_sound(snd_click, AUDIO_CHANNEL_SFX_1); }}\
+ renderer->render_rectangle(x,yy,fnt_big->px_h, fnt_big->px_h, enabled_categories[_index] ? colors[_index] : LEGENDA_COLOR_DISABLED);\
+ if (hovered) renderer->render_rectangle(x-outline_spacing,yy-outline_spacing,totalw+(text_spacing),fnt_big->px_h+(text_spacing), LEGENDA_HOVER_BACKGROUND_COLOR);\
+ renderer->render_text(fnt_big,x+fnt_big->px_h+text_spacing,yy,_text,COLOR_TEXT);_index++;}
+
+ for (s32 i = 0; i < sizeof(legenda_items)/sizeof(char*); i++) {
+ PUSH_LEGENDA_ITEM(legenda_items[i]);
+ }
+ }
+
+ // Graph y axis
+ {
+ s32 linex = x + 4*width_per_item;
+
+ float step_h = (h)/(float)steps;
+ for (s32 i = 0; i < steps+1; i++) {
+ char buf[20];
+ s32 val = max_val - (step_size*i);
+ sprintf(buf, "%d", val);
+ s32 textw = renderer->calculate_text_width(fnt, buf);
+ renderer->render_text(fnt, x+(width_per_item*4)-textw-(10*scale), y+(step_h*i)-(fnt->px_h/2), buf, COLOR_TEXT);
+ renderer->render_rectangle(linex, y+h*(i/(float)steps), width_per_item*(MONTHS_IN_YEAR-1), 1, LEGENDA_SUB_COLOR_DISABLED);
+ }
+
+ {
+ renderer->render_rectangle(linex, y + h - (h*1.0f), width_per_item*(MONTHS_IN_YEAR-1), 1, LEGENDA_COLOR_DISABLED);
+ renderer->render_rectangle(linex, y + h - (h*0.5f), width_per_item*(MONTHS_IN_YEAR-1), 1, LEGENDA_COLOR_DISABLED);
+ renderer->render_rectangle(linex, y + h - (h*0.0f), width_per_item*(MONTHS_IN_YEAR-1), 1, LEGENDA_COLOR_DISABLED);
+ }
+ }
+
+ // Graph x axis
+ for (s32 i = 0; i < MONTHS_IN_YEAR; i++) {
+ char buf[50];
+ buf[0] = 0;
+
+ switch(i+1) {
+ case 1: strcpy(buf, "Jan"); break;
+ case 2: strcpy(buf, "Feb"); break;
+ case 3: strcpy(buf, "Mar"); break;
+ case 4: strcpy(buf, "Apr"); break;
+ case 5: strcpy(buf, "May"); break;
+ case 6: strcpy(buf, "Jun"); break;
+ case 7: strcpy(buf, "Jul"); break;
+ case 8: strcpy(buf, "Aug"); break;
+ case 9: strcpy(buf, "Sep"); break;
+ case 10: strcpy(buf, "Oct"); break;
+ case 11: strcpy(buf, "Nov"); break;
+ case 12: strcpy(buf, "Dec"); break;
+ default: break;
+ }
+ s32 textw = renderer->calculate_text_width(fnt,buf);
+
+ renderer->render_text(fnt, x+(width_per_item*(i+4))-(textw/2), y+h+(10*scale), buf, COLOR_TEXT);
+ }
+
+ // Graph data
+ {
+ s32 total_diff = max_val-min_val;
+ if (total_diff == 0) total_diff = 1;
+
+ s32 dot_size = 4*scale;
+ s32 dot_offset =dot_size/2;
+ s32 _index = 0;
+ //if (min_val < 0) min_val = 0;
+
+ #define DRAW_LINES_FOR_DATA(_var){\
+ s32 last_dot_x = 0;\
+ s32 last_dot_y = 0;\
+ for (s32 i = 0; i < MONTHS_IN_YEAR; i++) {\
+ if (!collection) break;\
+ if (!enabled_categories[_index]) break;\
+ money_data data = collection->months[i];\
+ if (isnan(data.total_income)) continue;\
+ s32 val_diff = (_var) - min_val;\
+ s32 dot_x = x + (i+4)*width_per_item;\
+ s32 dot_y = y + h - (h*(val_diff/(float)total_diff));\
+ renderer->render_image_tint(img_dot, dot_x-dot_offset, dot_y-dot_offset, dot_size, dot_size, colors[_index]);\
+ if (mouse_interacts(dot_x-dot_offset, dot_y-dot_offset, dot_size, dot_size)) {\
+ reset_left_click();\
+ s32 info_x = dot_x;\
+ s32 info_y = dot_y+(dotsize/2);\
+ char info_txt[50];\
+ sprintf(info_txt, "%s: $%.0f", legenda_items[_index], _var);\
+ show_tooltip(info_x, info_y, info_txt);\
+ }\
+ if (last_dot_x != 0) {\
+ renderer->render_line(dot_x, dot_y, last_dot_x, last_dot_y, 1, colors[_index]);\
+ }\
+ last_dot_x = dot_x;\
+ last_dot_y = dot_y;\
+ }_index++;\
+ }
+
+ DRAW_LINES_FOR_DATA(data.income_from_trips);
+ DRAW_LINES_FOR_DATA(data.expenses_from_utility);
+ DRAW_LINES_FOR_DATA(data.expenses_from_healthcare);
+ DRAW_LINES_FOR_DATA(data.expenses_from_repairs);
+ DRAW_LINES_FOR_DATA(data.expenses_from_fuel);
+ DRAW_LINES_FOR_DATA(data.expenses_from_employees);
+ DRAW_LINES_FOR_DATA(data.expenses_from_trucks);
+ // ADD NEW ENTRY HERE
+ DRAW_LINES_FOR_DATA(data.total_income);
+ DRAW_LINES_FOR_DATA(data.total_expenses);
+ DRAW_LINES_FOR_DATA(data.total_profit);
+ }
+
+ if (!collection) {
+ char* txtbuf = invalid_location ? "Invalid location" : "No data for time period";
+ s32 textw = renderer->calculate_text_width(fnt_big, txtbuf);
+ s32 textx = x + (width_per_item*9) - (textw/2);
+ s32 texty = y + (h/2)-(fnt_big->px_h/2);
+ renderer->render_text(fnt_big, textx, texty, txtbuf, COLOR_TEXT);
+ }
+}
+
+static void world_map_draw_insights(platform_window* window)
+{
+ s32 w = area.w*0.9;
+ s32 h = area.h*0.7;
+ s32 x = area.x + area.w*0.05;
+ s32 y = area.y + area.w*0.05;
+ s32 w_orig = w;
+ s32 h_orig = h;
+ s32 x_orig = x;
+ s32 y_orig = y;
+ panel_render(scale, x, y, w, h);
+
+ #define GRID_ROWS ((sizeof(money_data)/sizeof(float))+2)
+ #define GRID_COLS (MONTHS_IN_YEAR+3)
+
+ s32 button_row_h = 44*scale;
+ s32 btn_size = 34*scale;
+ s32 spacing = 5*scale;
+
+ s32 pad = 40*scale;
+ s32 halfpad = pad/2;
+ s32 textpad = 20*scale;
+
+ w -= pad*2;
+ h -= pad*2;
+
+ h -= button_row_h+spacing;
+
+ s32 width_per_item = (w/GRID_COLS);
+ s32 height_per_item = (h/GRID_ROWS);
+ s32 new_w = width_per_item*GRID_COLS;
+ s32 new_h = height_per_item*GRID_ROWS;
+ s32 off_x = w - new_w;
+ s32 off_y = h - new_h;
+ w = new_w;
+ h = new_h;
+
+ s32 panel_w = w+(halfpad*2);
+
+ enum insights_panel_format
+ {
+ CHART,
+ GRID,
+ };
+ static enum insights_panel_format current_format = GRID;
+ static world_location* active_world_location_filter = 0;
+
+ {
+ s32 btn_row_x = x + off_x/2+halfpad;
+ s32 btn_row_y = y + off_y/2+halfpad;
+
+ s32 button_pad = (button_row_h - btn_size)/2;
+ s32 btn_start_x = btn_row_x + panel_w - (4*scale);
+ s32 btn_left_start_x = btn_row_x+(4*scale)+button_pad;
+ s32 year_w = (80*scale);
+ s32 year_text_w = year_w+(button_pad*2);
+ s32 btn_y = btn_row_y + button_pad;
+
+ // background
+ button_render(scale, BUTTON_STATIC, 0, btn_row_x, btn_row_y,panel_w, button_row_h);
+
+ // Buttons right
+ if (push_info_panel_button(scale, img_graph, btn_start_x - ((btn_size + button_pad)*1), btn_y, btn_size, current_format!=CHART, false)) (current_format = CHART);
+ if (push_info_panel_button(scale, img_grid, btn_start_x - ((btn_size + button_pad)*2), btn_y, btn_size, current_format!=GRID, false)) (current_format = GRID);
+
+ font* fnt = fnt_rd24;
+ s32 current_year = 1900+_active_world->start_year+insights_selected_year_index;
+
+ // Buttons left
+ button_render(scale, BUTTON_STATIC, 0, btn_left_start_x+btn_size+button_pad, btn_y,year_w, btn_size);
+ char buf[10];
+ sprintf(buf, "%d", current_year);
+ s32 textw = renderer->calculate_text_width(fnt,buf);
+ renderer->render_text(fnt,btn_left_start_x+(btn_size)+(year_text_w/2)-(textw/2), btn_y+(btn_size/2)-(fnt->px_h/2), buf, COLOR_TEXT);
+
+ s32 btn_right_x = btn_left_start_x + (btn_size) + year_text_w;
+ if (push_info_panel_button(scale, img_arrow_left, btn_left_start_x, btn_y, btn_size, insights_selected_year_index>=1, false)) (insights_selected_year_index--);
+ if (push_info_panel_button(scale, img_arrow_right, btn_right_x, btn_y,
+ btn_size, insights_selected_year_index<_active_world->insights.length-1, false)) (insights_selected_year_index++);
+
+ // Location selector
+ s32 tb_filter_x = btn_right_x + btn_size + button_pad;
+ s32 tb_width = 520*scale;
+ active_world_location_filter = location_selector_render(window, scale, true, active_world_location_filter, tb_filter_x, btn_y, tb_width, btn_size);
+
+ // Clear button
+ s32 clear_btn_x = tb_filter_x + tb_width + button_pad;
+ if (push_info_panel_button(scale, img_globe, clear_btn_x, btn_y, btn_size, _global_keyboard.input_text_len, false)) {
+ active_world_location_filter = 0;
+ keyboard_set_input_text("");
+ }
+ }
+
+ x+=off_x/2+pad;
+ y+=off_y/2+pad+button_row_h+spacing;
+
+ button_render(scale, BUTTON_STATIC, 0, x-halfpad,y-halfpad,panel_w,h+(halfpad*2));
+
+ static money_data_collection* prev_collection = 0;
+ money_data_collection* collection = get_current_insights_data(_active_world); // Get current year page, also a hack to make sure a new page exists at year switch.
+ if (!prev_collection || collection != prev_collection) {
+ prev_collection = collection;
+ insights_selected_year_index = _active_world->insights.length-1;
+ }
+
+ money_data_collection* collection_to_use = array_at(&_active_world->insights, insights_selected_year_index); // Get page to use
+ if (active_world_location_filter) {
+ if (active_world_location_filter->is_owned) {
+ s32 index_of_filter_page = insights_selected_year_index;
+ s32 purchase_year_diff = active_world_location_filter->purchase_year - _active_world->start_year;
+ index_of_filter_page -= purchase_year_diff;
+ if (index_of_filter_page < 0) collection_to_use = 0;
+ else if (index_of_filter_page > active_world_location_filter->insights.length-1) collection_to_use = 0;
+ else collection_to_use = array_at(&active_world_location_filter->insights, index_of_filter_page);
+ }
+ else {
+ collection_to_use = 0;
+ }
+ }
+
+ bool is_typing_in_filter_tb = (!active_world_location_filter && _global_keyboard.input_text_len);
+ if (is_typing_in_filter_tb) collection_to_use = 0;
+
+ if (current_format == GRID) _insights_draw_grid(scale, is_typing_in_filter_tb, collection_to_use, GRID_ROWS, GRID_COLS, textpad, x, y, w, h, width_per_item, height_per_item);
+ if (current_format == CHART) _insights_draw_chart(window, is_typing_in_filter_tb, scale, collection_to_use, GRID_ROWS, GRID_COLS, textpad, x, y, w, h, width_per_item, height_per_item);
+
+ if (mouse_interacts(x_orig, y_orig, w_orig, h_orig)) {
+ reset_left_click();
+ } else if (is_left_clicked()) {
+ scene_state = WORLD_SCENE_STATE_IDLE;
+ }
+}
+
+static void world_map_draw_event_log(platform_window* window)
+{
+ _active_world->log.has_unread_messages = false;
+
+ font* fnt = fnt_rd16;
+
+ s32 panel_h = area.h*0.9f;
+ s32 panel_pad = area.h*0.05f;
+
+ s32 panel_w = area.w/3.5f;
+
+ s32 panel_x = area.x + panel_pad;
+ s32 panel_y = area.y + panel_pad;
+ panel_render(scale, panel_x, panel_y, panel_w, panel_h);
+
+ float text_pad = panel_w*0.05f;
+
+ s32 text_y = panel_y + text_pad;
+ s32 text_x = panel_x + text_pad;
+ s32 text_w = panel_w - (text_pad*2);
+
+ renderer->render_set_scissor(window, text_x, text_y, panel_w, panel_h-(text_pad*2));
+
+ if (_active_world->log.events.length) {
+ s32 read_cursor = _active_world->log.write_cursor;
+ for (s32 i = 0; i < _active_world->log.events.length; i++) {
+ read_cursor--;
+ if (read_cursor < 0) read_cursor = _active_world->log.events.length-1;
+
+ event* e = array_at(&_active_world->log.events, read_cursor);
+
+ float inner_pad = 5*scale;
+
+
+ s32 texth = renderer->render_text_cutoff(fnt, text_x+inner_pad, text_y+inner_pad, e->message, COLOR_TEXT, text_w-(inner_pad*2));
+ s32 highlight_w = text_w;
+ s32 highlight_h = texth+(inner_pad);
+
+ if (mouse_interacts(text_x, text_y, highlight_w, highlight_h)) {
+ platform_set_cursor(window, CURSOR_POINTER);
+ renderer->render_rectangle(text_x, text_y, text_w, highlight_h, rgba(255,255,255,20));
+
+ if (is_left_clicked()) {
+ switch (e->type)
+ {
+ case EVENT_TYPE_MISSED_SHIPMENT_NO_TRUCK:
+ place_detail_show_employee_detail((employee*)e->data);
+ break;
+ case EVENT_TYPE_MISSED_SHIPMENT_NO_ASSIGNEE:
+ case EVENT_TYPE_MISSED_SHIPMENT_NOT_AT_LOCATION: {
+ scheduled_job* job = (scheduled_job*)e->data;
+ place_detail_show_schedule_with_highlighted_job(job->location, job, e->job_time);
+ } break;
+ case EVENT_TYPE_EMPLOYEE_QUIT:
+ place_detail_show_schedule_with_highlighted_job((world_location*)e->data, 0, e->job_time);
+ break;
+
+ default:
+ log_assert(0, "Invalid event type.");
+ break;
+ }
+ }
+ }
+
+ text_y += highlight_h;
+ if (text_y > panel_y + panel_h) break;
+ }
+ }
+ else {
+ char* buf = "No events.";
+ s32 tw = renderer->calculate_text_width(fnt, buf);
+ renderer->render_text_cutoff(fnt, text_x + (text_w/2)-(tw/2), text_y, "No events.", COLOR_TEXT, text_w);
+ }
+
+ renderer->render_reset_scissor(window);
+
+ if (mouse_interacts(panel_x, panel_y, panel_w, panel_h)) {
+ reset_left_click();
+ } else if (is_left_clicked()) {
+ scene_state = WORLD_SCENE_STATE_IDLE;
+ }
+}
+
+s32 hovered_investment_panel_item_index = -1;
+static s32 world_map_push_invest_panel_item(s32 x, s32 y, s32 w, s32 index, char* text, s32* fval)
+{
+ s32 pad = 40*scale;
+ s32 halfpad = pad/2;
+ s32 panel_w = w-(pad);
+ float item_pad = 30*scale;
+ float item_spacing = 5*scale;
+ float item_halfpad = item_pad/2;
+
+ font* fnt = fnt_rd24;
+ char value_str[30];
+ sprintf(value_str, "$%d", *fval);
+ float item_h = fnt->px_h+item_pad;
+ float item_x = x+halfpad;
+ float item_y = y+halfpad+(index*(item_h+item_spacing));
+ float item_w = panel_w;
+ float content_x = item_x+item_halfpad;
+ float content_y = item_y+item_halfpad;
+ float total_val_editor_w = item_w*0.3f;
+ float button_left_x = item_x + item_w - total_val_editor_w - item_halfpad;
+ float btn_size = fnt->px_h*2;
+ float btn_y = item_y + ((item_h-btn_size)/2);
+ button_render(scale, BUTTON_STATIC, 0, item_x, item_y,item_w,item_h);
+ renderer->render_text(fnt, content_x, content_y, text, COLOR_TEXT);
+ // Button decrease simulation speed
+ if (push_info_panel_button(scale, img_arrow_left, button_left_x, btn_y, btn_size, *fval > 0, false)) {
+ *fval -= 100.0f;
+ }
+ // Button increase simulation speed
+ if (push_info_panel_button(scale, img_arrow_right, button_left_x+total_val_editor_w-btn_size, btn_y, btn_size, true, false)) {
+ *fval += 100.0f;
+ }
+ s32 item_text_w = renderer->calculate_text_width(fnt, value_str);
+ s32 item_text_x = button_left_x + (total_val_editor_w/2) - (item_text_w/2);
+ renderer->render_text(fnt, item_text_x, content_y, value_str, COLOR_TEXT);
+
+ if (mouse_interacts_peak(item_x, item_y, item_w, item_h)) {
+ hovered_investment_panel_item_index = index;
+ }
+
+ return item_y + item_h;
+}
+
+static void world_map_draw_invest_panel(platform_window* window)
+{
+ s32 w = area.w*0.5;
+ s32 h = area.h*0.7;
+ s32 x = area.x + area.w*0.25;
+ s32 y = area.y + area.w*0.05;
+ panel_render(scale, x, y, w, h);
+
+ s32 pad = 40*scale;
+ s32 halfpad = pad/2;
+ s32 panel_w = w-(pad);
+
+ #define TOTAL_INVEST_ITEM_COUNT (5)
+
+ hovered_investment_panel_item_index = -1;
+
+ world_map_push_invest_panel_item(x, y, w, 0, "Workspace safety", (s32*)(&_active_world->investments.safety));
+ world_map_push_invest_panel_item(x, y, w, 1, "Marketing", (s32*)(&_active_world->investments.marketing));
+ world_map_push_invest_panel_item(x, y, w, 2, "Human resources", (s32*)(&_active_world->investments.human_resources));
+ world_map_push_invest_panel_item(x, y, w, 3, "Employee training", (s32*)(&_active_world->investments.training));
+ s32 item_bottom = world_map_push_invest_panel_item(x, y, w, 4, "Legal department", (s32*)(&_active_world->investments.legal));
+
+ float detail_panel_y = item_bottom + halfpad;
+ float detail_panel_h = h - (detail_panel_y-y)-halfpad;
+
+ button_render(scale, BUTTON_STATIC, 0, x+halfpad, detail_panel_y,panel_w,detail_panel_h);
+
+ s32 info_text_pad = 20*scale;
+
+ char* info_text = 0;
+ switch (hovered_investment_panel_item_index)
+ {
+ case 0: info_text = "Increasing the budget for workspace safety will ensure employees receive the proper workspace safety training.";
+ break;
+ case 1: info_text = "Increasing the marketing budget will give your business more publicity and will secure more work proposals.";
+ break;
+ case 2: info_text = "Investing in human resources will guarantee more responses to vacancies.";
+ break;
+ case 3: info_text = "Providing training for your employees will increase happiness for your employees and make them work more efficiently.";
+ break;
+ case 4: info_text = "The legal department is essential for settling legal disputes.";
+ break;
+ }
+
+ if (info_text)
+ renderer->render_text_cutoff(fnt_rd24, x+halfpad+info_text_pad, detail_panel_y+info_text_pad, info_text, COLOR_TEXT, panel_w - (info_text_pad*2));
+
+ if (mouse_interacts(x,y,w,h)) {
+ reset_left_click();
+ } else if (is_left_clicked()) {
+ scene_state = WORLD_SCENE_STATE_IDLE;
+ }
+}
+
+static void world_map_draw_purchase_location_panel(platform_window* window)
+{
+ s32 screen_center_x = area.x + (area.w/2);
+ s32 screen_center_y = area.y + (area.h/2);
+
+ float vertical_pad = 20 * scale;
+
+ s32 panel_h = 160 * scale;
+ s32 panel_w = 280 * scale;
+
+ s32 panel_x = screen_center_x - (panel_w/2);
+ s32 panel_y = screen_center_y - (panel_h/2);
+ panel_render(scale, panel_x, panel_y, panel_w, panel_h);
+
+ // info text
+ {
+ font* font_title = FONT_REGULAR(SIZE_RD(area.w, 32));
+ {
+ char* title = scene_data.location_to_purchase->name;
+ s32 text_w = renderer->calculate_text_width(font_title, title);
+ s32 text_x = screen_center_x - (text_w/2);
+ s32 text_y = panel_y + (vertical_pad);
+ renderer->render_text(font_title, text_x+2, text_y+2, title, COLOR_TEXT_SHADOW);
+ renderer->render_text(font_title, text_x, text_y, title, COLOR_TEXT);
+ }
+ {
+ char buf[100];
+ sprintf(buf, "Purchase a garage for $%.0f?", world_location_get_price(scene_data.location_to_purchase));
+ char* text = buf;
+ font* font_info = FONT_REGULAR(SIZE_RD(area.w, 20));
+ s32 text_w = renderer->calculate_text_width(font_info, text);
+ s32 text_x = screen_center_x - (text_w/2);
+ s32 text_y = panel_y + vertical_pad*1.5+font_title->px_h;
+ renderer->render_text(font_info, text_x+2, text_y+2, text, COLOR_TEXT_SHADOW);
+ renderer->render_text(font_info, text_x, text_y, text, COLOR_TEXT);
+ }
+ }
+
+ s32 button_w = 178 * scale;
+ s32 button_h = 37 * scale;
+ if (button_render(scale, true, "Purchase", screen_center_x - (button_w/2), panel_y + panel_h - button_h - vertical_pad, button_w, button_h))
+ {
+ // Mark as owned.
+ scene_data.location_to_purchase->is_owned = true;
+ scene_data.location_to_purchase->purchase_year = _active_world->current_time.tm_year;
+
+ // Make sure insights are ready to display.
+ money_data_collection* collection = get_current_insights_data_for_location(_active_world, scene_data.location_to_purchase);
+ collection->months[_active_world->current_time.tm_mon].total_income = 0;
+
+ _active_world->money -= world_location_get_price(scene_data.location_to_purchase);
+
+ scene_state = WORLD_SCENE_STATE_IDLE;
+ }
+
+ button_w = 30 * scale;
+ if (button_render(scale, true, "X", panel_x+panel_w-button_w, panel_y+2, button_w, button_w))
+ {
+ scene_state = WORLD_SCENE_STATE_IDLE;
+ }
+
+ if (mouse_interacts(panel_x,panel_y,panel_w,panel_h)) {
+ reset_left_click();
+ } else if (is_left_clicked()) {
+ scene_state = WORLD_SCENE_STATE_IDLE;
+ }
+}
+
+static void world_map_draw_viewing_job(platform_window* window)
+{
+ active_job* job = get_active_job_by_ref(_active_world, currently_viewing_active_job);
+ if (!job) {
+ currently_viewing_active_job.offerid = INVALID_ID;
+ return;
+ }
+
+ s32 panel_offset_from_job = area.h*0.1f;
+ s32 panel_h = area.h*0.32f;
+ s32 panel_w = area.w*0.15f;
+
+ s32 panel_x = job->px_pos.x;
+ s32 panel_y = job->px_pos.y;
+
+ if (job->px_pos.y > area.y + (area.h/2)) {
+ panel_y -= panel_h - panel_offset_from_job;
+
+ renderer->render_line(job->px_pos.x+(dotsize/2), job->px_pos.y+(dotsize/2), panel_x + (panel_w/3), panel_y+panel_h-5, 3, COLOR_INSPECT_ACTIVE_JOB_LINE_CONNECTION);
+ renderer->render_line(job->px_pos.x+(dotsize/2), job->px_pos.y+(dotsize/2), panel_x + (panel_w/3*2), panel_y+panel_h-5, 3, COLOR_INSPECT_ACTIVE_JOB_LINE_CONNECTION);
+ }
+ else {
+ panel_y += panel_offset_from_job;
+
+ renderer->render_line(job->px_pos.x+(dotsize/2), job->px_pos.y+(dotsize/2), panel_x + (panel_w/3), panel_y+5, 3, COLOR_INSPECT_ACTIVE_JOB_LINE_CONNECTION);
+ renderer->render_line(job->px_pos.x+(dotsize/2), job->px_pos.y+(dotsize/2), panel_x + (panel_w/3*2), panel_y+5, 3, COLOR_INSPECT_ACTIVE_JOB_LINE_CONNECTION);
+ }
+
+ panel_render(scale, panel_x, panel_y, panel_w, panel_h);
+
+ s32 panel_pad = 10*scale;
+ s32 truck_img_x = panel_x + panel_pad;
+ s32 truck_img_y = panel_y + panel_pad;
+ s32 truck_img_s = panel_w * 0.6f;
+
+ s32 portrait_s = truck_img_s / 2;
+
+ renderer->render_image(job->assigned_truck.logo, truck_img_x+(panel_pad), truck_img_y, truck_img_s, truck_img_s);
+ draw_employee_portrait(&job->assignee, truck_img_x+(panel_pad) + truck_img_s - (portrait_s/3), truck_img_y + (truck_img_s / 3), portrait_s, portrait_s);
+
+ {
+ s32 text_x = truck_img_x;
+ float text_pad = (5*scale);
+ s32 text_y = truck_img_y + truck_img_s + panel_pad;
+ font* fnt = fnt_rd16;
+ font* fnt_s = fnt_rd12;
+ renderer->render_text(fnt,text_x,text_y,job->assignee.name,COLOR_TEXT);
+ text_y += fnt->px_h + text_pad;
+
+ // Name + origin
+ {
+ world_location* origin = get_world_location_by_id(_active_world, job->assignee.original_location_id);
+ char buf[100];
+ sprintf(buf, "Works at %s", origin->name);
+ renderer->render_text(fnt_s,text_x,text_y,buf,COLOR_TEXT);
+ text_y += fnt_s->px_h + text_pad;
+ }
+
+ // Job info
+ {
+ text_y += (20*scale);
+
+ char daybuf[10];
+ switch(job->day) {
+ case 1: strcpy(daybuf, "Mon"); break;
+ case 2: strcpy(daybuf, "Tue"); break;
+ case 3: strcpy(daybuf, "Wed"); break;
+ case 4: strcpy(daybuf, "Thu"); break;
+ case 5: strcpy(daybuf, "Fri"); break;
+ case 6: strcpy(daybuf, "Sat"); break;
+ case 0: strcpy(daybuf, "Sun"); break;
+ }
+
+ char buf[100];
+ sprintf(buf, "Left on %s %02d:%02d", daybuf, WORK_HOUR_START + (job->timeslot/TIME_SLOTS_PER_HOUR), job->timeslot%TIME_SLOTS_PER_HOUR);
+ renderer->render_text(fnt_s,text_x,text_y,buf,COLOR_TEXT);
+ text_y += fnt_s->px_h + text_pad;
+ }
+
+ // Job status
+ if (!job->reversed)
+ {
+ job_endpoints endpoints = job_offer_get_endpoints(&job->offer);
+ char buf[100];
+ sprintf(buf, "Shipping %s to %s", job->offer.product->name, endpoints.dest->name);
+ text_y += renderer->render_text_cutoff(fnt_s,text_x,text_y,buf,COLOR_TEXT, panel_w - (panel_pad*2));
+ text_y += text_pad;
+ }
+ else {
+ world_location* source = *(world_location**)array_at(&job->offer.connections, 0); // Get source location
+ char buf[100];
+ sprintf(buf, "Returning to %s", source->name);
+ renderer->render_text_cutoff(fnt_s,text_x,text_y,buf,COLOR_TEXT, panel_w - (panel_pad*2));
+ text_y += fnt_s->px_h + text_pad;
+ }
+ }
+
+ if (mouse_interacts(panel_x,panel_y,panel_w,panel_h)) {
+ reset_left_click();
+ } else if (is_left_clicked()) {
+ currently_viewing_active_job.offerid = INVALID_ID;
+ }
+}
+
+static void world_handle_scroll(platform_window* window)
+{
+ static float target_zoom = 1.0f;
+ if (global_ui_context.mouse->scroll_state == SCROLL_UP)
+ {
+ if (target_zoom < zoom) target_zoom = zoom;
+ target_zoom+=0.1f;
+ }
+ if (global_ui_context.mouse->scroll_state == SCROLL_DOWN)
+ {
+ if (target_zoom > zoom) target_zoom = zoom;
+ target_zoom-=0.1f;
+ }
+
+ // Keep camera position
+ if (target_zoom != zoom) {
+ vec4 area = camera_get_target_rectangle(window);
+ int orig_w = area.w * zoom;
+ int orig_h = area.h * zoom;
+ int new_w = area.w * target_zoom;
+ int new_h = area.h * target_zoom;
+
+ float errorw = new_w - orig_w;
+ float errorh = new_h - orig_h;
+ camera_x -= errorw / 25.0f;
+ camera_y -= errorh / 40.0f;
+ }
+
+ // Smooth scrolling
+ if (target_zoom != zoom)
+ {
+ float error = target_zoom - zoom;
+ zoom += error / 10;
+ }
+
+ if (zoom < 1.0f) zoom = 1.0f;
+ if (zoom > 5.0f) zoom = 5.0f;
+
+ if (camera_x > 0.0f) camera_x = 0.0f;
+ if (camera_y > 0.0f) camera_y = 0.0f;
+
+ //if (is_left_down_peak())
+ {
+
+ }
+}
+
+void world_map_scene_render(platform_window* window)
+{
+ renderer->set_render_depth(5);
+
+ world_handle_scroll(window);
+
+ world_map_draw_info_panel(window, true);
+
+ renderer->set_render_depth(4);
+ switch (scene_state)
+ {
+ case WORLD_SCENE_STATE_INSIGHTS:
+ world_map_draw_insights(window);
+ break;
+ case WORLD_SCENE_STATE_LOG:
+ world_map_draw_event_log(window);
+ break;
+ case WORLD_SCENE_STATE_IDLE: break;
+ case WORLD_SCENE_STATE_PURCHASE_LOCATION:
+ world_map_draw_purchase_location_panel(window);
+ break;
+ case WORLD_SCENE_STATE_INVEST:
+ world_map_draw_invest_panel(window);
+ break;
+ }
+
+ renderer->set_render_depth(3);
+ if (currently_viewing_active_job.offerid != INVALID_ID) world_map_draw_viewing_job(window);
+
+ if (_active_world) {
+ world_update_result click_result = world_render(window, _active_world);
+
+ if (click_result.clicked_location) {
+ if (click_result.clicked_location->is_owned) {
+ place_detail_set_active_location(click_result.clicked_location);
+ game_set_active_scene(GAME_STATE_PLACE_DETAIL);
+ }
+ else {
+ scene_data.location_to_purchase = click_result.clicked_location;
+ scene_state = WORLD_SCENE_STATE_PURCHASE_LOCATION;
+ }
+ }
+ else if (click_result.clicked_job) {
+ currently_viewing_active_job = (active_job_ref){click_result.clicked_job->day, click_result.clicked_job->timeslot, click_result.clicked_job->offer.id};
+ }
+ }
+
+ renderer->set_render_depth(0);
+
+ vec4 area = camera_get_target_rectangle(window);
+ renderer->render_rectangle(area.x, area.y, area.w, area.h, COLOR_WORLD_MAP_BACKGROUND);
+ renderer->render_image(img_world_map, area.x + camera_x, area.y + camera_y, area.w*zoom, area.h*zoom);
+}
+
+void world_map_scene_update(platform_window* window)
+{
+ world_update(window, _active_world);
+ if (keyboard_is_key_pressed(KEY_ESCAPE)) {
+ scene_state = WORLD_SCENE_STATE_IDLE;
+ }
+}
+
+void world_map_scene_destroy()
+{
+
+} \ No newline at end of file