Merge pull request #536 from NQNStudios:recast-hint
Quality of life: Spellcasting This makes changes to the spellcasting UI. * M or P to recast will no longer default to Light or Minor Bless/Minor Heal. You need to cast something before recast becomes available. This fixes #535 and I think it's disorienting when I've just started the game and M casts Light in a town that's fully lit, so the change is generally good I'd say. * I implemented a recasting hint in the text bar, which was one of the things I mentioned in my quality-of-life checklist https://github.com/NQNStudios/cboe/issues/16. It replaces the status icons in combat mode. * Sometimes when my eyes glaze over, I think I'm casting the spell on the wrong side of the LED. I thought there was a bug when I cast Long Light instead of Dumbfound (even though I know the distance between the two is pretty large -- I wasn't paying much attention). I thought it would be nice to highlight the name of the selected spell. Light green seemed to make more sense than red for that, because the LED turns green. Then I made the caster/target selection texts also use light green instead of red, to match. Uncastable spells are grey.
This commit is contained in:
@@ -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;
|
||||
@@ -1864,6 +1872,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;
|
||||
@@ -2472,6 +2481,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();
|
||||
|
@@ -611,8 +611,42 @@ 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 << ": ";
|
||||
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 << "No spell to 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 +657,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,9 +669,13 @@ 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);
|
||||
|
||||
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;
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
@@ -94,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 ;
|
||||
@@ -490,6 +498,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;
|
||||
@@ -1640,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 {
|
||||
@@ -1691,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);
|
||||
@@ -1706,7 +1722,7 @@ static void put_pc_caster_buttons(cDialog& me) {
|
||||
std::string n = boost::lexical_cast<std::string>(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());
|
||||
}
|
||||
}
|
||||
@@ -1716,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<std::string>(store_spell_target + 1);
|
||||
me["hp" + n].setColour(Colours::RED);
|
||||
me["sp" + n].setColour(Colours::RED);
|
||||
me["arrow" + n].show();
|
||||
}
|
||||
if((store_last_target_darkened < 6) && (store_last_target_darkened != store_spell_target)) {
|
||||
std::string n = boost::lexical_cast<std::string>(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;
|
||||
}
|
||||
@@ -1740,12 +1754,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1862,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);
|
||||
|
||||
@@ -1914,6 +1930,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<short>(store_spell);
|
||||
return true;
|
||||
@@ -1921,6 +1938,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<short>(store_spell);
|
||||
return true;
|
||||
@@ -1938,6 +1956,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;
|
||||
}
|
||||
@@ -1947,7 +1966,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 +2014,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;
|
||||
@@ -2042,9 +2070,7 @@ eSpell pick_spell(short pc_num,eSkill type) { // 70 - no spell OW spell num
|
||||
cLed& led = dynamic_cast<cLed&>(castSpell[id]);
|
||||
led.attachKey(key);
|
||||
castSpell.addLabelFor(id, {static_cast<char>(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)));
|
||||
|
Reference in New Issue
Block a user