Files
oboe/src/game/boe.text.cpp
ALONSO Laurent c709d6ec6f game:
+ draw terrains: try to improve the display of the fields,
+ try to avoid flikering in the text messages' zone
+ get items: correct the bottom button hide/show
2022-06-30 01:10:46 -04:00

1192 lines
41 KiB
C++

const int LINES_IN_TEXT_WIN = 11;
const int TEXT_BUF_LEN = 70;
#include <sstream>
#include <list>
#include "boe.global.hpp"
#include "boe.graphutil.hpp"
#include "universe.hpp"
#include "boe.text.hpp"
#include "boe.locutils.hpp"
#include "boe.infodlg.hpp"
#include "mathutil.hpp"
#include "render_text.hpp"
#include "render_image.hpp"
#include "render_shapes.hpp"
#include "tiling.hpp"
#include "utility.hpp"
#include "scrollbar.hpp"
#include "res_image.hpp"
#include "res_font.hpp"
#include "spell.hpp"
#include "enum_map.hpp"
typedef struct {
char line[50];
} buf_line;
buf_line text_buffer[TEXT_BUF_LEN];
short buf_pointer = 30, lines_to_print= 0, num_added_since_stop = 0;
short start_print_point= 0;
short mark_where_printing_long;
bool printing_long = false;
char c_str[256] = "";
bool save_mess_given = false;
rectangle status_panel_clip_rect = {11, 299, 175, 495},item_panel_clip_rect = {11,297,175,463};
rectangle item_buttons_from[7] = {
{11,0,24,14},{11,14,24,28},{11,28,24,42},{11,42,24,56},
{24,0,36,30},{24,30,36,60},{36,0,48,30}
};
eGameMode store_mode;
extern short had_text_freeze;
extern eStatMode stat_screen_mode;
// graphics globals
extern short which_combat_type;
extern eGameMode overall_mode;
extern eItemWinMode stat_window;
extern sf::RenderWindow mainPtr;
extern rectangle more_info_button;
extern short which_item_page[6];
extern std::shared_ptr<cScrollbar> text_sbar,item_sbar;
extern location store_anim_ul;
extern tessel_ref_t bg[];
extern short dest_personalities[40];
extern location source_locs[6];
extern location dest_locs[40] ;
extern location center;
extern cCustomGraphics spec_scen_g;
extern sf::RenderTexture pc_stats_gworld, item_stats_gworld, text_area_gworld;
extern sf::RenderTexture terrain_screen_gworld;
extern rectangle text_area_rect;
// game globals
extern enum_map(eItemButton, rectangle) item_buttons[8];
extern enum_map(ePlayerButton, rectangle) pc_buttons[6];
extern enum_map(eItemButton, bool) item_area_button_active[8];
extern enum_map(ePlayerButton, bool) pc_area_button_active[6];
extern rectangle item_screen_button_rects[9];
extern std::vector<int> spec_item_array;
// combat globals
extern short item_bottom_button_active[9];
extern cUniverse univ;
extern short shop_identify_cost;
extern short store_selling_values[8];
extern short combat_posing_monster, current_working_monster; // 0-5 PC 100 + x - monster x
extern bool supressing_some_spaces;
extern location ok_space[4];
extern std::map<eStatus, std::pair<short, short>> statIcons;
// Draws the pc area in upper right
void put_pc_screen() {
rectangle erase_rect = {17,2,98,269},to_draw_rect,from_rect;
rectangle food_rect[2] = {{103,34,114,76}, {103,3,114,40}};
rectangle gold_rect[2] = {{103,106,114,147}, {103,75,114,104}};
rectangle day_rect[2] = {{103,174,114,201}, {103,147,114,172}};
rectangle title_rects[3] = {{4,4,16,180}, {4,184,16,214}, {4,214,16,237}};
rectangle bottom_bar_rect = {99,0,116,271};
rectangle info_from = {0,1,12,13}, switch_from = {0, 13, 12, 25};
pc_stats_gworld.setActive(false);
// First clean up gworld with pretty patterns
auto const &orig = *ResMgr::textures.get("statarea");
rectangle const stats_rect(orig);
rect_draw_some_item(orig, stats_rect, pc_stats_gworld, stats_rect);
tileImage(pc_stats_gworld, erase_rect,bg[6]);
TextStyle style;
style.font = FONT_BOLD;
style.pointSize = 10;
style.colour = Colours::YELLOW;
style.lineHeight = 10;
win_draw_string(pc_stats_gworld,food_rect[1],"Food:",eTextMode::WRAP,style);
win_draw_string(pc_stats_gworld,gold_rect[1],"Gold:",eTextMode::WRAP,style);
win_draw_string(pc_stats_gworld,day_rect[1],"Day:",eTextMode::WRAP,style);
win_draw_string(pc_stats_gworld,title_rects[0],"Party stats:",eTextMode::WRAP,style);
win_draw_string(pc_stats_gworld,title_rects[1],"HP:",eTextMode::WRAP,style);
win_draw_string(pc_stats_gworld,title_rects[2],"SP:",eTextMode::WRAP,style);
style.colour = Colours::WHITE;
style.pointSize = 12;
// Put food, gold, day
win_draw_string(pc_stats_gworld,food_rect[0],std::to_string(univ.party.food),eTextMode::WRAP,style);
win_draw_string(pc_stats_gworld,gold_rect[0],std::to_string(univ.party.gold),eTextMode::WRAP,style);
win_draw_string(pc_stats_gworld,day_rect[0],std::to_string(univ.party.calc_day()),eTextMode::WRAP,style);
style.colour = Colours::BLACK;
auto const & invenbtn_gworld = *ResMgr::textures.get("invenbtns");
for(short i = 0; i < 6; i++) {
if(univ.party[i].main_status != eMainStatus::ABSENT) {
for(auto& flag : pc_area_button_active[i])
flag = true;
if(i == univ.cur_pc) {
style.italic = true;
style.colour = Colours::BLUE;
}
std::ostringstream sout;
sout << i + 1 << ". " << univ.party[i].name;
win_draw_string(pc_stats_gworld,pc_buttons[i][PCBTN_NAME],sout.str(),eTextMode::WRAP,style);
style.italic = false;
style.colour = Colours::BLACK;
to_draw_rect = pc_buttons[i][PCBTN_HP];
to_draw_rect.right += 20;
sout.str("");
switch(univ.party[i].main_status) {
case eMainStatus::ALIVE:
if(univ.party[i].cur_health == univ.party[i].max_health)
style.colour = Colours::GREEN;
else if(univ.party[i].cur_health > univ.party[i].max_health)
style.colour = Colours::ORANGE;
else style.colour = Colours::RED;
win_draw_string( pc_stats_gworld,pc_buttons[i][PCBTN_HP],std::to_string(univ.party[i].cur_health),eTextMode::WRAP,style);
if(univ.party[i].cur_sp == univ.party[i].max_sp)
style.colour = Colours::BLUE;
else if(univ.party[i].cur_sp > univ.party[i].max_sp)
style.colour = Colours::TEAL;
else style.colour = Colours::PINK;
win_draw_string( pc_stats_gworld,pc_buttons[i][PCBTN_SP],std::to_string(univ.party[i].cur_sp),eTextMode::WRAP,style);
draw_pc_effects(i);
break;
case eMainStatus::DEAD:
sout << "Dead";
break;
case eMainStatus::DUST:
sout << "Dust";
break;
case eMainStatus::STONE:
sout << "Stone";
break;
case eMainStatus::FLED:
sout << "Fled";
break;
case eMainStatus::SURFACE:
sout << "Surface";
break;
case eMainStatus::WON:
sout << "Won";
break;
default:
sout << "Absent";
break;
}
if(univ.party[i].main_status != eMainStatus::ALIVE)
win_draw_string( pc_stats_gworld,to_draw_rect,sout.str(),eTextMode::WRAP,style);
style.colour = Colours::BLACK;
// Now put trade and info buttons
rect_draw_some_item(invenbtn_gworld,info_from,pc_stats_gworld,pc_buttons[i][PCBTN_INFO],sf::BlendAlpha);
rect_draw_some_item(invenbtn_gworld,switch_from,pc_stats_gworld,pc_buttons[i][PCBTN_TRADE],sf::BlendAlpha);
}
else {
for(auto& flag : pc_area_button_active[i])
flag = false;
}
}
rectangle help_from_rect = {46,60,59,76};
to_draw_rect = {101,251,114,267};
rect_draw_some_item(invenbtn_gworld, help_from_rect, pc_stats_gworld, to_draw_rect, sf::BlendAlpha);
pc_stats_gworld.setActive();
pc_stats_gworld.display();
// Sometimes this gets called when character is slain. when that happens, if items for
// that PC are up, switch item page.
if(univ.cur_pc < 6 && univ.current_pc().main_status != eMainStatus::ALIVE && stat_window == univ.cur_pc) {
set_stat_window_for_pc(univ.cur_pc);
}
}
// Draws item area in middle right
// Screen_num is what page is visible in the item menu.
void put_item_screen(eItemWinMode screen_num) {
std::ostringstream sout;
long i_num;
long item_offset;
short pc;
rectangle erase_rect = {17,2,122,255},dest_rect;
rectangle upper_frame_rect = {3,3,15,268};
item_stats_gworld.setActive(false);
// First clean up gworld with pretty patterns
auto const & orig = *ResMgr::textures.get("inventory");
rectangle const item_stats_rect(orig);
rect_draw_some_item(orig, item_stats_rect, item_stats_gworld, item_stats_rect);
tileImage(item_stats_gworld, erase_rect,bg[6]);
// Draw buttons at bottom
item_offset = item_sbar->getPosition();
for(short i = 0; i < 8; i++)
for(auto& flag : item_area_button_active[i])
flag = false;
TextStyle style;
style.lineHeight = 10;
style.font = FONT_BOLD;
style.colour = Colours::YELLOW;
switch(screen_num) {
case ITEM_WIN_SPECIAL:
win_draw_string(item_stats_gworld,upper_frame_rect,"Special items:",eTextMode::WRAP,style);
break;
case ITEM_WIN_QUESTS:
win_draw_string(item_stats_gworld,upper_frame_rect,"Quests/Jobs:",eTextMode::WRAP,style);
break;
default: // on an items page
pc = screen_num;
sout.str("");;
sout << univ.party[pc].name << " inventory:",
win_draw_string(item_stats_gworld,upper_frame_rect,sout.str(),eTextMode::WRAP,style);
break;
}
clip_rect(item_stats_gworld, erase_rect);
switch(screen_num) {
case ITEM_WIN_SPECIAL:
style.colour = Colours::BLACK;
for(short i = 0; i < 8; i++) {
i_num = i + item_offset;
if(i_num < spec_item_array.size()) {
win_draw_string(item_stats_gworld,item_buttons[i][ITEMBTN_NAME],univ.get_special_item(spec_item_array[i_num]).name,eTextMode::WRAP,style);
place_item_button(3,i,ITEMBTN_INFO);
if((univ.get_special_item(spec_item_array[i_num]).flags % 10 == 1)
&& (!(is_combat())))
place_item_button(0,i,ITEMBTN_USE);
}
}
break;
case ITEM_WIN_QUESTS:
style.colour = Colours::BLACK;
for(short i = 0; i < 8; i++) {
i_num = i + item_offset;
if(i_num < spec_item_array.size()) {
int which_quest = spec_item_array[i_num] % 10000;
if(spec_item_array[i_num] / 10000 == 2)
style.colour = Colours::RED;
win_draw_string(item_stats_gworld,item_buttons[i][ITEMBTN_NAME],univ.get_quest(which_quest).name,eTextMode::WRAP,style);
if(spec_item_array[i_num] / 10000 == 1) {
location from, to;
from = to = item_buttons[i][ITEMBTN_NAME].centre();
from.x = item_buttons[i][ITEMBTN_NAME].left;
to.x = from.x + string_length(univ.get_quest(which_quest).name, style);
draw_line(item_stats_gworld, from, to, 1, Colours::GREEN);
}
place_item_button(3,i,ITEMBTN_INFO);
}
}
break;
default: // on an items page
style.colour = Colours::BLACK;
for(short i = 0; i < 8; i++) {
i_num = i + item_offset;
sout.str("");
sout << i_num + 1 << '.';
win_draw_string(item_stats_gworld,item_buttons[i][ITEMBTN_NAME],sout.str(),eTextMode::WRAP,style);
dest_rect = item_buttons[i][ITEMBTN_NAME];
dest_rect.left += 36;
dest_rect.top -= 2;
const cPlayer& who = univ.party[pc];
const cItem& item = who.items[i_num];
if(item.variety != eItemType::NO_ITEM) {
style.font = FONT_PLAIN;
if(who.equip[i_num]) {
style.italic = true;
if(item.variety == eItemType::ONE_HANDED || item.variety == eItemType::TWO_HANDED)
style.colour = Colours::PINK;
else if((*item.variety).is_armour)
style.colour = Colours::GREEN;
else style.colour = Colours::BLUE;
} else style.colour = Colours::BLACK;
sout.str("");
if(!item.ident)
sout << item.name << " ";
else { /// Don't place # of charges when Sell button up and space tight
sout << item.full_name << ' ';
if(item.charges > 0 && item.ability != eItemAbil::MESSAGE && (stat_screen_mode == MODE_INVEN || stat_screen_mode == MODE_SHOP))
sout << '(' << int(item.charges) << ')';
}
dest_rect.left -= 2;
win_draw_string(item_stats_gworld,dest_rect,sout.str(),eTextMode::WRAP,style);
style.italic = false;
style.colour = Colours::BLACK;
if(stat_screen_mode == MODE_SHOP && !is_combat()) { // place give and drop and use
place_item_graphic(i,item.graphic_num);
place_item_button(3,i,ITEMBTN_INFO); // info button
}
else {
place_item_graphic(i,item.graphic_num);
place_item_button(3,i,ITEMBTN_INFO); // info button
if((stat_screen_mode == MODE_INVEN) &&
((is_town()) || (is_out()) || ((is_combat()) && (pc == univ.cur_pc)))) { // place give and drop and use
place_item_button(1,i,ITEMBTN_GIVE);
place_item_button(2,i,ITEMBTN_DROP);
if(item.can_use()) // place use if can
place_item_button(0,i,ITEMBTN_USE);
}
}
if(stat_screen_mode != MODE_INVEN && stat_screen_mode != MODE_SHOP) {
place_buy_button(i,pc,i_num);
}
} // end of if item is there
} // end of for(short i = 0; i < 8; i++)
break;
}
undo_clip(item_stats_gworld);
place_item_bottom_buttons();
item_stats_gworld.setActive();
item_stats_gworld.display();
}
void place_buy_button(short position,short pc_num,short item_num) {
rectangle dest_rect,source_rect;
rectangle button_sources[3] = {{24,0,36,30},{36,0,48,30},{48,0,60,30}};
short val_to_place;
// TODO: This is now duplicated here and in start_town_mode()
short aug_cost[10] = {4,7,10,8, 15,15,10, 0,0,0};
const cPlayer& pc = univ.party[pc_num];
const cItem& item = pc.items[item_num];
if(item.variety == eItemType::NO_ITEM)
return;
dest_rect = item_buttons[position][ITEMBTN_SPEC];
val_to_place = (item.charges > 0) ?
item.charges * item.value :
item.value;
val_to_place = val_to_place / 2;
switch(stat_screen_mode) {
case MODE_IDENTIFY:
if(!item.ident) {
item_area_button_active[position][ITEMBTN_SPEC] = true;
source_rect = button_sources[0];
val_to_place = shop_identify_cost;
}
break;
case MODE_SELL_WEAP:
if((*item.variety).is_weapon && !pc.equip[item_num] && item.ident && val_to_place > 0 && !item.unsellable) {
item_area_button_active[position][ITEMBTN_SPEC] = true;
source_rect = button_sources[1];
}
break;
case MODE_SELL_ARMOR:
if((*item.variety).is_armour && !pc.equip[item_num] && item.ident && val_to_place > 0 && !item.unsellable) {
item_area_button_active[position][ITEMBTN_SPEC] = true;
source_rect = button_sources[1];
}
break;
case MODE_SELL_ANY:
if(!pc.equip[item_num] && item.ident && val_to_place > 0 && !item.unsellable) {
item_area_button_active[position][ITEMBTN_SPEC] = true;
source_rect = button_sources[1];
}
break;
case MODE_ENCHANT:
if((item.variety == eItemType::ONE_HANDED || item.variety == eItemType::TWO_HANDED) && item.ident && item.ability == eItemAbil::NONE && !item.magic) {
item_area_button_active[position][ITEMBTN_SPEC] = true;
source_rect = button_sources[2];
val_to_place = max(aug_cost[shop_identify_cost] * 100, item.value * (5 + aug_cost[shop_identify_cost]));
}
break;
case MODE_INVEN: case MODE_SHOP:
// These modes don't have buy buttons, so we shouldn't get here; bail out!
return;
}
if(item_area_button_active[position][ITEMBTN_SPEC]) {
auto const & invenbtn_gworld = *ResMgr::textures.get("invenbtns");
store_selling_values[position] = val_to_place;
dest_rect = item_buttons[position][ITEMBTN_SPEC];
dest_rect.right = dest_rect.left + 30;
rect_draw_some_item(invenbtn_gworld, source_rect, item_stats_gworld, dest_rect, sf::BlendAlpha);
TextStyle style;
if(val_to_place >= 10000)
style.font = FONT_PLAIN;
style.lineHeight = 10;
dest_rect.offset(dest_rect.width() + 5,0);
win_draw_string(item_stats_gworld,dest_rect,std::to_string(val_to_place),eTextMode::LEFT_TOP,style);
}
}
void place_item_graphic(short which_slot,short graphic) {
rectangle from_rect = {0,0,18,18},to_rect;
item_area_button_active[which_slot][ITEMBTN_NAME] = item_area_button_active[which_slot][ITEMBTN_ICON] = true;
from_rect.offset((graphic % 10) * 18,(graphic / 10) * 18);
to_rect = item_buttons[which_slot][ITEMBTN_ICON];
Texture src_gw;
if(graphic >= 10000) {
std::tie(src_gw,from_rect) = spec_scen_g.find_graphic(graphic - 10000, true);
rect_draw_some_item(src_gw, from_rect, item_stats_gworld, to_rect,sf::BlendAlpha);
} else if(graphic >= 1000) {
std::tie(src_gw,from_rect) = spec_scen_g.find_graphic(graphic - 1000);
rect_draw_some_item(src_gw, from_rect, item_stats_gworld, to_rect,sf::BlendAlpha);
}
else rect_draw_some_item(*ResMgr::textures.get("tinyobj"), from_rect, item_stats_gworld, to_rect, sf::BlendAlpha);
}
// name, use, give, drop, info, sell/id
// shortcuts - if which_button_to_put is 10, all 4 buttons now
// if which_button_to_put is 11, just right 2
void place_item_button(short button_position,short which_slot,eItemButton button_type) {
rectangle from_rect = {0,0,18,18},to_rect;
auto const & invenbtn_gworld = *ResMgr::textures.get("invenbtns");
switch(button_position) {
default: // this means put a regular item button
item_area_button_active[which_slot][button_type] = true;
rect_draw_some_item(invenbtn_gworld, item_buttons_from[button_type - 2], item_stats_gworld, item_buttons[which_slot][button_type], sf::BlendAlpha);
break;
case ITEMBTN_ALL: // this means put all 4
item_area_button_active[which_slot][ITEMBTN_USE] = true;
item_area_button_active[which_slot][ITEMBTN_GIVE] = true;
item_area_button_active[which_slot][ITEMBTN_DROP] = true;
item_area_button_active[which_slot][ITEMBTN_INFO] = true;
from_rect = item_buttons_from[0];
from_rect.right = item_buttons_from[3].right;
to_rect = item_buttons[which_slot][ITEMBTN_USE];
to_rect.right = to_rect.left + from_rect.right - from_rect.left;
rect_draw_some_item(invenbtn_gworld, from_rect, item_stats_gworld, to_rect, sf::BlendAlpha);
break;
case ITEMBTN_NORM: // this means put right 3
item_area_button_active[which_slot][ITEMBTN_GIVE] = true;
item_area_button_active[which_slot][ITEMBTN_DROP] = true;
item_area_button_active[which_slot][ITEMBTN_INFO] = true;
from_rect = item_buttons_from[1];
from_rect.right = item_buttons_from[3].right;
to_rect = item_buttons[which_slot][ITEMBTN_GIVE];
to_rect.right = to_rect.left + from_rect.right - from_rect.left;
rect_draw_some_item(invenbtn_gworld, from_rect, item_stats_gworld, to_rect, sf::BlendAlpha);
break;
}
}
void place_item_bottom_buttons() {
rectangle pc_from_rect = {0,0,36,28},but_from_rect = {30,60,46,78},to_rect;
rectangle spec_from_rect = {0,60,15,95}, job_from_rect = {15,60,30,95}, help_from_rect = {46,60,59,76};
// TODO: What about when the buttons are pressed?
TextStyle style;
style.lineHeight = 10;
style.pointSize = 12;
style.font = FONT_BOLD;
style.colour = Colours::YELLOW;
auto const & invenbtn_gworld = *ResMgr::textures.get("invenbtns");
for(short i = 0; i < 6; i++) {
if(univ.party[i].main_status == eMainStatus::ALIVE) {
item_bottom_button_active[i] = true;
to_rect = item_screen_button_rects[i];
rect_draw_some_item(invenbtn_gworld, but_from_rect, item_stats_gworld, to_rect, sf::BlendAlpha);
pic_num_t pic = univ.party[i].which_graphic;
Texture from_gw;
if(pic >= 1000) {
bool isParty = pic >= 10000;
pic_num_t need_pic = pic % 1000;
std::tie(from_gw,pc_from_rect) = spec_scen_g.find_graphic(need_pic, isParty);
} else if(pic >= 100) {
// Note that we assume it's a 1x1 graphic.
// PCs can't be larger than that, but we leave it to the scenario designer to avoid assigning larger graphics.
pic_num_t need_pic = pic - 100;
int mode = 0;
pc_from_rect = get_monster_template_rect(need_pic, mode, 0);
int which_sheet = m_pic_index[need_pic].i / 20;
from_gw = *ResMgr::textures.get("monst" + std::to_string(1 + which_sheet));
} else {
pc_from_rect = calc_rect(2 * (pic / 8), pic % 8);
from_gw = *ResMgr::textures.get("pcs");
}
to_rect.inset(2,2);
rect_draw_some_item(from_gw, pc_from_rect, item_stats_gworld, to_rect, sf::BlendAlpha);
std::string numeral = std::to_string(i + 1);
short width = string_length(numeral, style);
// Offset "6" down two pixels to make it line up, because it has an ascender in this font
// Offset "1" - "4" down as well because they're not shorter and it looks a bit better
to_rect.offset(-width - 5, i != 4 ? 2 : 0);
win_draw_string(item_stats_gworld, to_rect, numeral, eTextMode::LEFT_TOP, style);
}
else item_bottom_button_active[i] = false;
}
to_rect = item_screen_button_rects[6];
rect_draw_some_item(invenbtn_gworld, spec_from_rect, item_stats_gworld, to_rect, sf::BlendAlpha);
to_rect = item_screen_button_rects[7];
rect_draw_some_item(invenbtn_gworld, job_from_rect, item_stats_gworld, to_rect, sf::BlendAlpha);
to_rect = item_screen_button_rects[8];
rect_draw_some_item(invenbtn_gworld, help_from_rect, item_stats_gworld, to_rect, sf::BlendAlpha);
}
void set_stat_window_for_pc(int pc) {
if(pc < 0) pc = 0;
if(pc > 5) pc = 5;
set_stat_window(eItemWinMode(pc));
}
void set_stat_window(eItemWinMode new_stat) {
if(new_stat == ITEM_WIN_SPECIAL)
give_help(50,0);
short array_pos = 0;
stat_window = new_stat;
if(stat_window < ITEM_WIN_SPECIAL && univ.party[stat_window].main_status != eMainStatus::ALIVE)
stat_window = eItemWinMode(first_active_pc());
item_sbar->setPageSize(8);
spec_item_array.clear();
switch(stat_window) {
case ITEM_WIN_SPECIAL:
std::fill(spec_item_array.begin(), spec_item_array.end(), -1);
for(short i = 0; i < univ.scenario.special_items.size(); i++)
if(univ.party.spec_items.count(i)) {
spec_item_array.push_back(i);
array_pos++;
}
array_pos = max(0,array_pos - 8);
item_sbar->setMaximum(array_pos);
break;
case ITEM_WIN_QUESTS:
std::fill(spec_item_array.begin(), spec_item_array.end(), -1);
for(short i = 0; i < univ.scenario.quests.size(); i++)
if(univ.party.active_quests[i].status == eQuestStatus::STARTED) {
spec_item_array.push_back(i);
array_pos++;
} else if(univ.party.active_quests[i].status == eQuestStatus::COMPLETED) {
spec_item_array.push_back(i + 10000);
array_pos++;
} else if(univ.party.active_quests[i].status == eQuestStatus::FAILED) {
spec_item_array.push_back(i + 20000);
array_pos++;
}
array_pos = max(0,array_pos - 8);
item_sbar->setMaximum(array_pos);
break;
default:
item_sbar->setMaximum(16);
break;
}
item_sbar->setPosition(0);
put_item_screen(stat_window);
}
short first_active_pc() {
for(short i = 0; i < 6; i++)
if(univ.party[i].main_status == eMainStatus::ALIVE)
return i;
return 6;
}
void refresh_stat_areas(short mode) {
sf::BlendMode x;
extern enum_map(eGuiArea, rectangle) win_to_rects;
if(mode == 1) x = sf::BlendAdd;
else x = sf::BlendNone;
rect_draw_some_item(pc_stats_gworld.getTexture(), rectangle(pc_stats_gworld), mainPtr, win_to_rects[WINRECT_PCSTATS], x);
rect_draw_some_item(item_stats_gworld.getTexture(), rectangle(item_stats_gworld), mainPtr, win_to_rects[WINRECT_INVEN], x);
rect_draw_some_item(text_area_gworld.getTexture(), rectangle(text_area_gworld), mainPtr, win_to_rects[WINRECT_TRANSCRIPT], x);
}
rectangle get_stat_effect_rect(int code) {
rectangle base = {0,0,12,12};
base.offset(12 * (code % 3), 12 * (code / 3));
return base;
}
void draw_pc_effects(short pc) {
rectangle dest_rect = {18,15,30,27};
short right_limit = 250;
short name_width;
TextStyle style;
name_width = string_length(univ.party[pc].name, style);
right_limit = pc_buttons[0][PCBTN_HP].left - 5;
dest_rect.left = name_width + 33;
dest_rect.right = dest_rect.left + 12;
dest_rect.top += pc * 13;
dest_rect.bottom += pc * 13;
if(exceptSplit(univ.party[pc].main_status) != eMainStatus::ALIVE)
return;
univ.party[pc].status[eStatus::HASTE_SLOW]; // This just makes sure it exists in the map, without changing its value if it does
auto const & status_gworld = *ResMgr::textures.get("staticons");
for(auto next : univ.party[pc].status) {
short placedIcon = -1;
if(next.first == eStatus::POISON && next.second > 4) placedIcon = 1;
else if(next.second > 0) placedIcon = statIcons[next.first].first;
else if(next.second < 0) placedIcon = statIcons[next.first].second;
else if(next.first == eStatus::HASTE_SLOW) placedIcon = 7;
if(placedIcon >= 0) {
rect_draw_some_item(status_gworld, get_stat_effect_rect(placedIcon), pc_stats_gworld, dest_rect, sf::BlendAlpha);
dest_rect.offset(13, 0);
}
if(dest_rect.right >= right_limit) break;
}
}
void print_party_stats() {
add_string_to_buf("PARTY STATS:");
add_string_to_buf(" Number of kills: " + std::to_string(univ.party.total_m_killed));
if((is_town()) || ((is_combat()) && (which_combat_type == 1))) {
add_string_to_buf(" Kills in this town: " + std::to_string(univ.town->m_killed));
}
add_string_to_buf(" Total experience: " + std::to_string(univ.party.total_xp_gained));
add_string_to_buf(" Total damage done: " + std::to_string(univ.party.total_dam_done));
add_string_to_buf(" Total damage taken: " + std::to_string(univ.party.total_dam_taken));
print_buf();
}
short do_look(location space) {
short num_items = 0;
bool gold_here = false, food_here = false, is_lit = true;
location from_where;
std::string msg;
from_where = get_cur_loc();
is_lit = is_out() || pt_in_light(from_where,space);
if((overall_mode == MODE_LOOK_OUTDOORS && space == univ.party.out_loc) ||
(overall_mode == MODE_LOOK_TOWN && space == univ.party.town_loc))
add_string_to_buf(" Your party");
if(overall_mode == MODE_LOOK_COMBAT)
for(short i = 0; i < 6; i++)
if(space == univ.party[i].combat_pos && univ.party[i].main_status == eMainStatus::ALIVE
&& (is_lit) && (can_see_light(univ.current_pc().combat_pos,space,sight_obscurity) < 5)) {
msg = " " + univ.party[i].name;
add_string_to_buf(msg);
}
if((overall_mode == MODE_LOOK_TOWN) || (overall_mode == MODE_LOOK_COMBAT)) {
for(short i = 0; i < univ.town.monst.size(); i++)
if((univ.town.monst[i].active != 0) && (is_lit)
&& univ.town.monst[i].on_space(space) &&
((overall_mode == MODE_LOOK_TOWN) || (can_see_light(univ.current_pc().combat_pos,space,sight_obscurity) < 5))
&& (univ.town.monst[i].picture_num != 0)) {
msg = " ";
if(univ.town.monst[i].health < univ.town.monst[i].m_health)
msg += "Wounded ";
msg += get_m_name(univ.town.monst[i].number);
msg += univ.town.monst[i].is_friendly() ? " (F)" : " (H)";
add_string_to_buf(msg.c_str());
}
}
if(overall_mode == MODE_LOOK_OUTDOORS) {
for(short i = 0; i < 10; i++) {
if((univ.party.out_c[i].exists)
&& (space == univ.party.out_c[i].m_loc)) {
for(short j = 0; j < 7; j++)
if(univ.party.out_c[i].what_monst.monst[j] != 0) {
msg = get_m_name(univ.party.out_c[i].what_monst.monst[j]);
msg = " " + msg;
add_string_to_buf(msg.c_str());
j = 7;
}
}
}
location lSpace=global_to_local(space);
if(univ.out->is_road(lSpace.x,lSpace.y))
add_string_to_buf(" Road");
if(out_boat_there(space))
add_string_to_buf(" Boat");
if(out_horse_there(space))
add_string_to_buf(" Horse");
if(univ.out->is_special_spot(lSpace.x,lSpace.y))
add_string_to_buf(" Special Encounter");
}
if((overall_mode == MODE_LOOK_TOWN) || (overall_mode == MODE_LOOK_COMBAT)) {
if(univ.town.is_road(space.x,space.y))
add_string_to_buf(" Track");
if(town_boat_there(space))
add_string_to_buf(" Boat");
if(town_horse_there(space))
add_string_to_buf(" Horse");
if(univ.town.is_web(space.x,space.y))
add_string_to_buf(" Web");
if(univ.town.is_crate(space.x,space.y))
add_string_to_buf(" Crate");
if(univ.town.is_barrel(space.x,space.y))
add_string_to_buf(" Barrel");
if(univ.town.is_block(space.x,space.y))
add_string_to_buf(" Stone Block");
if(univ.town.is_fire_barr(space.x,space.y))
add_string_to_buf(" Magic Barrier");
if(univ.town.is_force_barr(space.x,space.y))
add_string_to_buf(" Magic Barrier");
if(univ.town.is_quickfire(space.x,space.y))
add_string_to_buf(" Quickfire");
if(univ.town.is_fire_wall(space.x,space.y))
add_string_to_buf(" Wall of Fire");
if(univ.town.is_force_wall(space.x,space.y))
add_string_to_buf(" Wall of Force");
if(univ.town.is_antimagic(space.x,space.y))
add_string_to_buf(" Antimagic Field");
if(univ.town.is_scloud(space.x,space.y))
add_string_to_buf(" Stinking Cloud");
if(univ.town.is_sleep_cloud(space.x,space.y))
add_string_to_buf(" Sleep Cloud");
if(univ.town.is_ice_wall(space.x,space.y))
add_string_to_buf(" Ice Wall");
if(univ.town.is_blade_wall(space.x,space.y))
add_string_to_buf(" Blade Wall");
if(univ.town.is_force_cage(space.x,space.y))
add_string_to_buf(" Force Cage");
if(univ.town.is_sm_blood(space.x,space.y))
add_string_to_buf(" Blood stain");
if(univ.town.is_med_blood(space.x,space.y))
add_string_to_buf(" Blood stain");
if(univ.town.is_lg_blood(space.x,space.y))
add_string_to_buf(" Blood stain");
if(univ.town.is_sm_slime(space.x,space.y))
add_string_to_buf(" Smears of slime");
if(univ.town.is_lg_slime(space.x,space.y))
add_string_to_buf(" Smears of slime");
if(univ.town.is_ash(space.x,space.y))
add_string_to_buf(" Ashes");
if(univ.town.is_bones(space.x,space.y))
add_string_to_buf(" Bones");
if(univ.town.is_rubble(space.x,space.y))
add_string_to_buf(" Rubble");
for(short i = 0; i < univ.town.items.size(); i++) {
if(univ.town.items[i].variety != eItemType::NO_ITEM && space == univ.town.items[i].item_loc
&& (is_lit)) {
if(univ.town.items[i].variety == eItemType::GOLD)
gold_here = true;
else if(univ.town.items[i].variety == eItemType::FOOD)
food_here = true;
else num_items++;
}
}
if(gold_here)
add_string_to_buf(" Gold");
if(food_here)
add_string_to_buf(" Food");
if(num_items > 8)
add_string_to_buf(" Many items");
else for(short i = 0; i < univ.town.items.size(); i++) {
if(univ.town.items[i].variety != eItemType::NO_ITEM && univ.town.items[i].variety != eItemType::GOLD && univ.town.items[i].variety != eItemType::FOOD &&
(space == univ.town.items[i].item_loc) && (!univ.town.items[i].contained)) {
if(univ.town.items[i].ident)
msg = " " + univ.town.items[i].full_name;
else msg = " " + univ.town.items[i].name;
add_string_to_buf((char *) msg.c_str());
}
}
if(univ.town.is_spot(space.x,space.y))
add_string_to_buf(" Special Encounter");
}
if(!is_lit) {
add_string_to_buf(" Dark");
return 0;
}
return print_terrain(space);
}
cVehicle* town_boat_there(location where) {
for(short i = 0; i < univ.party.boats.size(); i++)
if(univ.party.boats[i].exists && univ.party.boats[i].which_town == univ.party.town_num
&& (where == univ.party.boats[i].loc))
return &univ.party.boats[i];
return nullptr;
}
cVehicle* out_boat_there(location where) {
where = global_to_local(where);
for(short i = 0; i < univ.party.boats.size(); i++)
if((univ.party.boats[i].exists) && (where == univ.party.boats[i].loc)
&& (univ.party.boats[i].which_town == 200))
return &univ.party.boats[i];
return nullptr;
}
cVehicle* town_horse_there(location where) {
for(short i = 0; i < univ.party.horses.size(); i++)
if(univ.party.horses[i].exists && univ.party.horses[i].which_town == univ.party.town_num
&& (where == univ.party.horses[i].loc))
return &univ.party.horses[i];
return nullptr;
}
cVehicle* out_horse_there(location where) {
where = global_to_local(where);
for(short i = 0; i < univ.party.horses.size(); i++)
if((univ.party.horses[i].exists) && (where == univ.party.horses[i].loc)
&& (univ.party.horses[i].which_town == 200))
return &univ.party.horses[i];
return nullptr;
}
void notify_out_combat_began(cOutdoors::cWandering encounter,short *nums) {
std::string msg;
add_string_to_buf("COMBAT!");
for(short i = 0; i < 6; i++)
if(encounter.monst[i] != 0) {
msg = get_m_name(encounter.monst[i]);
std::ostringstream sout;
sout << " " << nums[i] << " x " << msg;
msg = sout.str();
add_string_to_buf(msg);
}
if(encounter.monst[6] != 0) {
msg = " " + get_m_name(encounter.monst[6]);
add_string_to_buf(msg);
}
}
std::string get_m_name(mon_num_t num) {
if(num >= 10000) return univ.party.summons[num - 10000].m_name;
return univ.scenario.scen_monsters[num].m_name;
}
std::string get_ter_name(ter_num_t num) {
if(num == 90 && (is_out() || is_town() || (is_combat() && which_combat_type == 1)))
return "Pit";
return univ.get_terrain(num).name;
}
void print_monst_name(mon_num_t m_type) {
std::string msg = get_m_name(m_type) + ':';
add_string_to_buf((char *) msg.c_str());
}
void damaged_message(short damage,eMonstMelee type) {
std::ostringstream sout;
sout << " " << get_str("monster-abilities",130 + int(type));
sout << " for " << damage;
add_string_to_buf(sout.str().c_str());
}
// This prepares the monster's string for the text bar
std::string print_monster_going(mon_num_t m_num,short ap) {
std::ostringstream sout;
sout << get_m_name(m_num);
sout << " (ap: " << ap << ')';
return sout.str();
}
void print_nums(short a,short b,short c) {
std::ostringstream sout;
sout << "debug: " << a << ' ' << b << ' ' << c;
add_string_to_buf((char *) sout.str().c_str());
}
short print_terrain(location space) {
ter_num_t which_terrain=0;
if(overall_mode == MODE_LOOK_OUTDOORS) {
which_terrain = univ.out[space.x][space.y];
}
if(overall_mode == MODE_LOOK_TOWN || overall_mode == MODE_LOOK_COMBAT)
which_terrain = univ.town->terrain(space.x,space.y);
std::string msg = get_ter_name(which_terrain);
msg = " " + msg;
add_string_to_buf(msg);
return (short) which_terrain;
}
void add_string_to_buf(std::string str) {
// This is a separate overload instead of using a defaulted parameter so that
// it can be passed as an argument to various other functions
add_string_to_buf(str, 0);
}
void add_string_to_buf(std::string str, unsigned short indent) {
static bool inited;
static size_t width;
static TextStyle buf_style;
if(!inited) {
inited = true;
buf_style.font = FONT_PLAIN;
buf_style.pointSize = 12;
width = text_area_rect.width() - 5;
}
if(overall_mode == MODE_STARTUP)
return;
if(str == "") return;
if(str.find_last_not_of(' ') == std::string::npos)
return;
if(indent && string_length(str.substr(0,str.find_last_not_of(' ')), buf_style) >= width) {
if(indent > 20) indent = 20;
size_t split = str.find_last_of(' ', 49);
while(string_length(str.substr(0,split), buf_style) >= width)
split = str.find_last_of(' ', split - 1);
add_string_to_buf(str.substr(0,split));
str = str.substr(split);
std::string space(indent, ' ');
size_t wrap_w = width - string_length(space, buf_style);
while(string_length(str.substr(0,str.find_last_not_of(' ')), buf_style) > wrap_w) {
std::string wrap = space;
split = str.find_last_of(' ', 49 - indent);
while(string_length(str.substr(0,split), buf_style) >= wrap_w)
split = str.find_last_of(' ', split - 1);
wrap += str.substr(0,split);
str = str.substr(split);
add_string_to_buf(wrap);
}
add_string_to_buf(space + str);
return;
}
// Now check if this is a duplicate message
int prev_pointer = buf_pointer - 1;
if(prev_pointer < 0) prev_pointer = TEXT_BUF_LEN - 1;
size_t last = 0, new_last = str.find_last_not_of(' ');
while(last < str.length() && str[last] == text_buffer[prev_pointer].line[last])
last++;
// ASAN last can be 0
while(last>0 && text_buffer[prev_pointer].line[--last] == ' ');
bool is_dup = false;
if(last == new_last) {
size_t num_pos = 0;
enum {begin, f_space, f_lparen, f_x, f_num, f_rparen, err} state = begin;
for(short i = last; i < 50 && text_buffer[prev_pointer].line[i]; i++) {
if(state == f_x) num_pos = i;
if(isdigit(text_buffer[prev_pointer].line[i]) && (state == f_x || state == f_num))
state = f_num;
else switch(text_buffer[prev_pointer].line[i]) {
case ' ':
if(state == begin || state == f_space)
state = f_space;
break;
case '(':
if(state == begin || state == f_space)
state = f_lparen;
break;
case 'x':
if(state == f_lparen)
state = f_x;
break;
case ')':
if(state == f_num)
state = f_rparen;
break;
default:
if(i > last)
state = err;
break;
}
if(state == f_rparen) break;
}
if(state == begin || state == f_space || state == f_rparen) {
is_dup = true;
last++;
}
if(is_dup) {
int lastCount = 1;
if(num_pos > 0)
sscanf(text_buffer[prev_pointer].line + num_pos, "%d", &lastCount);
sprintf(text_buffer[prev_pointer].line + last, " (x%d)", lastCount + 1);
return;
}
}
text_sbar->setPosition(58); // TODO: This seems oddly specific
if(buf_pointer == mark_where_printing_long) {
printing_long = true;
print_buf();
through_sending();
}
sprintf((char *)text_buffer[buf_pointer].line, "%-49.49s", str.c_str());
if(buf_pointer == (TEXT_BUF_LEN - 1))
buf_pointer = 0;
else buf_pointer++;
}
void init_buf() {
for(short i = 0; i < TEXT_BUF_LEN; i++)
sprintf((char *) text_buffer[buf_pointer].line, " ");
}
void print_buf () {
short num_lines_printed = 0;
long ctrl_val;
short line_to_print;
long start_print_point;
rectangle store_text_rect,dest_rect,erase_rect = {2,2,136,255};
text_area_gworld.setActive(false);
// First clean up gworld with pretty patterns
tileImage(text_area_gworld, erase_rect,bg[6]);
ctrl_val = 58 - text_sbar->getPosition();
start_print_point = buf_pointer - LINES_IN_TEXT_WIN - ctrl_val;
if(start_print_point< 0)
start_print_point= TEXT_BUF_LEN + start_print_point;
line_to_print= start_print_point;
location moveTo;
while((line_to_print!= buf_pointer) && (num_lines_printed < LINES_IN_TEXT_WIN)) {
moveTo = location(4, 1 + 12 * num_lines_printed);
sf::Text text(text_buffer[line_to_print].line, *ResMgr::fonts.get("plain"), 12);
text.setColor(Colours::BLACK);
text.setPosition(moveTo);
text_area_gworld.draw(text);
num_lines_printed++;
line_to_print++;
if(line_to_print== TEXT_BUF_LEN) {
line_to_print= 0;
}
if((num_lines_printed == LINES_IN_TEXT_WIN - 1) && (printing_long))
line_to_print= buf_pointer;
}
text_area_gworld.display();
text_area_gworld.setActive();
}
void restart_printing() {
lines_to_print = 0;
//clear_text_panel();
}
void restore_mode() {
overall_mode = store_mode;
}
void through_sending() {
mark_where_printing_long = buf_pointer + LINES_IN_TEXT_WIN - 1;
if(mark_where_printing_long > TEXT_BUF_LEN - 1)
mark_where_printing_long -= TEXT_BUF_LEN;
printing_long = false;
}
/* Draw a bitmap in the world window. hor in 0 .. 8, vert in 0 .. 8,
object is ptr. to bitmap to be drawn, and masking is for Copybits. */
void Draw_Some_Item(const Texture& src_gworld, rectangle src_rect, sf::RenderTarget& targ_gworld,location target, char masked, short main_win) {
rectangle destrec = {0,0,36,28};
if((target.x < 0) || (target.y < 0) || (target.x > 8) || (target.y > 8))
return;
if((supressing_some_spaces) && (target != ok_space[0]) &&
(target != ok_space[1]) && (target != ok_space[2]) && (target != ok_space[3]))
return;
destrec = coord_to_rect(target.x,target.y);
if(main_win == 1) destrec.offset(5,5);
if(main_win == 0) {
if(masked == 1)
rect_draw_some_item(src_gworld, src_rect, targ_gworld, destrec, sf::BlendAlpha);
else rect_draw_some_item(src_gworld, src_rect, targ_gworld, destrec, sf::BlendNone);
} else {
if(masked == 1)
rect_draw_some_item(src_gworld, src_rect, targ_gworld, destrec, sf::BlendAlpha);
else rect_draw_some_item(src_gworld, src_rect, targ_gworld, destrec, sf::BlendNone);
}
}
std::list<text_label_t> posted_labels;
void place_text_label(std::string string, location at, bool centred) {
TextStyle style;
style.font = FONT_PLAIN;
short height = 0;
short width = string_length(string, style, &height);
at.x -= center.x - 4;
at.x *= 28;
at.x += 14;
at.x -= width / 2;
at.y -= center.y - 4;
at.y *= 36;
if(centred)
at.y += 18;
if(at.y == 0) at.y = 36;
else at.y -= height;
rectangle text_rect(at, loc(at.x + width, at.y + height));
text_rect.offset(-min(at.x,0),-min(at.y,0)); // If it's longer, make it off-centre to keep it onscreen.
text_rect.offset(13,13);
posted_labels.push_back({text_rect, string});
}
void draw_text_label(const text_label_t& label) {
sf::Color back_clr = {64, 64, 64, 42};
TextStyle style;
style.font = FONT_PLAIN;
style.colour = Colours::WHITE;
rectangle back_rect = label.text_rect, text_rect = label.text_rect;
back_rect.inset(-7,-7);
back_rect.offset(0,-2);
for(int i = 0; i <= 3; i++) {
fill_roundrect(terrain_screen_gworld, back_rect, 7, back_clr);
back_rect.inset(2,2);
back_clr.a *= 1.5;
}
//text_rect.offset(0, -text_rect.height() + 1);
text_rect.offset(0, -5);
win_draw_string(terrain_screen_gworld, text_rect, label.str, eTextMode::LEFT_TOP, style);
}
// TODO: Replace uses of this function with direct use of calc_rect
rectangle coord_to_rect(short i,short j) {
rectangle to_return = calc_rect(i, j);
to_return.offset(13, 13);
return to_return;
}
// which_day is day event should happen
// which_event is the univ.party.key_times value to cross reference with.
// if the key_time is reached before which_day, event won't happen
// if it's 0, event always happens
bool day_reached(unsigned short which_day, unsigned short which_event) {
// Windows version unconditionally added 20 days for no reason at all.
// Instead, let's add 10 days, but only if easy mode enabled.
if(univ.party.easy_mode) which_day += 10;
if(which_event > 0) {
if(univ.party.key_times.find(which_event) == univ.party.key_times.end())
return false;
if(univ.party.key_times[which_event] < which_day)
return false;
}
if(univ.party.calc_day() >= which_day)
return true;
else return false;
}