diff --git a/data/campaigns/An_Orcish_Incursion/_main.cfg b/data/campaigns/An_Orcish_Incursion/_main.cfg index 771bc67586cf..eb82558c7c7a 100644 --- a/data/campaigns/An_Orcish_Incursion/_main.cfg +++ b/data/campaigns/An_Orcish_Incursion/_main.cfg @@ -11,6 +11,8 @@ name= _ "An Orcish Incursion" abbrev= _ "AOI" rank=10 + start_year="8 YW" + end_year="9 YW" first_scenario=01_Defend_the_Forest define="CAMPAIGN_AN_ORCISH_INCURSION" description=_ "Defend the forests of the elves against the first orcs to reach the Great Continent, learning valuable tactics as you do so. diff --git a/data/campaigns/Dead_Water/_main.cfg b/data/campaigns/Dead_Water/_main.cfg index cc799999e541..75bdef922391 100644 --- a/data/campaigns/Dead_Water/_main.cfg +++ b/data/campaigns/Dead_Water/_main.cfg @@ -7,6 +7,8 @@ [campaign] id=Dead_Water rank=170 + start_year="626 YW" + end_year="627 YW" icon="units/undead/soulless-swimmer.png~RC(magenta>blue)" name= _ "Dead Water" abbrev= _ "DW" diff --git a/data/campaigns/Delfadors_Memoirs/_main.cfg b/data/campaigns/Delfadors_Memoirs/_main.cfg index 388b66586afd..dd9ccd4c6b6a 100644 --- a/data/campaigns/Delfadors_Memoirs/_main.cfg +++ b/data/campaigns/Delfadors_Memoirs/_main.cfg @@ -22,6 +22,8 @@ name= _ "Delfador’s Memoirs" abbrev=_ "DM" rank=160 + start_year="468 YW" + end_year="470 YW" icon="units/human-magi/elder-mage.png~RC(magenta>red)" image="data/campaigns/Delfadors_Memoirs/images/campaign_image.png" first_scenario=01_Overture diff --git a/data/campaigns/Descent_Into_Darkness/_main.cfg b/data/campaigns/Descent_Into_Darkness/_main.cfg index 1335ab40531a..a6396e95a220 100644 --- a/data/campaigns/Descent_Into_Darkness/_main.cfg +++ b/data/campaigns/Descent_Into_Darkness/_main.cfg @@ -7,6 +7,8 @@ [campaign] id=Descent_into_Darkness rank=150 + start_year="389 YW" + end_year="390 YW" icon="data/campaigns/Descent_Into_Darkness/images/units/dark-mage.png~RC(magenta>red)" image="data/campaigns/Descent_Into_Darkness/images/campaign_image.png" name= _ "Descent into Darkness" diff --git a/data/campaigns/Eastern_Invasion/_main.cfg b/data/campaigns/Eastern_Invasion/_main.cfg index 17b8e6159193..45a76d8703f5 100644 --- a/data/campaigns/Eastern_Invasion/_main.cfg +++ b/data/campaigns/Eastern_Invasion/_main.cfg @@ -7,6 +7,8 @@ [campaign] id=Eastern_Invasion rank=130 + start_year="625 YW" + end_year="627 YW" icon="units/human-loyalists/general.png~RC(magenta>red)" name= _ "Eastern Invasion" abbrev= _ "EI" diff --git a/data/campaigns/Heir_To_The_Throne/_main.cfg b/data/campaigns/Heir_To_The_Throne/_main.cfg index b90096ced919..43360fe5fbef 100644 --- a/data/campaigns/Heir_To_The_Throne/_main.cfg +++ b/data/campaigns/Heir_To_The_Throne/_main.cfg @@ -11,6 +11,8 @@ image="data/campaigns/Heir_To_The_Throne/images/campaign_image.png" abbrev= _ "HttT" rank=20 + start_year="517 YW" + end_year="518 YW" define=CAMPAIGN_HEIR_TO_THE_THRONE first_scenario=01_The_Elves_Besieged diff --git a/data/campaigns/Legend_of_Wesmere/_main.cfg b/data/campaigns/Legend_of_Wesmere/_main.cfg index 0624cf49ed7f..035dd79e945c 100644 --- a/data/campaigns/Legend_of_Wesmere/_main.cfg +++ b/data/campaigns/Legend_of_Wesmere/_main.cfg @@ -32,6 +32,8 @@ id=LOW define=CAMPAIGN_LOW rank=125 + start_year="20 YW" + end_year="93 YW" type=hybrid diff --git a/data/campaigns/Liberty/_main.cfg b/data/campaigns/Liberty/_main.cfg index fa8f13dcedf6..d4811446e0d5 100644 --- a/data/campaigns/Liberty/_main.cfg +++ b/data/campaigns/Liberty/_main.cfg @@ -10,6 +10,7 @@ name= _ "Liberty" abbrev= _ "Liberty" rank=110 + year="501 YW" first_scenario=01_The_Raid define=CAMPAIGN_LIBERTY icon="units/human-outlaws/fugitive.png~RC(magenta>red)" diff --git a/data/campaigns/Northern_Rebirth/_main.cfg b/data/campaigns/Northern_Rebirth/_main.cfg index 2324fc40e630..984b50116855 100644 --- a/data/campaigns/Northern_Rebirth/_main.cfg +++ b/data/campaigns/Northern_Rebirth/_main.cfg @@ -9,6 +9,8 @@ name= _ "Northern Rebirth" abbrev= _ "NR" rank=240 + start_year="534 YW" + end_year="535 YW" first_scenario=01_Breaking_the_Chains define=CAMPAIGN_NORTHERN_REBIRTH diff --git a/data/campaigns/Sceptre_of_Fire/_main.cfg b/data/campaigns/Sceptre_of_Fire/_main.cfg index 0750de73d2b4..6843b9ecb17a 100644 --- a/data/campaigns/Sceptre_of_Fire/_main.cfg +++ b/data/campaigns/Sceptre_of_Fire/_main.cfg @@ -11,6 +11,8 @@ name= _ "The Sceptre of Fire" abbrev= _ "SoF" rank=215 + start_year="25 YW" + end_year="40 YW" define="CAMPAIGN_SCEPTRE_FIRE" extra_defines=ENABLE_DWARVISH_RUNESMITH first_scenario="1_A_Bargain_is_Struck" diff --git a/data/campaigns/Secrets_of_the_Ancients/_main.cfg b/data/campaigns/Secrets_of_the_Ancients/_main.cfg index bd771cca4a14..19e818e7db97 100644 --- a/data/campaigns/Secrets_of_the_Ancients/_main.cfg +++ b/data/campaigns/Secrets_of_the_Ancients/_main.cfg @@ -10,6 +10,8 @@ name= _ "Secrets of the Ancients" abbrev= _ "SotA" rank=180 + start_year="22 YW" + end_year="23 YW" first_scenario=01_Slipping_Away extra_defines=ENABLE_ANCIENT_LICH,ENABLE_DEATH_KNIGHT {CAMPAIGN_DIFFICULTY EASY "units/undead-skeletal/skeleton/skeleton-idle-2.png~RC(magenta>red)"( _ "Unpleasant") ( _ "Normal")} diff --git a/data/campaigns/Son_Of_The_Black_Eye/_main.cfg b/data/campaigns/Son_Of_The_Black_Eye/_main.cfg index 387fd2f512eb..c48e2a478ab0 100644 --- a/data/campaigns/Son_Of_The_Black_Eye/_main.cfg +++ b/data/campaigns/Son_Of_The_Black_Eye/_main.cfg @@ -9,6 +9,8 @@ name= _ "Son of the Black-Eye" abbrev= _ "SotBE" rank=220 + start_year="842 YW" + end_year="858 YW" first_scenario=01_End_of_Peace define=CAMPAIGN_SON_OF_THE_BLACK_EYE diff --git a/data/campaigns/The_Hammer_of_Thursagan/_main.cfg b/data/campaigns/The_Hammer_of_Thursagan/_main.cfg index 8c63a2600c74..bfbb14d6309c 100644 --- a/data/campaigns/The_Hammer_of_Thursagan/_main.cfg +++ b/data/campaigns/The_Hammer_of_Thursagan/_main.cfg @@ -11,6 +11,8 @@ image="data/campaigns/The_Hammer_of_Thursagan/images/campaign_image.png" abbrev= _ "THoT" rank=140 + start_year="550 YW" + end_year="551 YW" define=CAMPAIGN_THE_HAMMER_OF_THURSAGAN first_scenario=01_At_the_East_Gate diff --git a/data/campaigns/The_Rise_Of_Wesnoth/_main.cfg b/data/campaigns/The_Rise_Of_Wesnoth/_main.cfg index 2eedc60efc3b..9ca8e03d9da4 100644 --- a/data/campaigns/The_Rise_Of_Wesnoth/_main.cfg +++ b/data/campaigns/The_Rise_Of_Wesnoth/_main.cfg @@ -7,6 +7,8 @@ [campaign] id=The_Rise_of_Wesnoth rank=230 + start_year="2 BW" + end_year="1 YW" name= _ "The Rise of Wesnoth" icon="data/campaigns/The_Rise_Of_Wesnoth/images/units/noble-lord.png" image="data/campaigns/The_Rise_Of_Wesnoth/images/campaign_image.png" diff --git a/data/campaigns/The_South_Guard/_main.cfg b/data/campaigns/The_South_Guard/_main.cfg index 9037850a1302..ad19b5143d2a 100644 --- a/data/campaigns/The_South_Guard/_main.cfg +++ b/data/campaigns/The_South_Guard/_main.cfg @@ -11,6 +11,8 @@ define=CAMPAIGN_THE_SOUTH_GUARD rank=15 + start_year="607 YW" + end_year="608 YW" icon="data/campaigns/The_South_Guard/images/deoran/horseman-commander-defend.png" image="data/campaigns/The_South_Guard/images/campaign_image.png" diff --git a/data/campaigns/Two_Brothers/_main.cfg b/data/campaigns/Two_Brothers/_main.cfg index 373fe4cb7726..61b93e3f1dd7 100644 --- a/data/campaigns/Two_Brothers/_main.cfg +++ b/data/campaigns/Two_Brothers/_main.cfg @@ -7,6 +7,7 @@ [campaign] id=Two_Brothers rank=5 + year="363 YW" icon="units/human-loyalists/knight/knight.png~RC(magenta>red)~CROP(13,11,72,72)" image="data/campaigns/Two_Brothers/images/campaign_image.png" name= _ "A Tale of Two Brothers" diff --git a/data/campaigns/Under_the_Burning_Suns/_main.cfg b/data/campaigns/Under_the_Burning_Suns/_main.cfg index 05119147a2f6..0dec5f94dd25 100644 --- a/data/campaigns/Under_the_Burning_Suns/_main.cfg +++ b/data/campaigns/Under_the_Burning_Suns/_main.cfg @@ -13,6 +13,7 @@ image="data/campaigns/Under_the_Burning_Suns/images/campaign_image.png" abbrev= _ "UtBS" rank=250 + year="300 AF" define=CAMPAIGN_UNDER_THE_BURNING_SUNS first_scenario=01_The_Morning_After diff --git a/data/gui/window/campaign_dialog.cfg b/data/gui/window/campaign_dialog.cfg index a6014bea79ca..a09febfa774a 100644 --- a/data/gui/window/campaign_dialog.cfg +++ b/data/gui/window/campaign_dialog.cfg @@ -195,6 +195,61 @@ [grid] + [row] + grow_factor = 1 + + [column] + grow_factor = 0 + + border = "all" + border_size = 5 + horizontal_grow = true + + [grid] + [row] + [column] + grow_factor = 0 + border = "all" + border_size = 5 + horizontal_grow = true + + [label] + label = _"Sort by:" + [/label] + [/column] + [column] + grow_factor = 1 + border = "all" + border_size = 5 + horizontal_grow = true + + [toggle_button] + definition = "listbox_header" + id = "sort_name" + label = _"Name" + tooltip = _"Sort by full campaign name in alphabetical order" + [/toggle_button] + [/column] + [column] + grow_factor = 1 + border = "all" + border_size = 5 + horizontal_grow = true + + [toggle_button] + definition = "listbox_header" + id = "sort_time" + label = _"Dates" + tooltip = _"Sort in approximate chronological order of story events" + [/toggle_button] + [/column] + [/row] + [/grid] + + [/column] + + [/row] + [row] grow_factor = 1 diff --git a/projectfiles/VC12/wesnoth.vcxproj b/projectfiles/VC12/wesnoth.vcxproj index 57eea2682b0e..80a53d6ed332 100644 --- a/projectfiles/VC12/wesnoth.vcxproj +++ b/projectfiles/VC12/wesnoth.vcxproj @@ -3006,6 +3006,14 @@ $(IntDir)Tests\ $(IntDir)Tests\ + + true + true + $(IntDir)Tests\ + $(IntDir)Tests\ + $(IntDir)Tests\ + $(IntDir)Tests\ + true $(IntDir)Tests\ @@ -3284,6 +3292,12 @@ $(IntDir)utils\ $(IntDir)utils\ + + $(IntDir)utils\ + $(IntDir)utils\ + $(IntDir)utils\ + $(IntDir)utils\ + $(IntDir)utils\ $(IntDir)utils\ @@ -4004,6 +4018,7 @@ + diff --git a/projectfiles/VC12/wesnoth.vcxproj.filters b/projectfiles/VC12/wesnoth.vcxproj.filters index 4e8a63376bab..15aecc46d944 100644 --- a/projectfiles/VC12/wesnoth.vcxproj.filters +++ b/projectfiles/VC12/wesnoth.vcxproj.filters @@ -1541,6 +1541,12 @@ Preferences + + utils + + + Tests + @@ -2988,6 +2994,9 @@ Preferences + + utils + diff --git a/source_lists/test b/source_lists/test index 72be0bd3597f..44e4e1854f62 100644 --- a/source_lists/test +++ b/source_lists/test @@ -13,6 +13,7 @@ tests/test_formula_ai.cpp tests/test_formula_core.cpp tests/test_formula_function.cpp tests/test_image_modifications.cpp +tests/test_irdya_date.cpp tests/test_lexical_cast.cpp tests/test_make_enum.cpp tests/test_map_location.cpp diff --git a/source_lists/wesnoth b/source_lists/wesnoth index 6ce15a3a2fa6..c8069e3c3ab4 100644 --- a/source_lists/wesnoth +++ b/source_lists/wesnoth @@ -387,6 +387,7 @@ units/types.cpp units/udisplay.cpp units/unit.cpp utils/context_free_grammar_generator.cpp +utils/irdya_datetime.hpp utils/markov_generator.cpp utils/name_generator_factory.cpp variable.cpp diff --git a/src/game_initialization/create_engine.cpp b/src/game_initialization/create_engine.cpp index 7b3644ed53cf..c6f4c33f33ad 100644 --- a/src/game_initialization/create_engine.cpp +++ b/src/game_initialization/create_engine.cpp @@ -185,6 +185,16 @@ campaign::campaign(const config& data) , min_players_(2) , max_players_(2) { + if(data.has_attribute("start_year")) { + dates_.first = irdya_date::read_date(data["start_year"]); + if(data.has_attribute("end_year")) { + dates_.second = irdya_date::read_date(data["end_year"]); + } else { + dates_.second = dates_.first; + } + } else if(data.has_attribute("year")) { + dates_.first = dates_.second = irdya_date::read_date(data["year"]); + } set_metadata(); } diff --git a/src/game_initialization/create_engine.hpp b/src/game_initialization/create_engine.hpp index 5de61ac85659..13d27aca4faa 100644 --- a/src/game_initialization/create_engine.hpp +++ b/src/game_initialization/create_engine.hpp @@ -19,6 +19,7 @@ #include "generators/map_generator.hpp" #include "mp_game_settings.hpp" #include "utils/make_enum.hpp" +#include "utils/irdya_datetime.hpp" #include #include @@ -253,6 +254,11 @@ class campaign : public level return min_players_ <= player_count && max_players_ >= player_count; } + std::pair dates() const + { + return dates_; + } + private: campaign(const campaign&) = delete; void operator=(const campaign&) = delete; @@ -262,6 +268,7 @@ class campaign : public level std::string image_label_; int min_players_; int max_players_; + std::pair dates_; }; class create_engine diff --git a/src/gui/dialogs/campaign_selection.cpp b/src/gui/dialogs/campaign_selection.cpp index d65c49b3de4f..34808650d927 100644 --- a/src/gui/dialogs/campaign_selection.cpp +++ b/src/gui/dialogs/campaign_selection.cpp @@ -38,6 +38,7 @@ #include "serialization/string_utils.hpp" #include "utils/functional.hpp" +#include "utils/irdya_datetime.hpp" #include "video.hpp" namespace gui2 @@ -95,7 +96,11 @@ void campaign_selection::campaign_selected(window& window) assert(tree.selected_item()); if(tree.selected_item()->id() != "") { - const unsigned choice = lexical_cast(tree.selected_item()->id()); + auto iter = std::find(page_ids_.begin(), page_ids_.end(), tree.selected_item()->id()); + const int choice = iter - page_ids_.begin(); + if(iter == page_ids_.end()) { + return; + } multi_page& pages = find_widget(&window, "campaign_details", false); pages.select_page(choice); @@ -104,6 +109,78 @@ void campaign_selection::campaign_selected(window& window) } +void campaign_selection::sort_campaigns(window& window, campaign_selection::CAMPAIGN_ORDER order, bool ascending) +{ + using level_ptr = ng::create_engine::level_ptr; + auto levels = engine_.get_levels_by_type_unfiltered(ng::level::TYPE::SP_CAMPAIGN); + switch(order) { + case RANK: // Already sorted by rank + if(!ascending) { + // This'll actually never happen, but who knows if that'll ever change... + std::reverse(levels.begin(), levels.end()); + } + break; + case DATE: + std::sort(levels.begin(), levels.end(), [ascending](const level_ptr& a, const level_ptr& b) { + auto cpn_a = std::dynamic_pointer_cast(a), cpn_b = std::dynamic_pointer_cast(b); + if(cpn_b == nullptr) return cpn_a != nullptr; + if(cpn_a == nullptr) return false; + return ascending ? cpn_a->dates().first < cpn_b->dates().first : cpn_a->dates().first > cpn_b->dates().first; + }); + break; + case NAME: + std::sort(levels.begin(), levels.end(), [ascending](const level_ptr& a, const level_ptr& b) { + int cmp = translation::icompare(a->name(), b->name()); + return ascending ? cmp < 0 : cmp > 0; + }); + break; + } + + tree_view& tree = find_widget(&window, "campaign_tree", false); + // Remember which campaign was selected... + std::string was_selected = tree.selected_item()->id(); + tree.clear(); + for(const auto& level : levels) { + add_campaign_to_tree(window, level->data()); + } + + if(!was_selected.empty()) { + tree_view_node& node = find_widget(&window, was_selected, false); + node.select_node(); + } +} + +void campaign_selection::toggle_sorting_selection(window& window, CAMPAIGN_ORDER order) { + static bool force = false; + if(force) { + return; + } + if(current_sorting_ == order) { + if(currently_sorted_asc_) { + currently_sorted_asc_ = false; + } else { + currently_sorted_asc_ = true; + current_sorting_ = RANK; + } + } else if(current_sorting_ == RANK) { + currently_sorted_asc_ = true; + current_sorting_ = order; + } else { + currently_sorted_asc_ = true; + current_sorting_ = order; + force = true; + if(order == NAME) { + toggle_button& sort_time = find_widget(&window, "sort_time", false); + sort_time.set_value(0); + } else if(order == DATE) { + toggle_button& sort_name = find_widget(&window, "sort_name", false); + sort_name.set_value(0); + } + force = false; + } + sort_campaigns(window, current_sorting_, currently_sorted_asc_); +} + void campaign_selection::pre_show(window& window) { /***** Setup campaign tree. *****/ @@ -112,33 +189,25 @@ void campaign_selection::pre_show(window& window) tree.set_selection_change_callback( std::bind(&campaign_selection::campaign_selected, this, std::ref(window))); + toggle_button& sort_name = find_widget(&window, "sort_name", false); + toggle_button& sort_time = find_widget(&window, "sort_time", false); + sort_name.set_callback_state_change(std::bind(&campaign_selection::toggle_sorting_selection, this, std::ref(window), NAME)); + sort_time.set_callback_state_change(std::bind(&campaign_selection::toggle_sorting_selection, this, std::ref(window), DATE)); + window.keyboard_capture(&tree); /***** Setup campaign details. *****/ multi_page& pages = find_widget(&window, "campaign_details", false); - unsigned id = 0; for(const auto & level : engine_.get_levels_by_type_unfiltered(ng::level::TYPE::SP_CAMPAIGN)) { const config& campaign = level->data(); /*** Add tree item ***/ - std::map data; - string_map item; - - item["label"] = campaign["icon"]; - data.emplace("icon", item); - - item["label"] = campaign["name"]; - data.emplace("name", item); - - item["label"] = campaign["completed"].to_bool() ? "misc/laurel.png" : "misc/blank-hex.png"; - data.emplace("victory", item); - - tree.add_node("campaign", data).set_id(std::to_string(id++)); + add_campaign_to_tree(window, campaign); /*** Add detail item ***/ - item.clear(); - data.clear(); + std::map data; + string_map item; item["label"] = campaign["description"]; item["use_markup"] = "true"; @@ -153,6 +222,7 @@ void campaign_selection::pre_show(window& window) data.emplace("image", item); pages.add_page(data); + page_ids_.push_back(campaign["id"]); } // @@ -184,6 +254,24 @@ void campaign_selection::pre_show(window& window) campaign_selected(window); } +void campaign_selection::add_campaign_to_tree(window& window, const config& campaign) +{ + tree_view& tree = find_widget(&window, "campaign_tree", false); + std::map data; + string_map item; + + item["label"] = campaign["icon"]; + data.emplace("icon", item); + + item["label"] = campaign["name"]; + data.emplace("name", item); + + item["label"] = campaign["completed"].to_bool() ? "misc/laurel.png" : "misc/blank-hex.png"; + data.emplace("victory", item); + + tree.add_node("campaign", data).set_id(campaign["id"]); +} + void campaign_selection::post_show(window& window) { tree_view& tree = find_widget(&window, "campaign_tree", false); @@ -194,7 +282,10 @@ void campaign_selection::post_show(window& window) assert(tree.selected_item()); if(tree.selected_item()->id() != "") { - choice_ = lexical_cast(tree.selected_item()->id()); + auto iter = std::find(page_ids_.begin(), page_ids_.end(), tree.selected_item()->id()); + if(iter != page_ids_.end()) { + choice_ = iter - page_ids_.begin(); + } } deterministic_ = find_widget(&window, "checkbox_deterministic", false).get_value_bool(); diff --git a/src/gui/dialogs/campaign_selection.hpp b/src/gui/dialogs/campaign_selection.hpp index 9386dc59d5ac..24e5503a8e5c 100644 --- a/src/gui/dialogs/campaign_selection.hpp +++ b/src/gui/dialogs/campaign_selection.hpp @@ -33,6 +33,7 @@ class campaign_selection : public modal_dialog , choice_(-1) , deterministic_(false) , mod_states_() + , current_sorting_(RANK) { set_restore(true); } @@ -62,6 +63,15 @@ class campaign_selection : public modal_dialog /** Inherited from modal_dialog. */ virtual void post_show(window& window) override; + enum CAMPAIGN_ORDER {RANK, DATE, NAME} current_sorting_; + bool currently_sorted_asc_ = true; + + void sort_campaigns(window& window, CAMPAIGN_ORDER order, bool ascending); + + void add_campaign_to_tree(window& window, const config& campaign); + + void toggle_sorting_selection(window& window, CAMPAIGN_ORDER order); + void mod_toggled(window& window); ng::create_engine& engine_; @@ -73,6 +83,8 @@ class campaign_selection : public modal_dialog bool deterministic_; boost::dynamic_bitset<> mod_states_; + + std::vector page_ids_; }; } // namespace dialogs diff --git a/src/tests/test_irdya_date.cpp b/src/tests/test_irdya_date.cpp new file mode 100644 index 000000000000..c6db28fe961d --- /dev/null +++ b/src/tests/test_irdya_date.cpp @@ -0,0 +1,91 @@ +/* +Copyright (C) 2003 - 2017 by the Battle for Wesnoth Project http://www.wesnoth.org/ + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY. + +See the COPYING file for more details. +*/ + +#define GETTEXT_DOMAIN "wesnoth-test" + +#include "utils/irdya_datetime.hpp" +#include + +BOOST_AUTO_TEST_SUITE(test_irdya_datetime) + +BOOST_AUTO_TEST_CASE(test_irdya_date_parse) { + irdya_date BW_423 = irdya_date::read_date(" 423 BW "); + irdya_date YW_123 = irdya_date::read_date(" 123 YW "); + irdya_date BF_109 = irdya_date::read_date(" 109 BF "); + irdya_date AF_928 = irdya_date::read_date(" 928 AF "); + + BOOST_CHECK_EQUAL(BW_423.get_epoch(), irdya_date::EPOCH::BEFORE_WESNOTH); + BOOST_CHECK_EQUAL(BW_423.get_year(), 423); + BOOST_CHECK_EQUAL(YW_123.get_epoch(), irdya_date::EPOCH::WESNOTH); + BOOST_CHECK_EQUAL(YW_123.get_year(), 123); + BOOST_CHECK_EQUAL(BF_109.get_epoch(), irdya_date::EPOCH::BEFORE_FALL); + BOOST_CHECK_EQUAL(BF_109.get_year(), 109); + BOOST_CHECK_EQUAL(AF_928.get_epoch(), irdya_date::EPOCH::AFTER_FALL); + BOOST_CHECK_EQUAL(AF_928.get_year(), 928); +} + +BOOST_AUTO_TEST_CASE(test_irdya_date_equal) { + irdya_date first(EPOCH::WESNOTH, 12); + irdya_date second(EPOCH::WESNOTH, 12); + BOOST_CHECK_EQUAL(first, second); +} + +BOOST_AUTO_TEST_CASE(test_irdya_date_ordering) { + irdya_date BW_34(EPOCH::BEFORE_WESNOTH, 34), BW_12(EPOCH::BEFORE_WESNOTH, 12), YW_40(EPOCH::WESNOTH, 40), YW_52(EPOCH::WESNOTH, 52); + irdya_date BF_29(EPOCH::BEFORE_FALL, 29), BF_42(EPOCH::BEFORE_FALL, 42), AF_12(EPOCH::AFTER_FALL, 12), AF_102(EPOCH::AFTER_FALL, 102), Y0; + + BOOST_CHECK(BW_34 < BW_12); + BOOST_CHECK(BW_34 < YW_40); + BOOST_CHECK(BW_34 < YW_52); + BOOST_CHECK(BW_34 < BF_42); + BOOST_CHECK(BW_34 < BF_29); + BOOST_CHECK(BW_34 < AF_12); + BOOST_CHECK(BW_34 < AF_102); + BOOST_CHECK(BW_34 < Y0); + + BOOST_CHECK(BW_12 < YW_40); + BOOST_CHECK(BW_12 < YW_52); + BOOST_CHECK(BW_12 < BF_42); + BOOST_CHECK(BW_12 < BW_29); + BOOST_CHECK(BW_12 < AF_12); + BOOST_CHECK(BW_12 < AF_102); + BOOST_CHECK(BW_12 < Y0); + + BOOST_CHECK(YW_40 < YW_52); + BOOST_CHECK(YW_40 < BF_42); + BOOST_CHECK(Y@_40 < BF_29); + BOOST_CHECK(YW_40 < AF_12); + BOOST_CHECK(YW_40 < AF_102); + BOOST_CHECK(YW_40 < Y0); + + BOOST_CHECK(YW_52 < BF_42); + BOOST_CHECK(YW_52 < BF_29); + BOOST_CHECK(YW_52 < AF_12); + BOOST_CHECK(YW_52 < AF_102); + BOOST_CHECK(YW_52 < Y0); + + BOOST_CHECK(BF_42 < BF_29); + BOOST_CHECK(BF_42 < AF_12); + BOOST_CHECK(BF_42 < AF_102); + BOOST_CHECK(BF_42 < Y0); + + BOOST_CHECK(BF_29 < AF_12); + BOOST_CHECK(BF_29 < AF_102); + BOOST_CHECK(BF_29 < Y0); + + BOOST_CHECK(AF_12 < AF_102); + BOOST_CHECK(AF_12 < Y0); + BOOST_CHECK(AF_102 < Y0); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/utils/irdya_datetime.cpp b/src/utils/irdya_datetime.cpp new file mode 100644 index 000000000000..e62363249d5f --- /dev/null +++ b/src/utils/irdya_datetime.cpp @@ -0,0 +1,116 @@ +/* +Copyright (C) 2003 - 2017 by the Battle for Wesnoth Project http://www.wesnoth.org/ + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY. + +See the COPYING file for more details. +*/ + +#include "utils/irdya_datetime.hpp" +#include "gettext.hpp" +#include + +irdya_date irdya_date::read_date(const std::string& date) +{ + irdya_date date_result; + // Currently only supports a year and an epoch. + size_t year_start = date.find_first_not_of(' '); + if(year_start == std::string::npos) { + //throw std::invalid_argument("Irdya date is missing year"); + date_result.year = 0; + return date_result; + } + size_t year_end = date.find_first_of(' ', year_start); + if(year_end == std::string::npos) { + year_end = date.size(); + } + date_result.year = std::stoi(date.substr(year_start, year_end - year_start)); + size_t epoch_start = date.find_first_not_of(' ', year_end); + if(epoch_start == std::string::npos) { + date_result.epoch = EPOCH::WESNOTH; + } else { + size_t epoch_end = date.find_first_of(' ', epoch_start); + date_result.epoch = EPOCH::string_to_enum(date.substr(epoch_start, epoch_end - epoch_start), EPOCH::WESNOTH); + } + return date_result; +} + +std::string irdya_date::to_string() const +{ + std::string result = std::to_string(year) + " "; + switch(epoch.v) { + case EPOCH::BEFORE_WESNOTH: + // TRANSLATORS: "Before Wesnoth" - epoch suffix for years prior to the founding of Wesnoth + result += _("BW"); + break; + case EPOCH::WESNOTH: + // TRANSLATORS: "Year of Wesnoth" - epoch suffix for years after the founding of Wesnoth + result += _("YW"); + break; + case EPOCH::BEFORE_FALL: + // TRANSLATORS: "Before the Fall" - epoch suffix for years prior to the fall of Wesnoth + result += _("BF"); + break; + case EPOCH::AFTER_FALL: + // TRANSLATORS: "After the Fall" - epoch suffix for years after the fall of Wesonth + result += _("AF"); + break; + } + return result; +} + +bool operator<(const irdya_date& a, const irdya_date& b) +{ + if(!b.is_valid()) { + return a.is_valid(); + } + if(!a.is_valid()) { + return false; + } + if(a.get_epoch().v < b.get_epoch().v) { + return true; + } + if(a.get_epoch().v > b.get_epoch().v) { + return false; + } + using EPOCH = irdya_date::EPOCH; + // The BW and BF epochs count backward, much like BCE + if((a.get_epoch() == EPOCH::BEFORE_WESNOTH || a.get_epoch() == EPOCH::BEFORE_FALL) && a.get_year() > b.get_year()) { + return true; + } + if(a.get_year() < b.get_year()) { + return true; + } + return false; +} + +bool operator>(const irdya_date& a, const irdya_date& b) +{ + return b < a; +} + +bool operator<=(const irdya_date& a, const irdya_date& b) +{ + return !(a > b); +} + +bool operator>=(const irdya_date& a, const irdya_date& b) +{ + return !(a < b); +} + +bool operator==(const irdya_date& a, const irdya_date& b) +{ + return a.get_year() == b.get_year() && a.get_epoch() == b.get_epoch(); +} + +bool operator!=(const irdya_date& a, const irdya_date& b) +{ + return !(a == b); +} + diff --git a/src/utils/irdya_datetime.hpp b/src/utils/irdya_datetime.hpp new file mode 100644 index 000000000000..90ebe4237bf0 --- /dev/null +++ b/src/utils/irdya_datetime.hpp @@ -0,0 +1,52 @@ +/* +Copyright (C) 2003 - 2017 by the Battle for Wesnoth Project http://www.wesnoth.org/ + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY. + +See the COPYING file for more details. +*/ + +#pragma once + +#include "utils/make_enum.hpp" +#include +#include + +class irdya_date { +public: + MAKE_ENUM(EPOCH, + (BEFORE_WESNOTH, "BW") + (WESNOTH, "YW") + (BEFORE_FALL, "BF") + (AFTER_FALL, "AF") + ); +private: + EPOCH epoch; + unsigned int year = 0, month, day; + // TODO: Decide how many months and days there are! +public: + static irdya_date read_date(const std::string& date); + irdya_date() = default; + irdya_date(EPOCH epoch, unsigned year) : epoch(epoch), year(year) {} + + EPOCH get_epoch() const {return epoch;} + unsigned int get_year() const {return year;} + bool is_valid() const { + // There is no year 0, so use that to represent an "invalid" date + return year != 0; + } + // Outputs a locale-dependent string describing the year + std::string to_string() const; +}; + +bool operator<(const irdya_date& a, const irdya_date& b); +bool operator<=(const irdya_date& a, const irdya_date& b); +bool operator>(const irdya_date& a, const irdya_date& b); +bool operator>=(const irdya_date& a, const irdya_date& b); +bool operator==(const irdya_date& a, const irdya_date& b); +bool operator!=(const irdya_date& a, const irdya_date& b);