From d8a9c132abf9da180060d36617d5cb4730691861 Mon Sep 17 00:00:00 2001 From: Espyo Date: Sun, 12 Jan 2025 22:06:06 +0000 Subject: [PATCH] Added a pack management menu to the options menu. --- game_data/base/graphics/menu_icons.png | Bin 15614 -> 17528 bytes game_data/base/gui/options_menu_top.txt | 10 +- game_data/base/gui/packs_menu.txt | 18 + manual/content/changelog.html | 4 +- manual/content/options.html | 24 + source/documents/todo.txt | 9 +- .../vectorial_graphics/menu_icons.svg | 80 ++- source/source/content_manager.cpp | 43 +- source/source/content_manager.h | 10 +- source/source/drawing.h | 3 + source/source/game_states/main_menu.cpp | 6 +- .../game_states/other_menus/options_menu.cpp | 81 ++- .../game_states/other_menus/options_menu.h | 4 + .../game_states/other_menus/packs_menu.cpp | 486 ++++++++++++++++++ .../game_states/other_menus/packs_menu.h | 116 +++++ source/source/gui.cpp | 28 +- source/source/options.cpp | 35 ++ source/source/options.h | 16 +- source/source/utils/general_utils.cpp | 37 -- source/source/utils/general_utils.h | 110 +++- source/source/utils/os_utils.cpp | 67 +++ source/source/utils/os_utils.h | 23 + 22 files changed, 1101 insertions(+), 109 deletions(-) create mode 100644 game_data/base/gui/packs_menu.txt create mode 100644 source/source/game_states/other_menus/packs_menu.cpp create mode 100644 source/source/game_states/other_menus/packs_menu.h create mode 100644 source/source/utils/os_utils.cpp create mode 100644 source/source/utils/os_utils.h diff --git a/game_data/base/graphics/menu_icons.png b/game_data/base/graphics/menu_icons.png index 796d1eb909d5532be0b9abf4ed07d1aaa2d56f69..33d4e26cb1d0dddf8a2b7869c60d3fff80bacdb2 100644 GIT binary patch literal 17528 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 FV1mcnR7?N> literal 15614 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.26.0
  • Added arms out, backflip, called, carrying struggle, climbing, crushed, flailing, lounging, plucking thrown, pushing, shaking, sitting, sliding, and twirling animations to Pikmin.
  • Added throwing animations to leaders.
  • Added Pikmin generation animations to Onions.
  • -
  • Added a new area: Olympian Ruins.
  • +
  • Added new areas: Olympian Ruins and Turbulent Tundra.
  • Added a new enemy: Cyclopod.
  • Added new treasures: Chocolate Square and Chocolate Bar (internally a resource and pile respectively), Soda Cap, Yarn Ball.
  • Added more shadows to objects, including rectangular shadows. (Thanks Helodity).
  • @@ -112,7 +112,7 @@

    0.26.0

  • Changed the focus and get_mob_info actions to support the same set of target object types.
  • Changed the world_sfx_volume and ui_sfx_volume GUI elements in the options menu to read sound instead of sfx.
  • Changed the internal name of the pause menu and control binds menu GUIs from pause_help and control_binds_menu to help and options_menu_control_binds.
  • -
  • Changed the list of menu icons, adding an icon to the third slot for the main menu's help button, and eleventh slot for the particle editor.
  • +
  • Changed the list of menu icons, adding an icon for the main menu's help button, for the particle editor, and for the pack management menu.
  • Changed the affects_* properties of status effects to be a single affects property.
  • Changed the bud_icon and flower_icon bitmaps to white_bud_icon and white_flower_icon.
  • Changed the properties of liquids.
  • diff --git a/manual/content/options.html b/manual/content/options.html index fbb3f1714..242cb32af 100644 --- a/manual/content/options.html +++ b/manual/content/options.html @@ -244,6 +244,30 @@ 1 No + + pack_order + The order in which each pack is loaded, separated by semicolon. You're better off controlling this from the options menu's pack management menu. + + No + + + packs_disabled + List of packs that are currently disabled, separated by semicolon. You're better off controlling this from the options menu's pack management menu. + + No + + + particle_editor_grid_interval + Space between each grid cell in the particle editor. + 32 + No + + + particle_editor_bg_texture + Full path to the background texture for the particle editor, if any. + 32 + No + resolution Width and height of the game window. The engine was made with a small-medium resolution in mind, although you can change the window size to anything you want. diff --git a/source/documents/todo.txt b/source/documents/todo.txt index 396ef541e..bbea604f3 100644 --- a/source/documents/todo.txt +++ b/source/documents/todo.txt @@ -1,4 +1,4 @@ -Point I left on (so I can continue the next day without losing my train of thought) +Point I left on (so I can continue the next day without losing my train of thought) Current tasks (tasks being worked on, but not yet committed) @@ -205,6 +205,13 @@ Next tasks (roughly sorted most important first) Barely important format changes In area geometry files, path stops use the term "nr" to refer to links, even though they contain more than the end stop number Organize all graphics and sound files into neat sub-folders + Menu refactor + Add a gui.start_transition function so that we don't have to do the responsive = true/false thing when we call gui.start_animation + Some sort of sub-menus manager so we don't have to have half a dozen if(sub_gui) sub_gui.handle_player_action() and stuff like that + Organize the code folders, files, and structs + Move all menus to their own struct + Differentiate between menus as game states, and menus as GUIs + The radar should show where obstacles (blocks_path == true) are, and differentiate between dead and living enemies and leaders Missions should have a property where the maker can specify their best score (and date) Add a screenshot, or better yet, a gif to the readme Manual updates diff --git a/source/documents/vectorial_graphics/menu_icons.svg b/source/documents/vectorial_graphics/menu_icons.svg index 0b13a96aa..b138ddd56 100644 --- a/source/documents/vectorial_graphics/menu_icons.svg +++ b/source/documents/vectorial_graphics/menu_icons.svg @@ -2,9 +2,9 @@ + x="-0.017215352" + y="-0.023342856" + width="1.0344307" + height="1.0466857"> + x="-0.034270793" + y="-0.031155278" + width="1.1085416" + height="1.0623106"> + + + + + transform="translate(194.99993)" /> + + + + + + diff --git a/source/source/content_manager.cpp b/source/source/content_manager.cpp index 12bfd722b..de638ad06 100644 --- a/source/source/content_manager.cpp +++ b/source/source/content_manager.cpp @@ -267,6 +267,8 @@ void content_manager::unload_all(const vector &types) { * @brief Clears all loaded manifests. */ void pack_manager::clear_manifests() { + manifests_sans_base_raw.clear(); + manifests_with_base_raw.clear(); manifests_sans_base.clear(); manifests_with_base.clear(); } @@ -276,11 +278,34 @@ void pack_manager::clear_manifests() { * @brief Fills in the manifests. */ void pack_manager::fill_manifests() { - vector folders = folder_to_vector(FOLDER_PATHS_FROM_ROOT::GAME_DATA, true); + //Raw manifests. + vector raw_folders = + folder_to_vector(FOLDER_PATHS_FROM_ROOT::GAME_DATA, true); - for(size_t f = 0; f < folders.size(); f++) { - if(folders[f] != FOLDER_NAMES::BASE_PACK) { - manifests_sans_base.push_back(folders[f]); + for(size_t f = 0; f < raw_folders.size(); f++) { + if(raw_folders[f] != FOLDER_NAMES::BASE_PACK) { + manifests_sans_base_raw.push_back(raw_folders[f]); + } + } + + manifests_with_base_raw.push_back(FOLDER_NAMES::BASE_PACK); + manifests_with_base_raw.insert( + manifests_with_base_raw.end(), + manifests_sans_base_raw.begin(), + manifests_sans_base_raw.end() + ); + + //Organized manifests. + vector organized_folders = + filter_vector_with_ban_list(raw_folders, game.options.packs_disabled); + organized_folders = + sort_vector_with_preference_list( + organized_folders, game.options.pack_order + ); + + for(size_t f = 0; f < organized_folders.size(); f++) { + if(organized_folders[f] != FOLDER_NAMES::BASE_PACK) { + manifests_sans_base.push_back(organized_folders[f]); } } @@ -296,18 +321,20 @@ void pack_manager::fill_manifests() { /** * @brief Loads all packs in the manifests, including the base pack. * This only loads their metadata, not their content! + * This also loads all packs, not just the ones organized via the + * player options. */ void pack_manager::load_all() { - for(size_t p = 0; p < manifests_with_base.size(); p++) { + for(size_t p = 0; p < manifests_with_base_raw.size(); p++) { data_node pack_file = load_data_file( FOLDER_PATHS_FROM_ROOT::GAME_DATA + "/" + - manifests_with_base[p] + "/" + + manifests_with_base_raw[p] + "/" + FILE_NAMES::PACK_DATA ); pack pack_data; - pack_data.name = manifests_with_base[p]; + pack_data.name = manifests_with_base_raw[p]; reader_setter rs(&pack_file); rs.set("name", pack_data.name); rs.set("description", pack_data.description); @@ -319,7 +346,7 @@ void pack_manager::load_all() { rs.set("conflicts", pack_data.conflicts); rs.set("notes", pack_data.notes); - list[manifests_with_base[p]] = pack_data; + list[manifests_with_base_raw[p]] = pack_data; } } diff --git a/source/source/content_manager.h b/source/source/content_manager.h index 962d30696..9df0c25b8 100644 --- a/source/source/content_manager.h +++ b/source/source/content_manager.h @@ -27,11 +27,17 @@ struct pack_manager { //--- Members --- - //Manifests, sans the base pack. + //Manifests, sans the base pack, organized via the player's options. vector manifests_sans_base; - //Manifests, with the base pack. + //Manifests, with the base pack, organized via the player's options. vector manifests_with_base; + + //Manifests, sans the base pack, not organized via the player's options. + vector manifests_sans_base_raw; + + //Manifests, with the base pack, not organized via the player's options. + vector manifests_with_base_raw; //List of loaded packs, with the base pack. map list; diff --git a/source/source/drawing.h b/source/source/drawing.h index 5fb8de1d4..e601909b7 100644 --- a/source/source/drawing.h +++ b/source/source/drawing.h @@ -89,6 +89,9 @@ enum MENU_ICON { //Options menu audio button. MENU_ICON_AUDIO, + //Options menu packs button. + MENU_ICON_PACKS, + //Options menu misc. button. MENU_ICON_OPTIONS_MISC, }; diff --git a/source/source/game_states/main_menu.cpp b/source/source/game_states/main_menu.cpp index 2284b0744..03400aea0 100644 --- a/source/source/game_states/main_menu.cpp +++ b/source/source/game_states/main_menu.cpp @@ -308,7 +308,11 @@ void main_menu_state::init_gui_main_page() { }); }; help_button->on_get_tooltip = - [] () { return "Some quick help and tips about how to play."; }; + [] () { + return + "Quick help and tips about how to play. " + "You can also find this in the pause menu."; + }; main_gui.add_item(help_button, "help"); //Options button. diff --git a/source/source/game_states/other_menus/options_menu.cpp b/source/source/game_states/other_menus/options_menu.cpp index 85d5cc8e3..7f73d16e2 100644 --- a/source/source/game_states/other_menus/options_menu.cpp +++ b/source/source/game_states/other_menus/options_menu.cpp @@ -122,6 +122,10 @@ options_menu_t::~options_menu_t() { graphics_gui.destroy(); audio_gui.destroy(); misc_gui.destroy(); + if(packs_menu) { + delete packs_menu; + packs_menu = nullptr; + } } @@ -192,6 +196,7 @@ void options_menu_t::draw() { graphics_gui.draw(); audio_gui.draw(); misc_gui.draw(); + if(packs_menu) packs_menu->draw(); if(capturing_input == 1) { al_draw_filled_rectangle( @@ -222,8 +227,6 @@ void options_menu_t::draw() { void options_menu_t::handle_event(const ALLEGRO_EVENT &ev) { if(closing) return; - - switch(capturing_input) { case 0: { //Not capturing. @@ -261,6 +264,7 @@ void options_menu_t::handle_event(const ALLEGRO_EVENT &ev) { graphics_gui.handle_event(ev); audio_gui.handle_event(ev); misc_gui.handle_event(ev); + if(packs_menu) packs_menu->handle_event(ev); } /** @@ -276,6 +280,7 @@ void options_menu_t::handle_player_action(const player_action &action) { graphics_gui.handle_player_action(action); audio_gui.handle_player_action(action); misc_gui.handle_player_action(action); + if(packs_menu) packs_menu->handle_player_action(action); } @@ -845,19 +850,21 @@ void options_menu_t::init_gui_top_page() { bool controls_icon_left = icon_left("controls", "true"); bool graphics_icon_left = icon_left("graphics", "true"); bool audio_icon_left = icon_left("audio", "true"); + bool packs_icon_left = icon_left("packs", "true"); bool misc_icon_left = icon_left("misc", "true"); #undef icon_left //Menu items. - top_gui.register_coords("back", 12, 5, 20, 6); - top_gui.register_coords("header", 50, 10, 50, 6); - top_gui.register_coords("controls", 50, 27.5, 65, 10); - top_gui.register_coords("graphics", 50, 42.5, 65, 10); - top_gui.register_coords("audio", 50, 57.5, 65, 10); - top_gui.register_coords("misc", 50, 72.5, 60, 10); - top_gui.register_coords("advanced", 87, 86, 22, 8); - top_gui.register_coords("tooltip", 50, 96, 96, 4); + top_gui.register_coords("back", 12, 5, 20, 6); + top_gui.register_coords("header", 50, 10, 50, 6); + top_gui.register_coords("controls", 50, 25, 65, 10); + top_gui.register_coords("graphics", 50, 37, 65, 10); + top_gui.register_coords("audio", 50, 49, 65, 10); + top_gui.register_coords("packs", 50, 61, 65, 10); + top_gui.register_coords("misc", 50, 73, 60, 10); + top_gui.register_coords("advanced", 87, 86, 22, 8); + top_gui.register_coords("tooltip", 50, 96, 96, 4); top_gui.read_coords(gui_file->get_child_by_name("positions")); //Back button. @@ -976,6 +983,51 @@ void options_menu_t::init_gui_top_page() { [] () { return "Change options about the way the game sounds."; }; top_gui.add_item(audio_button, "audio"); + //Packs options button. + button_gui_item* packs_button = + new button_gui_item("Packs", game.sys_assets.fnt_standard); + packs_button->on_draw = + [ = ] (const point & center, const point & size) { + draw_menu_button_icon( + MENU_ICON_PACKS, center, size, packs_icon_left + ); + draw_button( + center, size, + packs_button->text, packs_button->font, + packs_button->color, packs_button->selected, + packs_button->get_juice_value() + ); + }; + packs_button->on_activate = + [this] (const point &) { + top_gui.responsive = false; + top_gui.start_animation( + GUI_MANAGER_ANIM_CENTER_TO_LEFT, + OPTIONS_MENU::HUD_MOVE_TIME + ); + packs_menu = new packs_menu_t(); + packs_menu->gui.responsive = true; + packs_menu->gui.start_animation( + GUI_MANAGER_ANIM_RIGHT_TO_CENTER, + OPTIONS_MENU::HUD_MOVE_TIME + ); + packs_menu->back_callback = [ = ] () { + packs_menu->gui.responsive = false; + packs_menu->gui.start_animation( + GUI_MANAGER_ANIM_CENTER_TO_RIGHT, + OPTIONS_MENU::HUD_MOVE_TIME + ); + top_gui.responsive = true; + top_gui.start_animation( + GUI_MANAGER_ANIM_LEFT_TO_CENTER, + OPTIONS_MENU::HUD_MOVE_TIME + ); + }; + }; + packs_button->on_get_tooltip = + [] () { return "Manage any content packs you have installed."; }; + top_gui.add_item(packs_button, "packs"); + //Misc. options button. button_gui_item* misc_button = new button_gui_item("Misc.", game.sys_assets.fnt_standard); @@ -1428,6 +1480,15 @@ void options_menu_t::tick(float delta_t) { audio_gui.tick(game.delta_t); misc_gui.tick(game.delta_t); + if(packs_menu) { + if(!packs_menu->to_delete) { + packs_menu->tick(game.delta_t); + } else { + delete packs_menu; + packs_menu = nullptr; + } + } + //Tick the menu closing. if(closing) { closing_timer -= delta_t; diff --git a/source/source/game_states/other_menus/options_menu.h b/source/source/game_states/other_menus/options_menu.h index 2778b7b9c..8d542665b 100644 --- a/source/source/game_states/other_menus/options_menu.h +++ b/source/source/game_states/other_menus/options_menu.h @@ -16,6 +16,7 @@ #include "../../gui.h" #include "../../options.h" +#include "packs_menu.h" using std::map; using std::string; @@ -247,6 +248,9 @@ struct options_menu_t { //GUI for the misc. options page. gui_manager misc_gui; + //Information about the current pack management menu, if any. + packs_menu_t* packs_menu = nullptr; + //Auto-throw picker widget. options_menu_picker_gui_item* auto_throw_picker = nullptr; diff --git a/source/source/game_states/other_menus/packs_menu.cpp b/source/source/game_states/other_menus/packs_menu.cpp new file mode 100644 index 000000000..f5a8d6604 --- /dev/null +++ b/source/source/game_states/other_menus/packs_menu.cpp @@ -0,0 +1,486 @@ + +/* + * Copyright (c) Andre 'Espyo' Silva 2013. + * The following source file belongs to the open-source project Pikifen. + * Please read the included README and LICENSE files for more information. + * Pikmin is copyright (c) Nintendo. + * + * === FILE DESCRIPTION === + * Pack management menu struct and functions. + */ + +#include "packs_menu.h" + +#include "../../game.h" +#include "../../functions.h" +#include "../../load.h" +#include "../../utils/os_utils.h" +#include "../../utils/string_utils.h" + + +namespace PACKS_MENU { + +//Name of the pack management menu GUI information file. +const string GUI_FILE_NAME = "packs_menu"; + +} + + +/** + * @brief Constructs a new pack management menu object. + */ +packs_menu_t::packs_menu_t() { + //Fill the menu's lists of packs. + pack_order = + sort_vector_with_preference_list( + game.content.packs.manifests_sans_base_raw, + game.options.pack_order + ); + packs_disabled = game.options.packs_disabled; + + //Menu items. + gui.register_coords("back", 12, 5, 20, 6); + gui.register_coords("header", 61, 5, 74, 6); + gui.register_coords("list", 26, 47, 48, 74); + gui.register_coords("list_scroll", 52, 47, 2, 74); + gui.register_coords("info_box", 76, 47, 44, 74); + gui.register_coords("pack_name", 67.5, 19, 25, 16); + gui.register_coords("pack_thumbnail", 89, 19, 16, 16); + gui.register_coords("pack_description", 76, 48.5, 42, 41); + gui.register_coords("pack_tags", 76, 73, 42, 6); + gui.register_coords("pack_maker", 65, 80, 20, 6); + gui.register_coords("pack_version", 87, 80, 20, 6); + gui.register_coords("restart_warning", 35.5, 88.5, 67, 5); + gui.register_coords("open_folder", 84, 88.5, 28, 5); + gui.register_coords("tooltip", 50, 96, 96, 4); + gui.read_coords( + game.content.gui_defs.list[PACKS_MENU::GUI_FILE_NAME]. + get_child_by_name("positions") + ); + + //Back button. + gui.back_item = + new button_gui_item("Back", game.sys_assets.fnt_standard); + gui.back_item->on_activate = + [this] (const point &) { + game.options.pack_order = pack_order; + game.options.packs_disabled = packs_disabled; + start_closing(); + save_options(); + if(back_callback) back_callback(); + }; + gui.back_item->on_get_tooltip = + [] () { return "Return to the previous menu."; }; + gui.add_item(gui.back_item, "back"); + + //Header text. + text_gui_item* header_text = + new text_gui_item( + "PACKS", + game.sys_assets.fnt_area_name, + COLOR_TRANSPARENT_WHITE, ALLEGRO_ALIGN_CENTER + ); + gui.add_item(header_text, "header"); + + //Packs list. + packs_list = new list_gui_item(); + gui.add_item(packs_list, "list"); + + const float ITEM_HEIGHT = 0.08f; + const float ITEM_PADDING = 0.02f; + const float ITEMS_OFFSET = 0.01f; + + //Base pack's bullet. + bullet_gui_item* base_bullet = + new bullet_gui_item( + "Base", game.sys_assets.fnt_standard, COLOR_GOLD + ); + base_bullet->center = + point(0.37f, ITEMS_OFFSET + ITEM_HEIGHT / 2.0f); + base_bullet->size = + point(0.70f, ITEM_HEIGHT); + base_bullet->on_selected = + [this] () { change_info(-1); }; + packs_list->add_child(base_bullet); + gui.add_item(base_bullet); + + for(size_t p = 0; p < pack_order.size(); p++) { + float list_bottom_y = packs_list->get_child_bottom(); + float row_center_y = list_bottom_y + ITEM_PADDING + ITEM_HEIGHT / 2.0f; + + //Pack bullet. + bullet_gui_item* bullet = + new bullet_gui_item( + "", + game.sys_assets.fnt_standard + ); + bullet->center = point(0.37f, row_center_y); + bullet->size = point(0.70f, ITEM_HEIGHT); + bullet->on_selected = [p, this] () { change_info(p); }; + packs_list->add_child(bullet); + gui.add_item(bullet); + pack_bullets.push_back(bullet); + + //Enable/disable checkbox. + check_gui_item* check = + new check_gui_item( + false, "", game.sys_assets.fnt_standard + ); + check->center = point(0.78f, row_center_y); + check->size = point(0.08f, ITEM_HEIGHT); + check->on_activate = + [p, check, this] (const point & pos) { + check->def_activate_code(); + if(check->value) { + packs_disabled.erase( + std::find( + packs_disabled.begin(), packs_disabled.end(), + pack_order[p] + ) + ); + } else { + packs_disabled.push_back(pack_order[p]); + } + trigger_restart_warning(); + }; + check->on_selected = [p, this] () { change_info(p); }; + check->on_get_tooltip = + [] () { + return "Enable or disable this pack."; + }; + packs_list->add_child(check); + gui.add_item(check); + pack_checks.push_back(check); + + //Move up button. + if(p > 0) { + button_gui_item* up_button = + new button_gui_item( + "U", game.sys_assets.fnt_standard + ); + up_button->center = point(0.87f, row_center_y); + up_button->size = point(0.08f, ITEM_HEIGHT); + up_button->on_activate = + [p, this] (const point &) { + std::iter_swap( + pack_order.begin() + p, pack_order.begin() + (p - 1) + ); + pack_bullets[p]->start_juice_animation( + gui_item::JUICE_TYPE_GROW_TEXT_MEDIUM + ); + pack_bullets[p - 1]->start_juice_animation( + gui_item::JUICE_TYPE_GROW_TEXT_MEDIUM + ); + trigger_restart_warning(); + populate_packs_list(); + }; + up_button->on_selected = [p, this] () { change_info(p); }; + up_button->on_get_tooltip = + [] () { + return "Move up on the list (make it be loaded earlier)."; + }; + packs_list->add_child(up_button); + gui.add_item(up_button); + } + + //Move down button. + if(p < pack_order.size() - 1) { + button_gui_item* down_button = + new button_gui_item( + "D", game.sys_assets.fnt_standard + ); + down_button->center = point(0.95f, row_center_y); + down_button->size = point(0.08f, ITEM_HEIGHT); + down_button->on_activate = + [p, this] (const point &) { + std::iter_swap( + pack_order.begin() + p, pack_order.begin() + (p + 1) + ); + pack_bullets[p]->start_juice_animation( + gui_item::JUICE_TYPE_GROW_TEXT_MEDIUM + ); + pack_bullets[p + 1]->start_juice_animation( + gui_item::JUICE_TYPE_GROW_TEXT_MEDIUM + ); + trigger_restart_warning(); + populate_packs_list(); + }; + down_button->on_selected = [p, this] () { change_info(p); }; + down_button->on_get_tooltip = + [] () { + return "Move down on the list (make it be loaded later)."; + }; + packs_list->add_child(down_button); + gui.add_item(down_button); + } + } + + //Packs list scrollbar. + scroll_gui_item* list_scroll = new scroll_gui_item(); + list_scroll->list_item = packs_list; + gui.add_item(list_scroll, "list_scroll"); + + //Info box item. + gui_item* info_box = new gui_item(); + info_box->on_draw = + [] (const point & center, const point & size) { + draw_textured_box( + center, size, game.sys_assets.bmp_frame_box, + COLOR_TRANSPARENT_WHITE + ); + }; + gui.add_item(info_box, "info_box"); + + //Pack name text. + pack_name_text = + new text_gui_item( + "", game.sys_assets.fnt_area_name, COLOR_GOLD, ALLEGRO_ALIGN_LEFT + ); + gui.add_item(pack_name_text, "pack_name"); + + //Pack thumbnail. + gui_item* pack_thumb_item = new gui_item(); + pack_thumb_item->on_draw = + [this] (const point & center, const point & size) { + //Make it a square. + point final_size( + std::min(size.x, size.y), + std::min(size.x, size.y) + ); + //Align it to the top-right corner. + point final_center( + (center.x + size.x / 2.0f) - final_size.x / 2.0f, + (center.y - size.y / 2.0f) + final_size.y / 2.0f + ); + if(!cur_pack_name.empty() && pack_thumbs[cur_pack_name]) { + draw_bitmap( + pack_thumbs[cur_pack_name], + final_center, final_size - 4.0f + ); + } + draw_textured_box( + final_center, final_size, game.sys_assets.bmp_frame_box, + COLOR_TRANSPARENT_WHITE + ); + }; + gui.add_item(pack_thumb_item, "pack_thumbnail"); + + //Pack description text. + pack_description_text = + new text_gui_item( + "", game.sys_assets.fnt_standard, COLOR_WHITE, ALLEGRO_ALIGN_LEFT + ); + pack_description_text->line_wrap = true; + gui.add_item(pack_description_text, "pack_description"); + + //Pack tags text. + pack_tags_text = + new text_gui_item( + "", game.sys_assets.fnt_standard, COLOR_WHITE, ALLEGRO_ALIGN_LEFT + ); + gui.add_item(pack_tags_text, "pack_tags"); + + //Pack maker text. + pack_maker_text = + new text_gui_item( + "", game.sys_assets.fnt_standard, COLOR_WHITE, ALLEGRO_ALIGN_LEFT + ); + gui.add_item(pack_maker_text, "pack_maker"); + + //Pack version text. + pack_version_text = + new text_gui_item( + "", game.sys_assets.fnt_standard, COLOR_WHITE, ALLEGRO_ALIGN_RIGHT + ); + gui.add_item(pack_version_text, "pack_version"); + + //Restart warning text. + warning_text = + new text_gui_item( + "You may need to restart for some of the changes to take effect.", + game.sys_assets.fnt_standard, COLOR_WHITE, ALLEGRO_ALIGN_LEFT + ); + warning_text->visible = false; + gui.add_item(warning_text, "restart_warning"); + + //Open folder button. + button_gui_item* open_folder_button = + new button_gui_item("Open folder", game.sys_assets.fnt_standard); + open_folder_button->on_activate = + [this] (const point &) { + open_file_explorer(FOLDER_PATHS_FROM_ROOT::GAME_DATA); + }; + open_folder_button->on_get_tooltip = + [] () { + return + "Opens the packs folder on your operative system. " + "Place downloaded packs here!"; + }; + gui.add_item(open_folder_button, "open_folder"); + + //Tooltip text. + tooltip_gui_item* tooltip_text = + new tooltip_gui_item(&gui); + gui.add_item(tooltip_text, "tooltip"); + + populate_packs_list(); + + //Finishing touches. + gui.set_selected_item(gui.back_item, true); +} + + +/** + * @brief Destroys the pack management menu object. + */ +packs_menu_t::~packs_menu_t() { + gui.destroy(); +} + + +/** + * @brief Changes the info that's being shown about the currently-selected + * pack. + * + * @param idx Index of the pack. -1 for the base pack, -2 for nothing. + */ +void packs_menu_t::change_info(int idx) { + //Figure out what pack this is. + pack* pack_ptr = nullptr; + string new_pack_name; + if(idx == -1) { + new_pack_name = FOLDER_NAMES::BASE_PACK; + pack_ptr = &game.content.packs.list[new_pack_name]; + } else if(idx >= 0 && idx < (int) pack_order.size()) { + new_pack_name = pack_order[idx]; + pack_ptr = &game.content.packs.list[new_pack_name]; + } + + if(cur_pack_name == new_pack_name) { + return; + } + + cur_pack_name = new_pack_name; + if(!pack_ptr) { + pack_name_text->text.clear(); + pack_description_text->text.clear(); + pack_tags_text->text.clear(); + pack_maker_text->text.clear(); + pack_version_text->text.clear(); + return; + } + + //Fill the GUI items. + pack_name_text->text = + pack_ptr->name; + pack_name_text->start_juice_animation( + gui_item::JUICE_TYPE_GROW_TEXT_ELASTIC_LOW + ); + + pack_description_text->text = + pack_ptr->description; + pack_description_text->start_juice_animation( + gui_item::JUICE_TYPE_GROW_TEXT_ELASTIC_MEDIUM + ); + + pack_tags_text->text = + (pack_ptr->tags.empty() ? "" : "Tags: " + pack_ptr->tags); + pack_tags_text->start_juice_animation( + gui_item::JUICE_TYPE_GROW_TEXT_ELASTIC_LOW + ); + + pack_maker_text->text = + (pack_ptr->maker.empty() ? "" : "Maker: " + pack_ptr->maker); + pack_maker_text->start_juice_animation( + gui_item::JUICE_TYPE_GROW_TEXT_ELASTIC_LOW + ); + + pack_version_text->text = + (pack_ptr->version.empty() ? "" : "Version: " + pack_ptr->version); + pack_version_text->start_juice_animation( + gui_item::JUICE_TYPE_GROW_TEXT_ELASTIC_LOW + ); +} + + +/** + * @brief Draws the pack management menu. + */ +void packs_menu_t::draw() { + gui.draw(); +} + + +/** + * @brief Handles an Allegro event. + * + * @param ev The event. + */ +void packs_menu_t::handle_event(const ALLEGRO_EVENT &ev) { + if(!closing) gui.handle_event(ev); +} + +/** + * @brief Handles a player action. + * + * @param action Data about the player action. + */ +void packs_menu_t::handle_player_action(const player_action &action) { + gui.handle_player_action(action); +} + + +/** + * @brief Populates the packs list with rows for each pack. + */ +void packs_menu_t::populate_packs_list() { + for(size_t p = 0; p < pack_order.size(); p++) { + pack_bullets[p]->text = + game.content.packs.list[pack_order[p]].name; + pack_checks[p]->value = + std::find( + packs_disabled.begin(), packs_disabled.end(), pack_order[p] + ) == packs_disabled.end(); + } +} + + +/** + * @brief Starts the closing process. + */ +void packs_menu_t::start_closing() { + closing = true; + closing_timer = GAMEPLAY::MENU_EXIT_HUD_MOVE_TIME; +} + + +/** + * @brief Ticks time by one frame of logic. + * + * @param delta_t How long the frame's tick is, in seconds. + */ +void packs_menu_t::tick(float delta_t) { + //Tick the GUI. + gui.tick(delta_t); + + //Tick the menu closing. + if(closing) { + closing_timer -= delta_t; + if(closing_timer <= 0.0f) { + to_delete = true; + } + } +} + + +/** + * @brief Triggers the restart warning at the bottom of the screen. + */ +void packs_menu_t::trigger_restart_warning() { + if(!warning_text->visible) { + warning_text->visible = true; + warning_text->start_juice_animation( + gui_item::JUICE_TYPE_GROW_TEXT_ELASTIC_MEDIUM + ); + } +} diff --git a/source/source/game_states/other_menus/packs_menu.h b/source/source/game_states/other_menus/packs_menu.h new file mode 100644 index 000000000..35d59c202 --- /dev/null +++ b/source/source/game_states/other_menus/packs_menu.h @@ -0,0 +1,116 @@ +/* + * Copyright (c) Andre 'Espyo' Silva 2013. + * The following source file belongs to the open-source project Pikifen. + * Please read the included README and LICENSE files for more information. + * Pikmin is copyright (c) Nintendo. + * + * === FILE DESCRIPTION === + * Header for the pack management menu struct and related functions. + */ + +#pragma once + +#include +#include +#include + +#include + +#include "../../gui.h" + +using std::map; +using std::string; + + +namespace PACKS_MENU { +extern const string GUI_FILE_PATH; +} + + +/** + * @brief Info about the pack management menu currently being presented to + * the player. + */ +struct packs_menu_t { + public: + + //--- Members --- + + //GUI manager. + gui_manager gui; + + //Callback for when the "Back" button is pressed to leave the menu. + std::function back_callback; + + //Is the struct meant to be deleted? + bool to_delete = false; + + + //--- Function declarations --- + packs_menu_t(); + ~packs_menu_t(); + void draw(); + void handle_event(const ALLEGRO_EVENT &ev); + void handle_player_action(const player_action &action); + void tick(float delta_t); + + + private: + + //--- Members --- + + //Is it currently closing? + bool closing = false; + + //Time left until the menu finishes closing. + float closing_timer = 0.0f; + + //Working copy of the order of the packs. This is a list of internal + //names and excludes the base pack. + vector pack_order; + + //Working copy of the list of disabled packs. This is a list of internal + //names and excludes the base pack. + vector packs_disabled; + + //Pack list item. + list_gui_item* packs_list = nullptr; + + //Pack bullet items, in order. + vector pack_bullets; + + //Pack check items, in order. + vector pack_checks; + + //Pack name text item. + text_gui_item* pack_name_text = nullptr; + + //Pack description text item. + text_gui_item* pack_description_text = nullptr; + + //Pack tags text item. + text_gui_item* pack_tags_text = nullptr; + + //Pack maker text item. + text_gui_item* pack_maker_text = nullptr; + + //Pack version text item. + text_gui_item* pack_version_text = nullptr; + + //Restart warning text item. + text_gui_item* warning_text = nullptr; + + //Internal name of the currently-selected pack, if any. + string cur_pack_name; + + //Bitmaps for each pack's thumbnail. + std::map pack_thumbs; + + //--- Function declarations --- + + void change_info(int idx); + void populate_packs_list(); + void start_closing(); + void trigger_restart_warning(); + +}; diff --git a/source/source/gui.cpp b/source/source/gui.cpp index b2a028c9d..846d96ea0 100644 --- a/source/source/gui.cpp +++ b/source/source/gui.cpp @@ -87,7 +87,7 @@ bullet_gui_item::bullet_gui_item( * @brief Default bullet GUI item draw code. */ void bullet_gui_item::def_draw_code( - const point& center, const point& size + const point ¢er, const point &size ) { float item_x_start = center.x - size.x * 0.5; float text_x_offset = @@ -152,7 +152,7 @@ button_gui_item::button_gui_item( * @brief Default button GUI item draw code. */ void button_gui_item::def_draw_code( - const point& center, const point& size + const point ¢er, const point &size ) { draw_button( center, size, this->text, this->font, this->color, selected, @@ -222,7 +222,7 @@ void check_gui_item::def_activate_code() { /** * @brief Default check GUI item draw code. */ -void check_gui_item::def_draw_code(const point& center, const point& size) { +void check_gui_item::def_draw_code(const point ¢er, const point &size) { float juicy_grow_amount = get_juice_value(); draw_text( this->text, this->font, @@ -237,6 +237,8 @@ void check_gui_item::def_draw_code(const point& center, const point& size) { this->value ? game.sys_assets.bmp_checkbox_check : game.sys_assets.bmp_checkbox_no_check, + this->text.empty() ? + center : point((center.x + size.x * 0.5) - 40, center.y), point(32, -1) ); @@ -1268,7 +1270,7 @@ list_gui_item::list_gui_item() : /** * @brief Default list GUI item child selected code. */ -void list_gui_item::def_child_selected_code(const gui_item * child) { +void list_gui_item::def_child_selected_code(const gui_item* child) { //Try to center the child. float child_bottom = get_child_bottom(); if(child_bottom <= 1.0f && offset == 0.0f) { @@ -1286,7 +1288,7 @@ void list_gui_item::def_child_selected_code(const gui_item * child) { /** * @brief Default list GUI item draw code. */ -void list_gui_item::def_draw_code(const point& center, const point& size) { +void list_gui_item::def_draw_code(const point ¢er, const point &size) { draw_textured_box( center, size, game.sys_assets.bmp_frame_box, COLOR_TRANSPARENT_WHITE @@ -1374,7 +1376,7 @@ void list_gui_item::def_draw_code(const point& center, const point& size) { /** * @brief Default list GUI item event code. */ -void list_gui_item::def_event_code(const ALLEGRO_EVENT & ev) { +void list_gui_item::def_event_code(const ALLEGRO_EVENT &ev) { if( ev.type == ALLEGRO_EVENT_MOUSE_AXES && is_mouse_on(point(ev.mouse.x, ev.mouse.y)) && @@ -1459,7 +1461,7 @@ picker_gui_item::picker_gui_item( /** * @brief Default picker GUI item activate code. */ -void picker_gui_item::def_activate_code(const point& cursor_pos) { +void picker_gui_item::def_activate_code(const point &cursor_pos) { if(cursor_pos.x >= get_reference_center().x) { on_next(); } else { @@ -1471,7 +1473,7 @@ void picker_gui_item::def_activate_code(const point& cursor_pos) { /** * @brief Default picker GUI item draw code. */ -void picker_gui_item::def_draw_code(const point& center, const point& size) { +void picker_gui_item::def_draw_code(const point ¢er, const point &size) { if(this->nr_options != 0 && selected) { point option_boxes_start( center.x - size.x / 2.0f + 20.0f, @@ -1587,7 +1589,7 @@ bool picker_gui_item::def_menu_dir_code(size_t button_id) { /** * @brief Default picker GUI item mouse over code. */ -void picker_gui_item::def_mouse_over_code(const point & cursor_pos) { +void picker_gui_item::def_mouse_over_code(const point &cursor_pos) { arrow_highlight = cursor_pos.x >= get_reference_center().x ? 1 : 0; } @@ -1613,7 +1615,7 @@ scroll_gui_item::scroll_gui_item() : /** * @brief Default scroll GUI item draw code. */ -void scroll_gui_item::def_draw_code(const point& center, const point& size) { +void scroll_gui_item::def_draw_code(const point ¢er, const point &size) { float bar_y = 0.0f; //Top, in height ratio. float bar_h = 0.0f; //In height ratio. float list_bottom = list_item->get_child_bottom(); @@ -1648,7 +1650,7 @@ void scroll_gui_item::def_draw_code(const point& center, const point& size) { /** * @brief Default scroll GUI item event code. */ -void scroll_gui_item::def_event_code(const ALLEGRO_EVENT & ev) { +void scroll_gui_item::def_event_code(const ALLEGRO_EVENT &ev) { if( ev.type == ALLEGRO_EVENT_MOUSE_BUTTON_DOWN && ev.mouse.button == 1 && @@ -1700,7 +1702,7 @@ text_gui_item::text_gui_item( /** * @brief Default text GUI item draw code. */ -void text_gui_item::def_draw_code(const point& center, const point& size) { +void text_gui_item::def_draw_code(const point ¢er, const point &size) { int text_x = center.x; switch(this->flags) { case ALLEGRO_ALIGN_LEFT: { @@ -1781,7 +1783,7 @@ tooltip_gui_item::tooltip_gui_item(gui_manager* gui) : /** * @brief Default tooltip GUI item draw code. */ -void tooltip_gui_item::def_draw_code(const point& center, const point& size) { +void tooltip_gui_item::def_draw_code(const point ¢er, const point &size) { string cur_text = this->gui->get_current_tooltip(); if(cur_text != this->prev_text) { this->start_juice_animation(JUICE_TYPE_GROW_TEXT_LOW); diff --git a/source/source/options.cpp b/source/source/options.cpp index e08e8aa9a..3c2f6fd0a 100644 --- a/source/source/options.cpp +++ b/source/source/options.cpp @@ -246,6 +246,8 @@ void options_t::load(data_node* file) { unsigned char editor_view_mode_c; unsigned char auto_throw_mode_c; unsigned char leaving_confirmation_mode_c; + string pack_load_order_str; + string packs_disabled_str; rs.set("ambiance_volume", ambiance_volume); rs.set("anim_editor_bg_texture", anim_editor_bg_texture); @@ -289,7 +291,10 @@ void options_t::load(data_node* file) { rs.set("middle_zoom_level", zoom_mid_level); rs.set("mipmaps", mipmaps_enabled); rs.set("music_volume", music_volume); + rs.set("pack_order", pack_load_order_str); + rs.set("packs_disabled", packs_disabled_str); rs.set("particle_editor_bg_texture", particle_editor_bg_texture); + rs.set("particle_editor_grid_interval", particle_editor_grid_interval); rs.set("resolution", resolution_str); rs.set("smooth_scaling", smooth_scaling); rs.set("show_hud_input_icons", show_hud_input_icons); @@ -351,6 +356,9 @@ void options_t::load(data_node* file) { editor_secondary_color.a = 1.0f; editor_text_color.a = 1.0f; editor_highlight_color.a = 1.0f; + + pack_order = semicolon_list_to_vector(pack_load_order_str); + packs_disabled = semicolon_list_to_vector(packs_disabled_str); } @@ -422,6 +430,15 @@ void options_t::save(data_node* file) const { if(!open_nodes_str.empty()) open_nodes_str.pop_back(); //Other options. + string pack_load_order_str; + for(size_t p = 0; p < pack_order.size(); p++) { + pack_load_order_str += (p > 0 ? ";" : "") + pack_order[p]; + } + string packs_disabled_str; + for(size_t p = 0; p < packs_disabled.size(); p++) { + packs_disabled_str += (p > 0 ? ";" : "") + packs_disabled[p]; + } + file->add( new data_node( "ambiance_volume", @@ -662,6 +679,24 @@ void options_t::save(data_node* file) const { f2s(music_volume) ) ); + file->add( + new data_node( + "pack_order", + pack_load_order_str + ) + ); + file->add( + new data_node( + "packs_disabled", + packs_disabled_str + ) + ); + file->add( + new data_node( + "particle_editor_grid_interval", + i2s(particle_editor_grid_interval) + ) + ); file->add( new data_node( "particle_editor_bg_texture", diff --git a/source/source/options.h b/source/source/options.h index dfee91ebc..4d047102c 100644 --- a/source/source/options.h +++ b/source/source/options.h @@ -212,9 +212,6 @@ struct options_t { //Snap to grid? bool gui_editor_snap = OPTIONS::DEF_GUI_EDITOR_SNAP; - //Grid interval in the particle editor, in units. - float particle_editor_grid_interval = OPTIONS::DEF_PARTICLE_EDITOR_GRID_INTERVAL; - //Player's intended option for fullscreen, before restarting the game. bool intended_win_fullscreen = OPTIONS::DEF_WIN_FULLSCREEN; @@ -252,9 +249,20 @@ struct options_t { //Music volume (0 - 1). float music_volume = OPTIONS::DEF_MUSIC_VOLUME; + + //Preferred pack load order. + vector pack_order; + + //Disabled packs. + vector packs_disabled; + + //Grid interval in the particle editor, in units. + float particle_editor_grid_interval = + OPTIONS::DEF_PARTICLE_EDITOR_GRID_INTERVAL; //Background texture for the particle editor, if any. - string particle_editor_bg_texture = OPTIONS::DEF_PARTICLE_EDITOR_BG_TEXTURE; + string particle_editor_bg_texture = + OPTIONS::DEF_PARTICLE_EDITOR_BG_TEXTURE; //True to use interpolation when graphics are scaled up/down. bool smooth_scaling = OPTIONS::DEF_SMOOTH_SCALING; diff --git a/source/source/utils/general_utils.cpp b/source/source/utils/general_utils.cpp index 4bfc7a06d..0079a7c24 100644 --- a/source/source/utils/general_utils.cpp +++ b/source/source/utils/general_utils.cpp @@ -296,43 +296,6 @@ string standardize_path(const string &path) { } -#if defined(_WIN32) - - -/** - * @brief An implementation of strsignal from POSIX. - * - * @param signum Signal number. - * @return The string. - */ -string strsignal(int signum) { - switch(signum) { - case SIGINT: { - return "SIGINT"; - } case SIGILL: { - return "SIGILL"; - } case SIGFPE: { - return "SIGFPE"; - } case SIGSEGV: { - return "SIGSEGV"; - } case SIGTERM: { - return "SIGTERM"; - } case SIGBREAK: { - return "SIGBREAK"; - } case SIGABRT: { - return "SIGABRT"; - } case SIGABRT_COMPAT: { - return "SIGABRT_COMPAT"; - } default: { - return "Unknown"; - } - } -} - - -#endif //if defined(_WIN32) - - /** * @brief Returns a string that's a join of the strings in the specified vector, * but only past a certain position. The strings are joined with a space diff --git a/source/source/utils/general_utils.h b/source/source/utils/general_utils.h index 85b9ff598..324cf61fe 100644 --- a/source/source/utils/general_utils.h +++ b/source/source/utils/general_utils.h @@ -10,6 +10,7 @@ #pragma once +#include #include #include #include @@ -444,23 +445,121 @@ struct timer { }; - string get_current_time(bool file_name_friendly); string sanitize_file_name(const string &s); string standardize_path(const string &path); string vector_tail_to_string(const vector &v, size_t pos); + +/** + * @brief Removes elements from a vector if they show up in the ban list. + * + * @tparam t Type of contents of the vector and ban list. + * @param v Vector to filter. + * @param ban_list List of items that must be banned. + * @return The filtered vector. + */ +template +vector filter_vector_with_ban_list( + const vector &v, const vector &ban_list +) { + vector result = v; + for(size_t i = 0; i < result.size();) { + if( + std::find(ban_list.begin(), ban_list.end(), result[i]) != + ban_list.end() + ) { + result.erase(result.begin() + i); + } else { + i++; + } + } + return result; +} + + +/** + * @brief Sorts a vector, using the preference list to figure out which + * elements go before which. Elements not in the preference list will go + * to the end, in the same order as they are presented in the original + * vector. + * + * @tparam t Type of contents of the vector. + * @param v Vector to sort. + * @param preference_list Preference list. + * @param equal If not nullptr, use this function to compare whether + * an item of t1 matches an item of t2. + * @param less If not nullptr, use this function to sort missing items with. + * @param unknowns If not nullptr, unknown preferences (i.e. items in + * the preference list but not inside the vector) will be added here. + * @return The sorted vector. + */ +template +vector sort_vector_with_preference_list( + const vector &v, const vector preference_list, + vector* unknowns = nullptr +) { + vector result; + vector missing_items; + result.reserve(v.size()); + + //Sort the existing items. + for(size_t p = 0; p < preference_list.size(); p++) { + bool found_in_vector = false; + for(auto &i : v) { + if(i == preference_list[p]) { + found_in_vector = true; + result.push_back(i); + break; + } + } + if(!found_in_vector && unknowns) { + unknowns->push_back(preference_list[p]); + } + } + + //Find the missing items. + for(auto &i : v) { + bool found_in_preferences = false; + for(auto &p : preference_list) { + if(i == p) { + found_in_preferences = true; + break; + } + } + if(!found_in_preferences) { + //Missing from the list? Add it to the "missing" pile. + missing_items.push_back(i); + } + } + + //Sort and place the missing items. + if(!missing_items.empty()) { + std::sort( + missing_items.begin(), + missing_items.end() + ); + result.insert( + result.end(), + missing_items.begin(), + missing_items.end() + ); + } + + return result; +} + + /** * @brief Returns whether or not the two vectors contain the same items, * regardless of order. * - * @tparam t The contents of the vector. + * @tparam t Type of contents of the vector. * @param v1 First vector. * @param v2 Second vector. * @return Whether they contain the same items. */ - template bool vectors_contain_same(const vector &v1, const vector &v2) { if(v1.size() != v2.size()) { @@ -482,8 +581,3 @@ bool vectors_contain_same(const vector &v1, const vector &v2) { return true; } - - -#if defined(_WIN32) -string strsignal(int signum); -#endif //#if defined(_WIN32) diff --git a/source/source/utils/os_utils.cpp b/source/source/utils/os_utils.cpp new file mode 100644 index 000000000..ad68f833b --- /dev/null +++ b/source/source/utils/os_utils.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) Andre 'Espyo' Silva 2013. + * The following source file belongs to the open-source project Pikifen. + * Please read the included README and LICENSE files for more information. + * Pikmin is copyright (c) Nintendo. + * + * === FILE DESCRIPTION === + * Operative system utility functions. + * These don't contain logic specific to the Pikifen project. + */ + +#include + +#include "os_utils.h" + + +/** + * @brief Opens the operative system's file explorer on the specified folder. + * + * @param path Path of the folder to open. + * @return Whether it succeeded. + */ +bool open_file_explorer(const string& path) { +#ifdef _WIN32 + string command = "explorer " + path; +#elif __APPLE__ + string command = "open " + path; +#elif __linux__ + string command = "xdg-open " + path; +#else + return false; +#endif + return std::system(command.c_str()) == 0; +} + + +#if defined(_WIN32) +/** + * @brief An implementation of strsignal from POSIX. + * + * @param signum Signal number. + * @return The string. + */ +string strsignal(int signum) { + switch(signum) { + case SIGINT: { + return "SIGINT"; + } case SIGILL: { + return "SIGILL"; + } case SIGFPE: { + return "SIGFPE"; + } case SIGSEGV: { + return "SIGSEGV"; + } case SIGTERM: { + return "SIGTERM"; + } case SIGBREAK: { + return "SIGBREAK"; + } case SIGABRT: { + return "SIGABRT"; + } case SIGABRT_COMPAT: { + return "SIGABRT_COMPAT"; + } default: { + return "Unknown"; + } + } +} +#endif //if defined(_WIN32) diff --git a/source/source/utils/os_utils.h b/source/source/utils/os_utils.h new file mode 100644 index 000000000..9401b2dc4 --- /dev/null +++ b/source/source/utils/os_utils.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) Andre 'Espyo' Silva 2013. + * The following source file belongs to the open-source project Pikifen. + * Please read the included README and LICENSE files for more information. + * Pikmin is copyright (c) Nintendo. + * + * === FILE DESCRIPTION === + * Header for operative system utility functions. + * These don't contain logic specific to the Pikifen project. + */ + +#pragma once + +#include + +using std::string; + + +bool open_file_explorer(const std::string& path); + +#if defined(_WIN32) +string strsignal(int signum); +#endif //#if defined(_WIN32)