1280 lines
44 KiB
C++
1280 lines
44 KiB
C++
|
|
const int LINES_IN_TEXT_WIN = 11;
|
|
const int TEXT_BUF_LEN = 70;
|
|
|
|
const int LINES_IN_ITEM_WIN = 8;
|
|
|
|
#include <sstream>
|
|
#include <list>
|
|
|
|
#include "boe.global.hpp"
|
|
#include "boe.graphics.hpp"
|
|
#include "boe.graphutil.hpp"
|
|
#include "universe/universe.hpp"
|
|
#include "boe.text.hpp"
|
|
#include "boe.locutils.hpp"
|
|
#include "boe.infodlg.hpp"
|
|
#include "mathutil.hpp"
|
|
#include "gfx/render_text.hpp"
|
|
#include "gfx/render_image.hpp"
|
|
#include "gfx/render_shapes.hpp"
|
|
#include "gfx/tiling.hpp"
|
|
#include "utility.hpp"
|
|
#include "dialogxml/widgets/scrollbar.hpp"
|
|
#include "dialogxml/dialogs/strdlog.hpp"
|
|
#include "fileio/resmgr/res_image.hpp"
|
|
#include "fileio/resmgr/res_font.hpp"
|
|
#include "spell.hpp"
|
|
#include "tools/enum_map.hpp"
|
|
#include "tools/winutil.hpp"
|
|
#include "replay.hpp"
|
|
#include <boost/lexical_cast.hpp>
|
|
|
|
typedef struct {
|
|
std::string message;
|
|
int line_count;
|
|
int duplicate_count;
|
|
bool duplicate_overflow;
|
|
} buf_msg;
|
|
|
|
buf_msg text_buffer[TEXT_BUF_LEN];
|
|
|
|
short buf_pointer = 30;
|
|
|
|
rectangle status_panel_clip_rect = {11, 299, 175, 495},item_panel_clip_rect = {11,297,175,463};
|
|
|
|
rectangle item_buttons_from[7] = {
|
|
{12,0,24,14},{12,14,24,28},{12,28,24,42},{12,42,24,56},
|
|
{24,0,36,30},{24,30,36,60},{36,0,48,30}
|
|
};
|
|
|
|
eGameMode store_mode;
|
|
|
|
extern eStatMode stat_screen_mode;
|
|
|
|
// graphics globals
|
|
extern short which_combat_type;
|
|
extern eGameMode overall_mode;
|
|
extern eItemWinMode stat_window;
|
|
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;
|
|
|
|
// 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;
|
|
extern bool prime_time();
|
|
// combat globals
|
|
extern short item_bottom_button_active[9];
|
|
extern cUniverse univ;
|
|
extern short shop_identify_cost, shop_recharge_limit;
|
|
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];
|
|
|
|
// 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);
|
|
clear_scale_aware_text(pc_stats_gworld());
|
|
|
|
// First clean up gworld with pretty patterns
|
|
sf::Texture& orig = *ResMgr::graphics.get("statarea");
|
|
rect_draw_some_item(orig, rectangle(orig), pc_stats_gworld(), rectangle(pc_stats_gworld()));
|
|
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;
|
|
|
|
sf::Texture& invenbtn_gworld = *ResMgr::graphics.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::ELLIPSIS,style);
|
|
style.italic = false;
|
|
style.colour = Colours::BLACK;
|
|
|
|
to_draw_rect = pc_buttons[i][PCBTN_HP];
|
|
to_draw_rect.right += 20;
|
|
clear_sstr(sout);
|
|
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);
|
|
clear_scale_aware_text(item_stats_gworld());
|
|
|
|
// First clean up gworld with pretty patterns
|
|
sf::Texture& orig = *ResMgr::graphics.get("inventory");
|
|
rect_draw_some_item(orig, rectangle(orig), item_stats_gworld(), rectangle(item_stats_gworld()));
|
|
tileImage(item_stats_gworld(), erase_rect,bg[6]);
|
|
|
|
// Draw buttons at bottom
|
|
item_offset = item_sbar->getPosition();
|
|
|
|
for(short i = 0; i < LINES_IN_ITEM_WIN; i++)
|
|
for(auto& flag : item_area_button_active[i])
|
|
flag = false;
|
|
|
|
TextStyle title_style;
|
|
title_style.lineHeight = 10;
|
|
title_style.font = FONT_BOLD;
|
|
title_style.colour = Colours::YELLOW;
|
|
std::string title = "";
|
|
switch(screen_num) {
|
|
case ITEM_WIN_SPECIAL:
|
|
title = "Special items:";
|
|
break;
|
|
case ITEM_WIN_QUESTS:
|
|
title = "Quests/Jobs:";
|
|
break;
|
|
|
|
default: // on an items page
|
|
pc = screen_num;
|
|
clear_sstr(sout);
|
|
sout << univ.party[pc].name << " inventory:";
|
|
title = sout.str();
|
|
break;
|
|
}
|
|
win_draw_string(item_stats_gworld(),upper_frame_rect,title,eTextMode::ELLIPSIS,title_style);
|
|
|
|
TextStyle line_style;
|
|
auto draw_item_string = [&line_style](int i, std::string str, eFont font, sf::Color colour, bool italic = false, location offset = {0, 0}){
|
|
rectangle dest_rect = item_buttons[i][ITEMBTN_NAME];
|
|
dest_rect.offset(offset);
|
|
line_style.font = font;
|
|
line_style.colour = colour;
|
|
line_style.italic = italic;
|
|
win_draw_string(item_stats_gworld(), dest_rect, str, eTextMode::WRAP, line_style);
|
|
};
|
|
|
|
clip_rect(item_stats_gworld(), erase_rect);
|
|
switch(screen_num) {
|
|
case ITEM_WIN_SPECIAL:
|
|
for(short i = 0; i < LINES_IN_ITEM_WIN; i++) {
|
|
i_num = i + item_offset;
|
|
if(i_num < spec_item_array.size()) {
|
|
draw_item_string(i, univ.scenario.special_items[spec_item_array[i_num]].name, FONT_BOLD, Colours::BLACK);
|
|
|
|
item_area_button_active[i][ITEMBTN_NAME] = true;
|
|
place_item_button(i,ITEMBTN_INFO);
|
|
if((univ.scenario.special_items[spec_item_array[i_num]].flags % 10 == 1)
|
|
&& (!(is_combat()))){
|
|
// Put a Use button where the Drop button normally goes,
|
|
// so there's no gap between Use and Info:
|
|
place_item_button(i,ITEMBTN_USE, ITEMBTN_DROP);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case ITEM_WIN_QUESTS:
|
|
for(short i = 0; i < LINES_IN_ITEM_WIN; i++) {
|
|
sf::Color colour = Colours::BLACK;
|
|
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)
|
|
colour = Colours::RED;
|
|
|
|
draw_item_string(i, univ.scenario.quests[which_quest].name, FONT_BOLD, colour);
|
|
|
|
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.scenario.quests[which_quest].name, line_style);
|
|
draw_line(item_stats_gworld(), from, to, 1, Colours::GREEN);
|
|
}
|
|
|
|
item_area_button_active[i][ITEMBTN_NAME] = true;
|
|
place_item_button(i,ITEMBTN_INFO);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default: // on an items page
|
|
for(short i = 0; i < LINES_IN_ITEM_WIN; i++) {
|
|
i_num = i + item_offset;
|
|
clear_sstr(sout);
|
|
sout << i_num + 1 << '.';
|
|
draw_item_string(i, sout.str(), FONT_PLAIN, Colours::BLACK);
|
|
|
|
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];
|
|
|
|
bool italic = false;
|
|
sf::Color colour = Colours::BLACK;
|
|
if(item.variety != eItemType::NO_ITEM) {
|
|
if(who.equip[i_num]) {
|
|
italic = true;
|
|
if(item.variety == eItemType::ONE_HANDED || item.variety == eItemType::TWO_HANDED)
|
|
colour = Colours::PINK;
|
|
else if((*item.variety).is_armour)
|
|
colour = Colours::GREEN;
|
|
else colour = Colours::BLUE;
|
|
}
|
|
|
|
clear_sstr(sout);
|
|
|
|
if(item.ident)
|
|
sout << item.full_name << ' ';
|
|
else
|
|
sout << item.name << " ";
|
|
|
|
// Charges:
|
|
bool show_charges = item.max_charges > 1 || item.charges > 1; // stacked gems have no max, but do have charges
|
|
// Show charges for unidentified ammunition and lockpicks, but not other unidentified items
|
|
if(item.missile < 0 && item.ability != eItemAbil::LOCKPICKS)
|
|
show_charges &= item.ident;
|
|
// Don't show charges if it just shows a dialog
|
|
show_charges &= item.ability != eItemAbil::MESSAGE;
|
|
// Don't show charges when Sell button up and space tight
|
|
show_charges &= (stat_screen_mode == MODE_INVEN || stat_screen_mode == MODE_SHOP);
|
|
if(show_charges){
|
|
sout << '(' << int(item.charges) << ')';
|
|
}
|
|
|
|
dest_rect.left -= 2;
|
|
draw_item_string(i, sout.str(), FONT_PLAIN, colour, italic, {34, -2});
|
|
|
|
place_item_graphic(i,item.graphic_num);
|
|
// The info button is harmless and can be useful while shopping, so always show it
|
|
place_item_button(i,ITEMBTN_INFO);
|
|
|
|
if(stat_screen_mode == MODE_INVEN &&
|
|
prime_time() && pc == univ.cur_pc) {
|
|
|
|
// place give and drop and use
|
|
place_item_button(i,ITEMBTN_GIVE);
|
|
place_item_button(i,ITEMBTN_DROP);
|
|
if(item.can_use() && (item.rechargeable ? item.charges > 0 : true)) // place use if can
|
|
place_item_button(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 < LINES_IN_ITEM_WIN; 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[4] = {{24,0,36,30},{36,0,48,30},{48,0,60,30},{60,0,72,30}};
|
|
short val_to_place;
|
|
|
|
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_RECHARGE:
|
|
if(item.rechargeable && (shop_recharge_limit == 0 || item.charges < item.max_charges / shop_recharge_limit) && item.can_use()) {
|
|
item_area_button_active[position][ITEMBTN_SPEC] = true;
|
|
source_rect = button_sources[3];
|
|
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 = (*eEnchant(shop_identify_cost)).adjust_value(item.value);
|
|
}
|
|
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]) {
|
|
sf::Texture& invenbtn_gworld = *ResMgr::graphics.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];
|
|
std::shared_ptr<const sf::Texture> src_gw;
|
|
if(graphic >= 10000) {
|
|
graf_pos_ref(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) {
|
|
graf_pos_ref(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::graphics.get("tinyobj"), from_rect, item_stats_gworld(), to_rect, sf::BlendAlpha);
|
|
}
|
|
|
|
// name, use, give, drop, info, sell/id
|
|
void place_item_button(short which_slot,eItemButton button_type, eItemButton button_pos) {
|
|
if(button_pos == MAX_eItemButton){
|
|
button_pos = button_type;
|
|
}
|
|
|
|
rectangle from_rect = {0,0,18,18},to_rect;
|
|
|
|
sf::Texture& invenbtn_gworld = *ResMgr::graphics.get("invenbtns");
|
|
item_area_button_active[which_slot][button_pos] = true;
|
|
rect_draw_some_item(invenbtn_gworld, item_buttons_from[button_type - 2], item_stats_gworld(), item_buttons[which_slot][button_pos], sf::BlendAlpha);
|
|
}
|
|
|
|
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;
|
|
|
|
sf::Texture& invenbtn_gworld = *ResMgr::graphics.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;
|
|
std::shared_ptr<const sf::Texture> from_gw;
|
|
if(pic >= 1000) {
|
|
bool isParty = pic >= 10000;
|
|
pic_num_t need_pic = pic % 1000;
|
|
graf_pos_ref(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::graphics.get("monst" + std::to_string(1 + which_sheet));
|
|
} else {
|
|
pc_from_rect = calc_rect(2 * (pic / 8), pic % 8);
|
|
from_gw = &ResMgr::graphics.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 an extra pixel to make it line up, because it has an ascender in this font
|
|
to_rect.offset(-width - 5, i == 5 ? 3 : 2);
|
|
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);
|
|
|
|
// Don't draw the Jobs button if the scenario has none
|
|
item_bottom_button_active[7] = false;
|
|
if(!univ.scenario.quests.empty()){
|
|
item_bottom_button_active[7] = true;
|
|
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, bool record) {
|
|
if(pc < 0) pc = 0;
|
|
if(pc > 5) pc = 5;
|
|
set_stat_window(eItemWinMode(pc), record);
|
|
}
|
|
|
|
void set_stat_window(eItemWinMode new_stat, bool record) {
|
|
if(record && recording){
|
|
record_action("set_stat_window", boost::lexical_cast<std::string>((int)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(LINES_IN_ITEM_WIN);
|
|
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 - LINES_IN_ITEM_WIN);
|
|
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 - LINES_IN_ITEM_WIN);
|
|
item_sbar->setMaximum(array_pos);
|
|
break;
|
|
default:
|
|
item_sbar->setMaximum(cPlayer::INVENTORY_SIZE - LINES_IN_ITEM_WIN);
|
|
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(), rectangle(pc_stats_gworld()), mainPtr(), win_to_rects[WINRECT_PCSTATS], x);
|
|
rect_draw_some_item(item_stats_gworld(), rectangle(item_stats_gworld()), mainPtr(), win_to_rects[WINRECT_INVEN], x);
|
|
item_sbar->draw();
|
|
if(mode != 1){
|
|
rect_draw_some_item(text_area_gworld(), rectangle(text_area_gworld()), mainPtr(), win_to_rects[WINRECT_TRANSCRIPT], x);
|
|
text_sbar->draw();
|
|
}
|
|
}
|
|
|
|
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
|
|
sf::Texture& status_gworld = *ResMgr::graphics.get("staticons");
|
|
for(auto next : univ.party[pc].status) {
|
|
short placedIcon = -1;
|
|
const auto& statInfo = *next.first;
|
|
if(statInfo.special && next.second >= statInfo.special->lo && next.second <= statInfo.special->hi) {
|
|
placedIcon = statInfo.special->icon;
|
|
}
|
|
else if(next.second > 0) placedIcon = statInfo.icon;
|
|
else if(next.second < 0) placedIcon = statInfo.negIcon;
|
|
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() {
|
|
if(recording){
|
|
record_action("print_party_stats", "");
|
|
}
|
|
if(overall_mode == MODE_STARTUP) return;
|
|
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].is_alive()) && (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);
|
|
|
|
}
|
|
}
|
|
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);
|
|
j = 7;
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
location lSpace=global_to_local(space);
|
|
if(univ.out->roads[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->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(msg);
|
|
}
|
|
}
|
|
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);
|
|
location sector = {univ.party.outdoor_corner.x + univ.party.i_w_c.x, univ.party.outdoor_corner.y + univ.party.i_w_c.y};
|
|
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) && (sector == univ.party.boats[i].sector))
|
|
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;
|
|
}
|
|
|
|
static void print_monster_count(std::string m_name, short num){
|
|
if(num > 0){
|
|
std::ostringstream sout;
|
|
sout << " ";
|
|
if(num > 1){
|
|
sout << num << " x ";
|
|
}
|
|
sout << m_name;
|
|
add_string_to_buf(sout.str());
|
|
}
|
|
}
|
|
|
|
void print_encounter_monsters(cOutdoors::cWandering encounter, short* nums, std::map<std::string,short> alive) {
|
|
std::string m_name;
|
|
bool remaining_specified = !alive.empty();
|
|
// Still follow the predefined ordering when giving an updated total, and leave summons to the end.
|
|
for(short i = 0; i < 7; i++)
|
|
if(encounter.monst[i] != 0) {
|
|
m_name = get_m_name(encounter.monst[i]);
|
|
short num = 0;
|
|
// Combat is just starting, so print the predefined amount
|
|
if(!remaining_specified){
|
|
num = nums[i];
|
|
}
|
|
// Combat has started, so need to check how many are still alive
|
|
else{
|
|
auto iter = alive.find(m_name);
|
|
if(iter != alive.end()){
|
|
num = iter->second;
|
|
alive.erase(iter);
|
|
}
|
|
}
|
|
print_monster_count(m_name, num);
|
|
}
|
|
// Monsters still in the alive map must have been summoned
|
|
if(!alive.empty()){
|
|
for (auto iter = alive.begin(); iter != alive.end(); ++iter){
|
|
print_monster_count(iter->first, iter->second);
|
|
}
|
|
}
|
|
}
|
|
|
|
void notify_out_combat_began(cOutdoors::cWandering encounter,short *nums) {
|
|
add_string_to_buf("COMBAT!");
|
|
print_encounter_monsters(encounter, nums);
|
|
}
|
|
|
|
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) {
|
|
std::string store_name = "Pit";
|
|
|
|
if((num == 90) && ((is_out()) || (is_town()) || ((is_combat()) && (which_combat_type == 1))));
|
|
else {
|
|
store_name = univ.scenario.ter_types[num].name;
|
|
}
|
|
return store_name;
|
|
}
|
|
|
|
void print_monst_name(mon_num_t m_type) {
|
|
std::string msg = get_m_name(m_type) + ':';
|
|
add_string_to_buf(msg);
|
|
}
|
|
|
|
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());
|
|
}
|
|
|
|
// 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(sout.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
|
|
size_t starting_indent = str.find_first_not_of(' ');
|
|
if(starting_indent != std::string::npos)
|
|
add_string_to_buf(str, starting_indent + 2);
|
|
}
|
|
|
|
void add_string_to_buf(std::string str, unsigned short indent) {
|
|
if(overall_mode == MODE_STARTUP)
|
|
return;
|
|
if(str == "") return;
|
|
if(str.find_last_not_of(' ') == std::string::npos)
|
|
return;
|
|
|
|
if(indent > 20) indent = 20;
|
|
|
|
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_gworld().getSize().x - 5;
|
|
}
|
|
|
|
std::string wrapped_str = str;
|
|
int line_count = 1 + std::count(str.begin(), str.end(), '\n');
|
|
int wrapped_idx = 0;
|
|
std::string space(indent, ' ');
|
|
while(string_length(wrapped_str.substr(wrapped_idx, wrapped_str.find_last_not_of(' ')), buf_style) >= width) {
|
|
size_t split = wrapped_str.find_last_of(' ');
|
|
while(string_length(wrapped_str.substr(wrapped_idx, split), buf_style) >= width)
|
|
split = wrapped_str.find_last_of(' ', split - 1);
|
|
|
|
wrapped_str[split] = '\n';
|
|
wrapped_str.insert(split + 1, space);
|
|
|
|
wrapped_idx = split + 1 + indent;
|
|
line_count += 1;
|
|
}
|
|
|
|
// Check if this is a duplicate message
|
|
int prev_pointer = buf_pointer - 1;
|
|
if(prev_pointer < 0) prev_pointer = TEXT_BUF_LEN - 1;
|
|
|
|
if(wrapped_str == text_buffer[prev_pointer].message){
|
|
text_buffer[prev_pointer].duplicate_count++;
|
|
std::string test_fit = wrapped_str + " (x" + std::to_string(text_buffer[prev_pointer].duplicate_count) + ")";
|
|
if(string_length(test_fit, buf_style) >= width){
|
|
text_buffer[prev_pointer].line_count = line_count + 1;
|
|
// The duplicate count in parenthesis is on its own line
|
|
text_buffer[prev_pointer].duplicate_overflow = true;
|
|
}
|
|
}else{
|
|
text_buffer[buf_pointer].message = wrapped_str;
|
|
text_buffer[buf_pointer].line_count = line_count;
|
|
text_buffer[buf_pointer].duplicate_count = 1;
|
|
text_buffer[buf_pointer].duplicate_overflow = false;
|
|
if(buf_pointer == (TEXT_BUF_LEN - 1))
|
|
buf_pointer = 0;
|
|
else buf_pointer++;
|
|
}
|
|
|
|
text_sbar->setPosition(58); // TODO: This seems oddly specific
|
|
}
|
|
|
|
void add_caster_needs_to_buf(std::string needs_what, unsigned short pre_indent, unsigned short indent) {
|
|
std::ostringstream sout;
|
|
for(int i = 0; i < pre_indent; ++i){
|
|
sout << ' ';
|
|
}
|
|
extern short pc_casting;
|
|
sout << univ.party[pc_casting].name << " needs " << needs_what << ".";
|
|
add_string_to_buf(sout.str(), indent);
|
|
}
|
|
|
|
void init_buf() {
|
|
for(short i = 0; i < TEXT_BUF_LEN; i++){
|
|
// Buffer messages are no longer forced down to 50 chars each. In an effort to keep memory usage predictable,
|
|
// I've set an expected capacity of 100 per message.
|
|
text_buffer[i].message.reserve(100);
|
|
text_buffer[i].line_count = 1;
|
|
text_buffer[i].duplicate_count = 1;
|
|
text_buffer[i].duplicate_overflow = false;
|
|
}
|
|
}
|
|
|
|
void print_buf () {
|
|
rectangle store_text_rect,dest_rect,erase_rect = {2,2,136,255};
|
|
|
|
text_area_gworld().setActive(false);
|
|
clear_scale_aware_text(text_area_gworld());
|
|
|
|
// First clean up gworld with pretty patterns
|
|
tileImage(text_area_gworld(), erase_rect,bg[6]);
|
|
|
|
// Don't draw wrapped scale-aware text outside of the viewport
|
|
clip_rect(text_area_gworld(), erase_rect);
|
|
|
|
// Handling scrolling is a lot more confusing now.
|
|
// Think of line 0 as the LAST line of the most recently printed message. We count up from that accounting for
|
|
// multiple lines being stored in each message, to find the BOTTOM-MOST message to print first.
|
|
long lines_clipped_below = 58 - text_sbar->getPosition();
|
|
int message_idx = buf_pointer - 1;
|
|
if(message_idx < 0)
|
|
message_idx = TEXT_BUF_LEN + message_idx;
|
|
int line = 0;
|
|
while(line + text_buffer[message_idx].line_count <= lines_clipped_below){
|
|
line += text_buffer[message_idx].line_count;
|
|
message_idx--;
|
|
if(message_idx < 0)
|
|
message_idx = TEXT_BUF_LEN + message_idx;
|
|
}
|
|
|
|
// Shift the text down this many lines, counting on clip_rect to hide the overflow:
|
|
int line_offset = lines_clipped_below - line;
|
|
int num_lines_total = 0;
|
|
int num_lines_visible = -line_offset;
|
|
|
|
location moveTo;
|
|
TextStyle line_style;
|
|
line_style.font = FONT_PLAIN;
|
|
line_style.colour = Colours::BLACK;
|
|
line_style.pointSize = 12;
|
|
|
|
while(num_lines_visible < LINES_IN_TEXT_WIN){
|
|
std::string message = text_buffer[message_idx].message;
|
|
int indent = message.find_first_not_of(' ');
|
|
if(indent != std::string::npos){
|
|
if(text_buffer[message_idx].duplicate_count > 1){
|
|
if(text_buffer[message_idx].duplicate_overflow){
|
|
message += "\n" + std::string(indent, ' ');
|
|
}else{
|
|
message += " ";
|
|
}
|
|
message += "(x" + std::to_string(text_buffer[message_idx].duplicate_count) + ")";
|
|
}
|
|
|
|
moveTo = location(4, 1 + 12 * (LINES_IN_TEXT_WIN + line_offset - num_lines_total - text_buffer[message_idx].line_count));
|
|
sf::Text text;
|
|
line_style.applyTo(text, get_ui_scale());
|
|
// A spacing factor of 1.0 within multiline messages doesn't actually line up with other single buffer lines
|
|
text.setLineSpacing(0.85);
|
|
text.setString(message);
|
|
text.setPosition(moveTo);
|
|
draw_scale_aware_text(text_area_gworld(), text);
|
|
}
|
|
|
|
num_lines_visible += text_buffer[message_idx].line_count;
|
|
num_lines_total += text_buffer[message_idx].line_count;
|
|
message_idx--;
|
|
if(message_idx < 0)
|
|
message_idx = TEXT_BUF_LEN + message_idx;
|
|
}
|
|
|
|
undo_clip(text_area_gworld());
|
|
text_area_gworld().setActive();
|
|
text_area_gworld().display();
|
|
}
|
|
|
|
void restore_mode() {
|
|
overall_mode = store_mode;
|
|
}
|
|
|
|
/* 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 sf::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;
|
|
}
|
|
|
|
std::string get_location(cUniverse* specific_univ) {
|
|
// This function is used to determine text bar text, which may be intended to be blank
|
|
// even if the party is technically outdoors, depending on the active game mode.
|
|
// I'm just trying to keep the old behavior the same.
|
|
bool outdoors = is_out();
|
|
bool town = is_town();
|
|
// For checking a save file's location, it can only be one or the other.
|
|
if(specific_univ != nullptr){
|
|
outdoors = specific_univ->party.town_num >= 200;
|
|
town = !outdoors;
|
|
}else{
|
|
specific_univ = &univ;
|
|
}
|
|
|
|
std::string loc_str = "";
|
|
|
|
location loc = outdoors ? global_to_local(specific_univ->party.out_loc) : specific_univ->party.town_loc;
|
|
if(outdoors) {
|
|
loc_str = specific_univ->out->name;
|
|
for(short i = 0; i < specific_univ->out->area_desc.size(); i++)
|
|
if(loc.in(specific_univ->out->area_desc[i])) {
|
|
loc_str = specific_univ->out->area_desc[i].descr;
|
|
}
|
|
}
|
|
if(town){
|
|
loc_str = specific_univ->town->name;
|
|
for(short i = 0; i < specific_univ->town->area_desc.size(); i++)
|
|
if(loc.in(specific_univ->town->area_desc[i])) {
|
|
loc_str = specific_univ->town->area_desc[i].descr;
|
|
}
|
|
}
|
|
return loc_str;
|
|
}
|