From c38c79b12b60b4b52a9d275b72c3fc682acf8d08 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Sat, 18 Jan 2025 16:02:53 -0600 Subject: [PATCH 01/21] no default repeat spells & buf message when none stored Fix #535 --- src/game/boe.main.cpp | 2 +- src/game/boe.party.cpp | 37 +++++++++++++++++++++++++++++-------- src/universe/pc.hpp | 2 +- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/game/boe.main.cpp b/src/game/boe.main.cpp index 9145ae5c..d96bd930 100644 --- a/src/game/boe.main.cpp +++ b/src/game/boe.main.cpp @@ -147,7 +147,7 @@ eStatMode stat_screen_mode; short anim_step = -1; // Spell casting globals -eSpell store_mage = eSpell::LIGHT, store_priest = eSpell::BLESS_MINOR; +eSpell store_mage = eSpell::NONE, store_priest = eSpell::NONE; short store_mage_lev = 0, store_priest_lev = 0; short store_spell_target = 6,pc_casting; short num_targets_left = 0; diff --git a/src/game/boe.party.cpp b/src/game/boe.party.cpp index 5684fc8e..138aa48a 100644 --- a/src/game/boe.party.cpp +++ b/src/game/boe.party.cpp @@ -52,6 +52,11 @@ short combat_percent[20] = { 70,70,67,62,57,52,47,42,40,40}; short who_cast,which_pc_displayed; +// Light can be cast in or out of combat +const eSpell DEFAULT_SELECTED_MAGE = eSpell::LIGHT; +// Bless can only be cast in combat, so separate defaults are needed +const eSpell DEFAULT_SELECTED_PRIEST = eSpell::HEAL_MINOR; +const eSpell DEFAULT_SELECTED_PRIEST_COMBAT = eSpell::BLESS_MINOR; eSpell town_spell; extern bool spell_freebie; extern eSpecCtxType spec_target_type; @@ -490,6 +495,13 @@ bool repeat_cast_ok(eSkill type) { what_spell = univ.party[who_would_cast].last_cast[type]; else what_spell = type == eSkill::MAGE_SPELLS ? store_mage : store_priest; + if(what_spell == eSpell::NONE){ + std::ostringstream sout; + sout << "Repeat cast: No " << (type == eSkill::MAGE_SPELLS ? "mage" : "priest") << " spell stored."; + add_string_to_buf(sout.str()); + return false; + } + if(!pc_can_cast_spell(univ.party[who_would_cast],what_spell)) { add_string_to_buf("Repeat cast: Can't cast."); return false; @@ -1947,7 +1959,7 @@ static bool finish_pick_spell(cDialog& me, bool spell_toast, const short store_s //short situation; // 0 - out 1 - town 2 - combat eSpell pick_spell(short pc_num,eSkill type) { // 70 - no spell OW spell num using namespace std::placeholders; - eSpell store_spell = type == eSkill::MAGE_SPELLS ? store_mage : store_priest; + eSpell default_spell = type == eSkill::MAGE_SPELLS ? store_mage : store_priest; short former_target = store_spell_target; short dark = 6; @@ -1995,29 +2007,38 @@ eSpell pick_spell(short pc_num,eSkill type) { // 70 - no spell OW spell num // If in combat, make the spell being cast this PCs most recent spell if(is_combat()) { if(type == eSkill::MAGE_SPELLS) - store_spell = univ.party[pc_casting].last_cast[eSkill::MAGE_SPELLS]; - else store_spell = univ.party[pc_casting].last_cast[eSkill::PRIEST_SPELLS]; + default_spell = univ.party[pc_casting].last_cast[eSkill::MAGE_SPELLS]; + else{ + default_spell = univ.party[pc_casting].last_cast[eSkill::PRIEST_SPELLS]; + if(default_spell == eSpell::NONE){ + default_spell = DEFAULT_SELECTED_PRIEST_COMBAT; + } + } } + if(default_spell == eSpell::NONE){ + default_spell = type == eSkill::MAGE_SPELLS ? DEFAULT_SELECTED_MAGE : DEFAULT_SELECTED_PRIEST; + } + // Keep the stored spell, if it's still castable - if(!pc_can_cast_spell(univ.party[pc_casting],store_spell)) { + if(!pc_can_cast_spell(univ.party[pc_casting],default_spell)) { if(type == eSkill::MAGE_SPELLS) { - store_spell = eSpell::LIGHT; + default_spell = DEFAULT_SELECTED_MAGE; } else { - store_spell = eSpell::HEAL_MINOR; + default_spell = DEFAULT_SELECTED_PRIEST; } } // If a target is needed, keep the same target if that PC still targetable if(store_spell_target < 6) { - if((*store_spell).need_select != SELECT_NO) { + if((*default_spell).need_select != SELECT_NO) { if(univ.party[store_spell_target].main_status != eMainStatus::ALIVE) store_spell_target = 6; } else store_spell_target = 6; } - short former_spell = int(store_spell) % 100; + short former_spell = int(default_spell) % 100; // Set the spell page, based on starting spell if(former_spell >= 38) on_which_spell_page = 1; else on_which_spell_page = 0; diff --git a/src/universe/pc.hpp b/src/universe/pc.hpp index cd6b69c2..037536b1 100644 --- a/src/universe/pc.hpp +++ b/src/universe/pc.hpp @@ -105,7 +105,7 @@ public: eRace race; long unique_id; // transient stuff - std::map last_cast; + std::map last_cast = {{ eSkill::MAGE_SPELLS, eSpell::NONE}, { eSkill::PRIEST_SPELLS, eSpell::NONE }}; location combat_pos; short parry = 0; iLiving* last_attacked = nullptr; // Note: Currently this is assigned but never read From 0ef3ad81d99198caa93b6455fa4662f39ff90070 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Sat, 18 Jan 2025 16:42:16 -0600 Subject: [PATCH 02/21] Recast hint for spellcasters --- src/game/boe.actions.cpp | 1 + src/game/boe.graphics.cpp | 40 ++++++++++++++++++++++++++++++++++++--- src/game/boe.graphics.hpp | 2 +- src/game/boe.party.cpp | 3 +++ src/gfx/render_text.cpp | 2 +- src/universe/pc.hpp | 3 +++ 6 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/game/boe.actions.cpp b/src/game/boe.actions.cpp index 85b1b4c7..46ce13d8 100644 --- a/src/game/boe.actions.cpp +++ b/src/game/boe.actions.cpp @@ -1850,6 +1850,7 @@ void handle_menu_spell(eSpell spell_picked) { spell_forced = true; pc_casting = univ.cur_pc; univ.current_pc().last_cast[spell_type] = spell_picked; + univ.current_pc().last_cast_type = spell_type; if(spell_type == eSkill::MAGE_SPELLS) store_mage = spell_picked; else store_priest = spell_picked; diff --git a/src/game/boe.graphics.cpp b/src/game/boe.graphics.cpp index 17c7f121..77f8ea14 100644 --- a/src/game/boe.graphics.cpp +++ b/src/game/boe.graphics.cpp @@ -611,8 +611,38 @@ void draw_text_bar() { } if((is_combat()) && (univ.cur_pc < 6) && !monsters_going) { std::ostringstream sout; - sout << univ.current_pc().name << " (ap: " << univ.current_pc().ap << ')'; - put_text_bar(sout.str()); + + cPlayer& current_pc = univ.current_pc(); + sout << current_pc.name << " (ap: " << current_pc.ap << ')'; + + // Spellcasters print a hint for recasting. + // There's not enough space to print 2 hints for dual-casters, + // so just handle the last type cast. + eSkill type = current_pc.last_cast_type; + std::string hint_prefix = ""; + std::ostringstream hint_out; + switch(type){ + case eSkill::MAGE_SPELLS: + hint_prefix = "M"; + break; + case eSkill::PRIEST_SPELLS: + hint_prefix = "P"; + break; + // The only other expected value is eSkill::INVALID + default: + break; + } + if(!hint_prefix.empty()){ + hint_out << hint_prefix << ": "; + const cSpell& spell = (*current_pc.last_cast[type]); + if(pc_can_cast_spell(current_pc,type) && spell.cost <= current_pc.get_magic()) { + hint_out << "Recast " << spell.name(); + }else{ + hint_out << "Cannot recast"; + } + } + + put_text_bar(sout.str(), hint_out.str()); } if((is_combat()) && (monsters_going)) // Print bar for 1st monster with >0 ap - that is monster that is going @@ -623,7 +653,7 @@ void draw_text_bar() { } } -void put_text_bar(std::string str) { +void put_text_bar(std::string str, std::string right_str) { text_bar_gworld.setActive(false); auto& bar_gw = *ResMgr::graphics.get("textbar"); rect_draw_some_item(bar_gw, rectangle(bar_gw), text_bar_gworld, rectangle(bar_gw)); @@ -635,7 +665,11 @@ void put_text_bar(std::string str) { rectangle to_rect = rectangle(text_bar_gworld); to_rect.top += 7; to_rect.left += 5; + to_rect.right -= 5; win_draw_string(text_bar_gworld, to_rect, str, eTextMode::LEFT_TOP, style); + // Style has to be wrap to get right-alignment + win_draw_string(text_bar_gworld, to_rect, right_str, eTextMode::WRAP, style, true); + to_rect.right -= string_length(right_str, style); if(!monsters_going) { sf::Texture& status_gworld = *ResMgr::graphics.get("staticons"); diff --git a/src/game/boe.graphics.hpp b/src/game/boe.graphics.hpp index be72abf7..c1966d6f 100644 --- a/src/game/boe.graphics.hpp +++ b/src/game/boe.graphics.hpp @@ -31,7 +31,7 @@ void redraw_screen(int refresh); void put_background(); void draw_text_bar(); void refresh_text_bar(); -void put_text_bar(std::string str); +void put_text_bar(std::string str, std::string right_str = ""); void draw_terrain(short mode = 0); void place_trim(short q,short r,location where,ter_num_t ter_type); void draw_trim(short q,short r,short which_trim,short which_mode); diff --git a/src/game/boe.party.cpp b/src/game/boe.party.cpp index 138aa48a..766dbd6c 100644 --- a/src/game/boe.party.cpp +++ b/src/game/boe.party.cpp @@ -1926,6 +1926,7 @@ static bool finish_pick_spell(cDialog& me, bool spell_toast, const short store_s if(store_situation == eSkill::MAGE_SPELLS && (*picked_spell).need_select == SELECT_NO) { store_last_cast_mage = pc_casting; univ.party[pc_casting].last_cast[store_situation] = picked_spell; + univ.party[pc_casting].last_cast_type = store_situation; me.toast(false); me.setResult(store_spell); return true; @@ -1933,6 +1934,7 @@ static bool finish_pick_spell(cDialog& me, bool spell_toast, const short store_s if(store_situation == eSkill::PRIEST_SPELLS && (*picked_spell).need_select == SELECT_NO) { store_last_cast_priest = pc_casting; univ.party[pc_casting].last_cast[store_situation] = picked_spell; + univ.party[pc_casting].last_cast_type = store_situation; me.toast(false); me.setResult(store_spell); return true; @@ -1950,6 +1952,7 @@ static bool finish_pick_spell(cDialog& me, bool spell_toast, const short store_s store_last_cast_mage = pc_casting; else store_last_cast_priest = pc_casting; univ.party[pc_casting].last_cast[store_situation] = picked_spell; + univ.party[pc_casting].last_cast_type = store_situation; me.toast(true); return true; } diff --git a/src/gfx/render_text.cpp b/src/gfx/render_text.cpp index e7a6d22c..e0e55a49 100644 --- a/src/gfx/render_text.cpp +++ b/src/gfx/render_text.cpp @@ -149,7 +149,7 @@ static void win_draw_string(sf::RenderTarget& dest_window,rectangle dest_rect,st short total_width = str_to_draw.getLocalBounds().width; eTextMode mode = options.mode; - if(mode == eTextMode::WRAP && total_width < dest_rect.width()) + if(mode == eTextMode::WRAP && total_width < dest_rect.width() && !options.right_align) mode = eTextMode::LEFT_TOP; if(mode == eTextMode::LEFT_TOP && str.find('|') != std::string::npos) mode = eTextMode::WRAP; diff --git a/src/universe/pc.hpp b/src/universe/pc.hpp index 037536b1..0235fba7 100644 --- a/src/universe/pc.hpp +++ b/src/universe/pc.hpp @@ -106,6 +106,9 @@ public: long unique_id; // transient stuff std::map last_cast = {{ eSkill::MAGE_SPELLS, eSpell::NONE}, { eSkill::PRIEST_SPELLS, eSpell::NONE }}; + // There is already a global last_spellcast_type, but that variable is for the whole party. + // This one is per-PC + eSkill last_cast_type = eSkill::INVALID; location combat_pos; short parry = 0; iLiving* last_attacked = nullptr; // Note: Currently this is assigned but never read From c7ce84def227d0bfc0abeecf4ad41cfc49c31694 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Sat, 18 Jan 2025 17:38:29 -0600 Subject: [PATCH 03/21] More intuitive coloration in spell menu --- src/game/boe.party.cpp | 13 ++++++++++--- src/gfx/render_shapes.hpp | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/game/boe.party.cpp b/src/game/boe.party.cpp index 766dbd6c..913ba4fd 100644 --- a/src/game/boe.party.cpp +++ b/src/game/boe.party.cpp @@ -99,6 +99,9 @@ short spell_index[38] = {38,39,40,41,42,43,44,45,90,90,46,47,48,49,50,51,52,53,9 // Says which buttons hit which spells on second spell page, 90 means no button bool can_choose_caster; +const sf::Color SELECTED_COLOUR = Colours::LIGHT_GREEN; +const sf::Color DISABLED_COLOUR = Colours::GREY; + // Dialog vars short store_graphic_pc_num ; short store_graphic_mode ; @@ -1718,7 +1721,7 @@ static void put_pc_caster_buttons(cDialog& me) { std::string n = boost::lexical_cast(i + 1); if(me["caster" + n].isVisible()) { if(i == pc_casting) - me["pc" + n].setColour(Colours::RED); + me["pc" + n].setColour(SELECTED_COLOUR); else me["pc" + n].setColour(me.getDefTextClr()); } } @@ -1728,8 +1731,8 @@ static void put_pc_target_buttons(cDialog& me, short& store_last_target_darkened if(store_spell_target < 6) { std::string n = boost::lexical_cast(store_spell_target + 1); - me["hp" + n].setColour(Colours::RED); - me["sp" + n].setColour(Colours::RED); + me["hp" + n].setColour(SELECTED_COLOUR); + me["sp" + n].setColour(SELECTED_COLOUR); } if((store_last_target_darkened < 6) && (store_last_target_darkened != store_spell_target)) { std::string n = boost::lexical_cast(store_last_target_darkened + 1); @@ -1752,12 +1755,16 @@ static void put_spell_led_buttons(cDialog& me, const eSkill store_situation,cons eSpell spell = cSpell::fromNum(store_situation, spell_for_this_button); if(store_spell == spell_for_this_button) { led.setState(led_green); + // Text color: + led.setColour(SELECTED_COLOUR); } else if(pc_can_cast_spell(univ.party[pc_casting],spell)) { led.setState(led_red); + led.setColour(me.getDefTextClr()); } else { led.setState(led_off); + led.setColour(DISABLED_COLOUR); } } } diff --git a/src/gfx/render_shapes.hpp b/src/gfx/render_shapes.hpp index 20ca52a3..e043574b 100644 --- a/src/gfx/render_shapes.hpp +++ b/src/gfx/render_shapes.hpp @@ -48,6 +48,7 @@ void undo_clip(sf::RenderTarget& where); namespace Colours { const sf::Color WHITE = sf::Color::White; const sf::Color BLACK = sf::Color::Black; + const sf::Color GREY { 0x80, 0x80, 0x80}; const sf::Color RED { 0xdd, 0x00, 0x00}; const sf::Color GREEN { 0x00, 0x88, 0x00}; const sf::Color BLUE { 0x00, 0x00, 0xdd}; From f58d95496d3a9b0471537f73fa2a545c3b6bd538 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Sat, 18 Jan 2025 17:57:14 -0600 Subject: [PATCH 04/21] fix unit test for last_cast change --- test/init.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/init.cpp b/test/init.cpp index d35e2733..48cbda46 100644 --- a/test/init.cpp +++ b/test/init.cpp @@ -258,7 +258,7 @@ TEST_CASE("Construction sanity test for player character") { CHECK(pc.traits.empty()); CHECK(pc.race == eRace::HUMAN); // Skip unique_id since it's non-deterministic - CHECK(pc.last_cast.empty()); + CHECK(pc.last_cast.size() == 2); CHECK(pc.combat_pos == loc(-1,-1)); CHECK(pc.parry == 0); CHECK(pc.last_attacked == nullptr); From daecca4abe0bd0a73e16c39eedfc9f0f2bd0c4ba Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Mon, 20 Jan 2025 09:56:24 -0600 Subject: [PATCH 05/21] dialogxml parseColor use a map built from Colours constants parseColor() was doing a big if-else with string comparisons, and returning values that differed from our Colours constants. Since I couldn't find an instance of the colour attribute in our existing xml, it should be safe to do away with those conflicting values and refactor parseColor() to match the constants and use cleaner code. --- src/dialogxml/dialogs/dialog.cpp | 39 +++++--------------------------- src/gfx/render_shapes.cpp | 21 +++++++++++++++++ src/gfx/render_shapes.hpp | 1 + 3 files changed, 28 insertions(+), 33 deletions(-) diff --git a/src/dialogxml/dialogs/dialog.cpp b/src/dialogxml/dialogs/dialog.cpp index 755ccf3f..1979c327 100644 --- a/src/dialogxml/dialogs/dialog.cpp +++ b/src/dialogxml/dialogs/dialog.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include "dialog.hpp" #include "gfx/tiling.hpp" // for bg #include "fileio/resmgr/res_dialog.hpp" @@ -45,6 +46,8 @@ cDialog* cDialog::topWindow = nullptr; void (*cDialog::redraw_everything)() = nullptr; std::mt19937 cDialog::ui_rand; +extern std::map colour_map; + extern bool check_for_interrupt(std::string); std::string cDialog::generateRandomString(){ @@ -86,39 +89,9 @@ sf::Color cControl::parseColor(string what){ } } clr.r = r, clr.g = g, clr.b = b; - }else if(what == "black") - clr.r = 0x00, clr.g = 0x00, clr.b = 0x00; - else if(what == "red") - clr.r = 0xFF, clr.g = 0x00, clr.b = 0x00; - else if(what == "lime") - clr.r = 0x00, clr.g = 0xFF, clr.b = 0x00; - else if(what == "blue") - clr.r = 0x00, clr.g = 0x00, clr.b = 0xFF; - else if(what == "yellow") - clr.r = 0xFF, clr.g = 0xFF, clr.b = 0x00; - else if(what == "aqua") - clr.r = 0x00, clr.g = 0xFF, clr.b = 0xFF; - else if(what == "fuchsia") - clr.r = 0xFF, clr.g = 0x00, clr.b = 0xFF; - else if(what == "white") - clr.r = 0xFF, clr.g = 0xFF, clr.b = 0xFF; - else if(what == "gray" || what == "grey") - clr.r = 0x80, clr.g = 0x80, clr.b = 0x80; - else if(what == "maroon") - clr.r = 0x80, clr.g = 0x00, clr.b = 0x00; - else if(what == "green") - clr.r = 0x00, clr.g = 0x80, clr.b = 0x00; - else if(what == "navy") - clr.r = 0x00, clr.g = 0x00, clr.b = 0x80; - else if(what == "olive") - clr.r = 0x80, clr.g = 0x80, clr.b = 0x00; - else if(what == "teal") - clr.r = 0x00, clr.g = 0x80, clr.b = 0x80; - else if(what == "purple") - clr.r = 0x80, clr.g = 0x00, clr.b = 0x80; - else if(what == "silver") - clr.r = 0xC0, clr.g = 0xC0, clr.b = 0xC0; - else throw -1; + }else if(colour_map.find(what) != colour_map.end()){ + return colour_map[what]; + }else throw -1; return clr; } diff --git a/src/gfx/render_shapes.cpp b/src/gfx/render_shapes.cpp index e284547b..47c41360 100644 --- a/src/gfx/render_shapes.cpp +++ b/src/gfx/render_shapes.cpp @@ -17,6 +17,27 @@ using boost::math::constants::pi; using pt_idx_t = decltype(((sf::Shape*)nullptr)->getPointCount()); +std::map colour_map = { + {"white", Colours::WHITE}, + {"black", Colours::BLACK}, + {"grey", Colours::GREY}, + {"gray", Colours::GREY}, + {"red", Colours::RED}, + {"green", Colours::GREEN}, + {"blue", Colours::BLUE}, + {"teal", Colours::TEAL}, + {"pink", Colours::PINK}, + {"yellow", Colours::YELLOW}, + {"orange", Colours::ORANGE}, + {"shadow", Colours::SHADOW}, + {"title_blue", Colours::TITLE_BLUE}, + {"navy", Colours::NAVY}, + {"dark_blue", Colours::DARK_BLUE}, + {"dark_green", Colours::DARK_GREEN}, + {"light_green", Colours::LIGHT_GREEN}, + {"dark_red", Colours::DARK_RED} +}; + // TODO: Put these classes in a header? class EllipseShape : public sf::Shape { float divSz; diff --git a/src/gfx/render_shapes.hpp b/src/gfx/render_shapes.hpp index e043574b..90e98911 100644 --- a/src/gfx/render_shapes.hpp +++ b/src/gfx/render_shapes.hpp @@ -11,6 +11,7 @@ #include #include +#include #include #include "location.hpp" From 4260d41bf64ed75ba5f422efb792be36ad1968ed Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Mon, 20 Jan 2025 10:17:01 -0600 Subject: [PATCH 06/21] colorize hp and sp in casting menu --- rsrc/dialogs/cast-spell.xml | 24 ++++++++++++------------ src/gfx/render_shapes.cpp | 1 + src/gfx/render_shapes.hpp | 1 + 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/rsrc/dialogs/cast-spell.xml b/rsrc/dialogs/cast-spell.xml index 44810467..8eedb04e 100644 --- a/rsrc/dialogs/cast-spell.xml +++ b/rsrc/dialogs/cast-spell.xml @@ -23,18 +23,18 @@ - - - - - - - - - - - - + + + + + + + + + + + + Pick spell to cast. diff --git a/src/gfx/render_shapes.cpp b/src/gfx/render_shapes.cpp index 47c41360..75e3132a 100644 --- a/src/gfx/render_shapes.cpp +++ b/src/gfx/render_shapes.cpp @@ -29,6 +29,7 @@ std::map colour_map = { {"pink", Colours::PINK}, {"yellow", Colours::YELLOW}, {"orange", Colours::ORANGE}, + {"light_blue", Colours::LIGHT_BLUE}, {"shadow", Colours::SHADOW}, {"title_blue", Colours::TITLE_BLUE}, {"navy", Colours::NAVY}, diff --git a/src/gfx/render_shapes.hpp b/src/gfx/render_shapes.hpp index 90e98911..e11f9ba4 100644 --- a/src/gfx/render_shapes.hpp +++ b/src/gfx/render_shapes.hpp @@ -57,6 +57,7 @@ namespace Colours { const sf::Color PINK { 0xff, 0x00, 0x99}; const sf::Color YELLOW { 0xff, 0xff, 0x31}; const sf::Color ORANGE { 0xff, 0x80, 0x00}; + const sf::Color LIGHT_BLUE { 0xad, 0xd8, 0xe6 }; // Spell points on dark background // Text colours for shopping / talking // TODO: The Windows version appears to use completely different colours? const sf::Color SHADOW { 0x00, 0x00, 0x68}; // formerly c[3] QD colour = {0,0,26623} (shop/character name shadow, shop subtitle) From 4c383d116cb673a0e3fa2301b179c4f9e147621a Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Mon, 20 Jan 2025 12:13:48 -0600 Subject: [PATCH 07/21] re-arrange and align HP/SP --- rsrc/dialogs/cast-spell.xml | 89 +++++++++++++++++++++++-------------- src/game/boe.party.cpp | 7 ++- 2 files changed, 58 insertions(+), 38 deletions(-) diff --git a/rsrc/dialogs/cast-spell.xml b/rsrc/dialogs/cast-spell.xml index 8eedb04e..d4bb6dee 100644 --- a/rsrc/dialogs/cast-spell.xml +++ b/rsrc/dialogs/cast-spell.xml @@ -3,38 +3,57 @@ Select a Spell: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + Caster: + + + + + + + + + + + + + + + + Target: + + + + + + + + -> + -> + -> + -> + -> + -> + + HP: + + + + + + + + SP: + + + + + + + + Status: + + Pick spell to cast. @@ -75,8 +94,6 @@ - Caster: - Target: HP: SP: Status: Keyboard: @@ -148,4 +165,8 @@ + + + + diff --git a/src/game/boe.party.cpp b/src/game/boe.party.cpp index 913ba4fd..f995933e 100644 --- a/src/game/boe.party.cpp +++ b/src/game/boe.party.cpp @@ -1706,6 +1706,7 @@ static void draw_spell_pc_info(cDialog& me) { if(univ.party[i].main_status != eMainStatus::ABSENT) { me["pc" + n].setText(univ.party[i].name); + me["arrow" + n].hide(); if(univ.party[i].main_status == eMainStatus::ALIVE) { me["hp" + n].setTextToNum(univ.party[i].cur_health); me["sp" + n].setTextToNum(univ.party[i].cur_sp); @@ -1731,13 +1732,11 @@ static void put_pc_target_buttons(cDialog& me, short& store_last_target_darkened if(store_spell_target < 6) { std::string n = boost::lexical_cast(store_spell_target + 1); - me["hp" + n].setColour(SELECTED_COLOUR); - me["sp" + n].setColour(SELECTED_COLOUR); + me["arrow" + n].show(); } if((store_last_target_darkened < 6) && (store_last_target_darkened != store_spell_target)) { std::string n = boost::lexical_cast(store_last_target_darkened + 1); - me["hp" + n].setColour(me.getDefTextClr()); - me["sp" + n].setColour(me.getDefTextClr()); + me["arrow" + n].hide(); } store_last_target_darkened = store_spell_target; } From 0051274d2659376a4adcafa926563b08b08db877 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Mon, 20 Jan 2025 12:34:49 -0600 Subject: [PATCH 08/21] make spell columns relative-position --- rsrc/dialogs/cast-spell.xml | 207 +++++++++++++++++++----------------- 1 file changed, 107 insertions(+), 100 deletions(-) diff --git a/rsrc/dialogs/cast-spell.xml b/rsrc/dialogs/cast-spell.xml index d4bb6dee..1fe83ff1 100644 --- a/rsrc/dialogs/cast-spell.xml +++ b/rsrc/dialogs/cast-spell.xml @@ -53,47 +53,111 @@ Status: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - Pick spell to cast. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + Keyboard: @@ -106,67 +170,10 @@ key by spell to cast spell.
Alt-click spell name for description.
+ Pick spell to cast. - + + + From 8e4cb8fc658815a1fadb09a0c19169e3ad83aaf9 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Mon, 20 Jan 2025 13:26:26 -0600 Subject: [PATCH 09/21] relative positioning for PC stat icons --- rsrc/dialogs/cast-spell.xml | 149 ++++++++++++++++++++++++------------ 1 file changed, 100 insertions(+), 49 deletions(-) diff --git a/rsrc/dialogs/cast-spell.xml b/rsrc/dialogs/cast-spell.xml index 1fe83ff1..46dcbd2f 100644 --- a/rsrc/dialogs/cast-spell.xml +++ b/rsrc/dialogs/cast-spell.xml @@ -51,62 +51,113 @@ + Status: - - - - - - - - - + + + + + + - - - - - - - - + + + + + + - - - - - - - - + + + + + + - - - - - - - - + + + + + + - - - - - - - - + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From e2104a43901f8e7427d407979bf155be15c63234 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Mon, 20 Jan 2025 13:36:38 -0600 Subject: [PATCH 10/21] exclude split party members from target list --- src/game/boe.party.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/game/boe.party.cpp b/src/game/boe.party.cpp index f995933e..19943171 100644 --- a/src/game/boe.party.cpp +++ b/src/game/boe.party.cpp @@ -1655,8 +1655,8 @@ static void draw_spell_info(cDialog& me, const eSkill store_situation, const sho } break; case SELECT_ANY: - // TODO: Split off party members should probably be excluded too? - if(univ.party[i].main_status != eMainStatus::ABSENT) { + // Absent party members and split-off party members are excluded + if(univ.party[i].main_status != eMainStatus::ABSENT && univ.party[i].main_status < eMainStatus::SPLIT) { me[id].show(); } else { From cbb964a0736c1ac575e9e16cee6f61a147ab3397 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Mon, 20 Jan 2025 14:01:13 -0600 Subject: [PATCH 11/21] recast hint take the place of party status icons --- src/game/boe.graphics.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/game/boe.graphics.cpp b/src/game/boe.graphics.cpp index 77f8ea14..9fe6eb4f 100644 --- a/src/game/boe.graphics.cpp +++ b/src/game/boe.graphics.cpp @@ -667,11 +667,11 @@ void put_text_bar(std::string str, std::string right_str) { to_rect.left += 5; to_rect.right -= 5; win_draw_string(text_bar_gworld, to_rect, str, eTextMode::LEFT_TOP, style); - // Style has to be wrap to get right-alignment - win_draw_string(text_bar_gworld, to_rect, right_str, eTextMode::WRAP, style, true); - to_rect.right -= string_length(right_str, style); - - if(!monsters_going) { + // the recast hint will replace status icons: + if(!right_str.empty()){ + // Style has to be wrap to get right-alignment + win_draw_string(text_bar_gworld, to_rect, right_str, eTextMode::WRAP, style, true); + }else if(!monsters_going) { sf::Texture& status_gworld = *ResMgr::graphics.get("staticons"); to_rect.top -= 2; to_rect.left = to_rect.right - 15; From 7742373142c7418bbce65346a7632ef1dffdafa7 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Mon, 20 Jan 2025 14:42:40 -0600 Subject: [PATCH 12/21] Try recast opposite spell type: Toggle the hint --- src/game/boe.actions.cpp | 8 ++++++++ src/game/boe.graphics.cpp | 12 ++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/game/boe.actions.cpp b/src/game/boe.actions.cpp index 46ce13d8..a6973b68 100644 --- a/src/game/boe.actions.cpp +++ b/src/game/boe.actions.cpp @@ -266,6 +266,14 @@ void handle_spellcast(eSkill which_type, bool& did_something, bool& need_redraw, short store_sp[6]; extern short spec_target_fail; extern eSpecCtxType spec_target_type; + // Dual-caster recast hint toggle: + // Change the recast hint to mage if last spell wasn't mage + if(spell_forced && is_combat() && univ.current_pc().last_cast_type != which_type){ + spell_forced = false; + univ.current_pc().last_cast_type = which_type; + need_redraw = true; + return; + } if(!someone_awake()) { ASB("Everyone's asleep/paralyzed."); need_reprint = true; diff --git a/src/game/boe.graphics.cpp b/src/game/boe.graphics.cpp index 9fe6eb4f..a0f2b9e5 100644 --- a/src/game/boe.graphics.cpp +++ b/src/game/boe.graphics.cpp @@ -634,11 +634,15 @@ void draw_text_bar() { } if(!hint_prefix.empty()){ hint_out << hint_prefix << ": "; - const cSpell& spell = (*current_pc.last_cast[type]); - if(pc_can_cast_spell(current_pc,type) && spell.cost <= current_pc.get_magic()) { - hint_out << "Recast " << spell.name(); + if(current_pc.last_cast[type] != eSpell::NONE){ + const cSpell& spell = (*current_pc.last_cast[type]); + if(pc_can_cast_spell(current_pc,type) && spell.cost <= current_pc.get_magic()) { + hint_out << "Recast " << spell.name(); + }else{ + hint_out << "Cannot recast"; + } }else{ - hint_out << "Cannot recast"; + hint_out << "No spell to recast"; } } From fc30ea4619d20e847f4e34c751db67765509b6e4 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Mon, 20 Jan 2025 14:53:44 -0600 Subject: [PATCH 13/21] advance_time after space to end fancy target. Fix #539 --- src/game/boe.actions.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/game/boe.actions.cpp b/src/game/boe.actions.cpp index a6973b68..e08d01c8 100644 --- a/src/game/boe.actions.cpp +++ b/src/game/boe.actions.cpp @@ -2457,6 +2457,7 @@ bool handle_keystroke(const sf::Event& event, cFramerateLimiter& fps_limiter){ // cast multi-target spell, set # targets to 0 so that space clicked doesn't matter num_targets_left = 0; handle_target_space(center, did_something, need_redraw, need_reprint); + advance_time(did_something, need_redraw, need_reprint); } else if(overall_mode == MODE_SPELL_TARGET) // Rotate a force wall spell_cast_hit_return(); From 92b798c48ab88cb5044d25b8c074c68e46f8f6df Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Tue, 21 Jan 2025 12:11:26 -0600 Subject: [PATCH 14/21] simplify if-else with identical branches --- src/game/boe.party.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/game/boe.party.cpp b/src/game/boe.party.cpp index 19943171..6aa27207 100644 --- a/src/game/boe.party.cpp +++ b/src/game/boe.party.cpp @@ -1880,9 +1880,7 @@ static bool pick_spell_select_led(cDialog& me, std::string id, eKeyMod mods, con me["feedback"].setText(bad_spell); } else { - if(store_situation == eSkill::MAGE_SPELLS) - store_spell = (on_which_spell_page == 0) ? item_hit : spell_index[item_hit]; - else store_spell = (on_which_spell_page == 0) ? item_hit : spell_index[item_hit]; + store_spell = (on_which_spell_page == 0) ? item_hit : spell_index[item_hit]; draw_spell_info(me, store_situation, store_spell); put_spell_led_buttons(me, store_situation, store_spell); From 8703c772d482718b3ec0a7d32e041c2082d99da7 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Tue, 21 Jan 2025 12:23:48 -0600 Subject: [PATCH 15/21] fix bug which made several spell leds broken on page 1 --- src/game/boe.party.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/game/boe.party.cpp b/src/game/boe.party.cpp index 6aa27207..a809d4af 100644 --- a/src/game/boe.party.cpp +++ b/src/game/boe.party.cpp @@ -2070,9 +2070,7 @@ eSpell pick_spell(short pc_num,eSkill type) { // 70 - no spell OW spell num cLed& led = dynamic_cast(castSpell[id]); led.attachKey(key); castSpell.addLabelFor(id, {static_cast(i > 25 ? toupper(key.c) : key.c)}, LABEL_LEFT, 8, true); - if(spell_index[i] == 90){ - continue; - } + // All LEDs should get the click handler and set state, because page 2 will hide them if necessary led.setState((pc_can_cast_spell(univ.party[pc_casting],cSpell::fromNum(type,on_which_spell_page == 0 ? i : spell_index[i]))) ? led_red : led_green); led.attachClickHandler(std::bind(pick_spell_select_led, _1, _2, _3, type, std::ref(dark), std::ref(former_spell))); From e31403087973b0eb429cb49d501f86932c410fe1 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Tue, 21 Jan 2025 12:37:26 -0600 Subject: [PATCH 16/21] clarify asPeaceful() function for spells I found it very confusing how many of the spells were (WHEN_COMBAT).asPeaceful(). I thought those were bugs at first. --- src/spell.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/spell.cpp b/src/spell.cpp index 30c9311c..e000949c 100644 --- a/src/spell.cpp +++ b/src/spell.cpp @@ -47,6 +47,7 @@ cSpell& cSpell::needsSelect(eSpellSelect sel) { return *this; } +// Mark a spell as castable by pacifist PCs cSpell& cSpell::asPeaceful() { peaceful = true; return *this; From b14a238ee3374a5c31db4df5e2075171aac69794 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Tue, 21 Jan 2025 12:40:19 -0600 Subject: [PATCH 17/21] Don't allow Major Blessing in town mode. Fix #541 It seemed reasonable that we actually would want to LET the player cast this in town mode, but it seems like other blessing spells (i.e. Haste) do not --- src/spell.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spell.cpp b/src/spell.cpp index e000949c..f43b04cb 100644 --- a/src/spell.cpp +++ b/src/spell.cpp @@ -212,7 +212,7 @@ cSpell M_FLIGHT = cSpell(eSpell::FLIGHT).asType(eSkill::MAGE_SPELLS).asLevel(6) cSpell M_SHOCKWAVE = cSpell(eSpell::SHOCKWAVE).asType(eSkill::MAGE_SPELLS).asLevel(7) .withCost(12).withRefer(REFER_IMMED).when(WHEN_COMBAT).finish(); cSpell M_BLESS_MAJOR = cSpell(eSpell::BLESS_MAJOR).asType(eSkill::MAGE_SPELLS).asLevel(7) - .withCost(8).withRefer(REFER_IMMED).when(WHEN_COMBAT).when(WHEN_TOWN).asPeaceful().finish(); + .withCost(8).withRefer(REFER_IMMED).when(WHEN_COMBAT).asPeaceful().finish(); cSpell M_PARALYSIS_MASS = cSpell(eSpell::PARALYSIS_MASS).asType(eSkill::MAGE_SPELLS).asLevel(7) .withRange(8).withCost(20).withRefer(REFER_IMMED).when(WHEN_COMBAT).finish(); cSpell M_PROTECTION = cSpell(eSpell::PROTECTION).asType(eSkill::MAGE_SPELLS).asLevel(7) From 3de687062819c66c70e7567bd12e4eee15d85c2e Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Tue, 21 Jan 2025 19:15:38 -0600 Subject: [PATCH 18/21] parseColor use - instead of _ --- rsrc/dialogs/cast-spell.xml | 24 ++++++++++++------------ src/gfx/render_shapes.cpp | 10 +++++----- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/rsrc/dialogs/cast-spell.xml b/rsrc/dialogs/cast-spell.xml index 46dcbd2f..c3cf3b7c 100644 --- a/rsrc/dialogs/cast-spell.xml +++ b/rsrc/dialogs/cast-spell.xml @@ -28,12 +28,12 @@ - -> - -> - -> - -> - -> - -> + -> + -> + -> + -> + -> + -> HP: @@ -44,12 +44,12 @@ SP: - - - - - - + + + + + + Status: diff --git a/src/gfx/render_shapes.cpp b/src/gfx/render_shapes.cpp index 75e3132a..7b231dae 100644 --- a/src/gfx/render_shapes.cpp +++ b/src/gfx/render_shapes.cpp @@ -29,14 +29,14 @@ std::map colour_map = { {"pink", Colours::PINK}, {"yellow", Colours::YELLOW}, {"orange", Colours::ORANGE}, - {"light_blue", Colours::LIGHT_BLUE}, + {"light-blue", Colours::LIGHT_BLUE}, {"shadow", Colours::SHADOW}, - {"title_blue", Colours::TITLE_BLUE}, + {"title-blue", Colours::TITLE_BLUE}, {"navy", Colours::NAVY}, {"dark_blue", Colours::DARK_BLUE}, - {"dark_green", Colours::DARK_GREEN}, - {"light_green", Colours::LIGHT_GREEN}, - {"dark_red", Colours::DARK_RED} + {"dark-green", Colours::DARK_GREEN}, + {"light-green", Colours::LIGHT_GREEN}, + {"dark-red", Colours::DARK_RED} }; // TODO: Put these classes in a header? From 1fb1aa2ce36403feeb371404d599e4d0d9666181 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Tue, 21 Jan 2025 19:33:49 -0600 Subject: [PATCH 19/21] add back the extra colours --- src/gfx/render_shapes.cpp | 10 +++++++++- src/gfx/render_shapes.hpp | 8 ++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/gfx/render_shapes.cpp b/src/gfx/render_shapes.cpp index 7b231dae..efaf5584 100644 --- a/src/gfx/render_shapes.cpp +++ b/src/gfx/render_shapes.cpp @@ -36,7 +36,15 @@ std::map colour_map = { {"dark_blue", Colours::DARK_BLUE}, {"dark-green", Colours::DARK_GREEN}, {"light-green", Colours::LIGHT_GREEN}, - {"dark-red", Colours::DARK_RED} + {"dark-red", Colours::DARK_RED}, + // Extra Colors + {"lime", Colours::LIME}, + {"aqua", Colours::AQUA}, + {"fuchsia", Colours::FUCHSIA}, + {"maroon", Colours::MAROON}, + {"olive", Colours::OLIVE}, + {"purple", Colours::PURPLE}, + {"silvel", Colours::SILVER} }; // TODO: Put these classes in a header? diff --git a/src/gfx/render_shapes.hpp b/src/gfx/render_shapes.hpp index e11f9ba4..d56610ae 100644 --- a/src/gfx/render_shapes.hpp +++ b/src/gfx/render_shapes.hpp @@ -67,6 +67,14 @@ namespace Colours { const sf::Color DARK_GREEN { 0x00, 0x60, 0x00}; // formerly c[5] QD colour = {0,40959,0} (talking buttons) const sf::Color LIGHT_GREEN { 0x00, 0xa0, 0x00}; // formerly c[6] QD colour = {0,24575,0} (talking buttons pressed) const sf::Color DARK_RED { 0xa0, 0x00, 0x14}; // formerly c[7] (clickable text, new in OBoE) + // Extra colors + const sf::Color LIME { 0x00, 0xFF, 0x00}; + const sf::Color AQUA { 0x00, 0xFF, 0xFF}; + const sf::Color FUCHSIA { 0xFF, 0x00, 0xFF}; + const sf::Color MAROON { 0x80, 0x00, 0x00}; + const sf::Color OLIVE { 0x80, 0x80, 0x00}; + const sf::Color PURPLE { 0x80, 0x00, 0x80}; + const sf::Color SILVER { 0xC0, 0xC0, 0xC0}; } const sf::Color PRESET_WORD_ON = Colours::DARK_GREEN; From 4c53acc6863825c7683463a7487b32b566b493db Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Tue, 21 Jan 2025 19:36:19 -0600 Subject: [PATCH 20/21] move the asPeaceful() note to where it will help --- src/spell.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/spell.cpp b/src/spell.cpp index f43b04cb..b454b157 100644 --- a/src/spell.cpp +++ b/src/spell.cpp @@ -47,7 +47,6 @@ cSpell& cSpell::needsSelect(eSpellSelect sel) { return *this; } -// Mark a spell as castable by pacifist PCs cSpell& cSpell::asPeaceful() { peaceful = true; return *this; @@ -93,6 +92,10 @@ eSpell cSpell::fromNum(int num) { return check; } +// NOTE: +// asPeaceful() marks a spell as castable by pacifist PCs. It doesn't mean "only in peace mode" +// (which would make a lot of these contradictory/broken) + // Mage Spells cSpell M_LIGHT = cSpell(eSpell::LIGHT).asType(eSkill::MAGE_SPELLS).asLevel(1) .withCost(1).when(WHEN_COMBAT).when(WHEN_TOWN).when(WHEN_OUTDOORS).asPeaceful().finish(); From d8089fe013fb47759a61a182e6161a667a3a4b9b Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Wed, 22 Jan 2025 11:01:22 -0600 Subject: [PATCH 21/21] silver --- src/gfx/render_shapes.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gfx/render_shapes.cpp b/src/gfx/render_shapes.cpp index efaf5584..bc08df31 100644 --- a/src/gfx/render_shapes.cpp +++ b/src/gfx/render_shapes.cpp @@ -44,7 +44,7 @@ std::map colour_map = { {"maroon", Colours::MAROON}, {"olive", Colours::OLIVE}, {"purple", Colours::PURPLE}, - {"silvel", Colours::SILVER} + {"silver", Colours::SILVER} }; // TODO: Put these classes in a header?