Added a new gamebrowser widget.
This should work as expected but it doesn't support sorting right now. Also the map_info isn't field of the games is not filled and it may not be drawn at the correct position at all
This commit is contained in:
parent
cb71a570b2
commit
93325b25d4
2 changed files with 365 additions and 108 deletions
|
@ -34,6 +34,307 @@ std::string games_menu_heading()
|
|||
}
|
||||
|
||||
namespace mp {
|
||||
gamebrowser::gamebrowser(CVideo& video) : scrollarea(video),
|
||||
gold_icon_locator_("misc/gold.png"),
|
||||
xp_icon_locator_("misc/units.png"),
|
||||
vision_icon_locator_("misc/invisible.png"),
|
||||
observer_icon_locator_("misc/eye.png"), header_height_(20),
|
||||
item_height_(100), margin_(5), h_padding_(5),
|
||||
v_padding_(5), selected_(0), visible_range_(std::pair<size_t,size_t>(0,0)),
|
||||
double_clicked_(false), ignore_next_doubleclick_(false), last_was_doubleclick_(false)
|
||||
{
|
||||
}
|
||||
|
||||
void gamebrowser::set_inner_location(const SDL_Rect& rect)
|
||||
{
|
||||
set_full_size(games_.size());
|
||||
set_shown_size(rect.h / item_height_);
|
||||
bg_register(rect);
|
||||
scroll(get_position());
|
||||
}
|
||||
|
||||
void gamebrowser::scroll(int pos)
|
||||
{
|
||||
if(pos >= 0 && pos < games_.size()) {
|
||||
visible_range_.first = pos;
|
||||
visible_range_.second = minimum<size_t>(pos + inner_location().h / item_height_, games_.size());
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Rect gamebrowser::get_item_rect(size_t index) const {
|
||||
if(index < visible_range_.first || index > visible_range_.second) {
|
||||
const SDL_Rect res = { 0, 0, 0, 0 };
|
||||
return res;
|
||||
}
|
||||
const SDL_Rect& loc = inner_location();
|
||||
const SDL_Rect res = { loc.x, loc.y + (index - visible_range_.first) * item_height_, loc.w, item_height_ };
|
||||
return res;
|
||||
}
|
||||
|
||||
void gamebrowser::draw()
|
||||
{
|
||||
if(hidden())
|
||||
return;
|
||||
if(dirty()) {
|
||||
bg_restore();
|
||||
util::scoped_ptr<clip_rect_setter> clipper(NULL);
|
||||
if(clip_rect())
|
||||
clipper.assign(new clip_rect_setter(video().getSurface(), *clip_rect()));
|
||||
draw_contents();
|
||||
update_rect(location());
|
||||
set_dirty(false);
|
||||
}
|
||||
}
|
||||
|
||||
void gamebrowser::draw_contents() const
|
||||
{
|
||||
if(!games_.empty()) {
|
||||
for(size_t i = visible_range_.first; i != visible_range_.second; ++i) {
|
||||
draw_item(i);
|
||||
}
|
||||
} else {
|
||||
const SDL_Rect rect = inner_location();
|
||||
font::draw_text(&video(), rect, font::SIZE_NORMAL, font::NORMAL_COLOUR, _("<no games open>"), rect.x + margin_, rect.y + margin_);
|
||||
}
|
||||
}
|
||||
|
||||
void gamebrowser::draw_item(size_t index) const {
|
||||
const game_item& game = games_[index];
|
||||
SDL_Rect item_rect = get_item_rect(index);
|
||||
int xpos = item_rect.x + margin_;
|
||||
int ypos = item_rect.y + margin_;
|
||||
|
||||
bg_restore(item_rect);
|
||||
draw_solid_tinted_rectangle(item_rect.x, item_rect.y, item_rect.w, item_rect.h, 0, 0, 0, 0.2, video().getSurface());
|
||||
|
||||
// draw mini map
|
||||
video().blit_surface(xpos, ypos, game.mini_map);
|
||||
|
||||
xpos += item_height_ + margin_;
|
||||
|
||||
const surface name_surf(font::get_rendered_text(font::make_text_ellipsis(game.name, font::SIZE_NORMAL, item_rect.w - xpos - margin_), font::SIZE_PLUS, game.vacant_slots > 0 ? font::GOOD_COLOUR : game.observers ? font::NORMAL_COLOUR : font::BAD_COLOUR));
|
||||
video().blit_surface(xpos, ypos, name_surf);
|
||||
|
||||
ypos += v_padding_;
|
||||
|
||||
// draw map info
|
||||
const surface map_info_surf(font::get_rendered_text(font::make_text_ellipsis(game.map_info, font::SIZE_NORMAL, item_rect.w - xpos - margin_), font::SIZE_PLUS, font::GOOD_COLOUR));
|
||||
video().blit_surface(xpos, ypos, map_info_surf);
|
||||
|
||||
|
||||
// draw gold icon
|
||||
const surface gold_icon(image::get_image(gold_icon_locator_, image::UNSCALED));
|
||||
ypos = item_rect.y + item_rect.h - margin_ - gold_icon->h;
|
||||
|
||||
video().blit_surface(xpos, ypos, gold_icon);
|
||||
|
||||
xpos += gold_icon->w + h_padding_;
|
||||
|
||||
// draw gold text
|
||||
const surface gold_text(font::get_rendered_text(game.gold, font::SIZE_NORMAL, font::NORMAL_COLOUR));
|
||||
ypos -= abs(gold_icon->h - gold_text->h) / 2;
|
||||
video().blit_surface(xpos, ypos, gold_text);
|
||||
|
||||
xpos += gold_text->w + 2 * h_padding_;
|
||||
|
||||
// draw xp icon
|
||||
const surface xp_icon(image::get_image(xp_icon_locator_, image::UNSCALED));
|
||||
ypos = item_rect.y + item_rect.h - margin_ - xp_icon->h;
|
||||
video().blit_surface(xpos, ypos, xp_icon);
|
||||
|
||||
xpos += xp_icon->w + h_padding_;
|
||||
|
||||
// draw xp text
|
||||
const surface xp_text(font::get_rendered_text(game.xp, font::SIZE_NORMAL, font::NORMAL_COLOUR));
|
||||
ypos -= abs(xp_icon->h - xp_text->h) / 2;
|
||||
video().blit_surface(xpos, ypos, xp_text);
|
||||
|
||||
xpos += xp_text->w + 2 * h_padding_;
|
||||
|
||||
// draw vision icon
|
||||
const surface vision_icon(image::get_image(vision_icon_locator_, image::UNSCALED));
|
||||
video().blit_surface(xpos, ypos, vision_icon);
|
||||
|
||||
xpos += vision_icon->w + h_padding_;
|
||||
|
||||
const surface status_text(font::get_rendered_text(game.status, font::SIZE_NORMAL, game.vacant_slots > 0 ? font::GOOD_COLOUR : font::NORMAL_COLOUR));
|
||||
const surface vision_text(font::get_rendered_text(font::make_text_ellipsis(game.vision, font::SIZE_NORMAL, maximum<int>((item_rect.x + item_rect.w - margin_ - status_text->w - 2 * h_padding_) - xpos, 0)),font::SIZE_NORMAL, font::NORMAL_COLOUR));
|
||||
// draw vision text
|
||||
video().blit_surface(xpos, ypos, vision_text);
|
||||
|
||||
// draw status text
|
||||
xpos = item_rect.x + item_rect.w - margin_ - status_text->w;
|
||||
video().blit_surface(xpos, ypos, status_text);
|
||||
|
||||
if(selected_ == index)
|
||||
draw_solid_tinted_rectangle(item_rect.x, item_rect.y, item_rect.w, item_rect.h, 153, 0, 0, 0.3, video().getSurface());
|
||||
}
|
||||
|
||||
void gamebrowser::handle_event(const SDL_Event& event)
|
||||
{
|
||||
scrollarea::handle_event(event);
|
||||
if(event.type == SDL_KEYDOWN) {
|
||||
if(focus() && !games_.empty()) {
|
||||
switch(event.key.keysym.sym) {
|
||||
case SDLK_UP:
|
||||
if(selected_ > 0) {
|
||||
--selected_;
|
||||
adjust_position(selected_);
|
||||
set_dirty();
|
||||
}
|
||||
break;
|
||||
case SDLK_DOWN:
|
||||
if(selected_ < games_.size() - 1) {
|
||||
++selected_;
|
||||
adjust_position(selected_);
|
||||
set_dirty();
|
||||
}
|
||||
break;
|
||||
case SDLK_PAGEUP:
|
||||
{
|
||||
const long items_on_screen = visible_range_.second - visible_range_.first;
|
||||
selected_ = static_cast<size_t>(maximum<long>(static_cast<long>(selected_) - items_on_screen, 0));
|
||||
adjust_position(selected_);
|
||||
set_dirty();
|
||||
}
|
||||
break;
|
||||
case SDLK_PAGEDOWN:
|
||||
{
|
||||
const size_t items_on_screen = visible_range_.second - visible_range_.first;
|
||||
selected_ = minimum<size_t>(selected_ + items_on_screen, games_.size() - 1);
|
||||
adjust_position(selected_);
|
||||
set_dirty();
|
||||
}
|
||||
break;
|
||||
case SDLK_HOME:
|
||||
selected_ = 0;
|
||||
adjust_position(selected_);
|
||||
set_dirty();
|
||||
break;
|
||||
case SDLK_END:
|
||||
selected_ = games_.size() - 1;
|
||||
adjust_position(selected_);
|
||||
set_dirty();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if(event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT || event.type == DOUBLE_CLICK_EVENT) {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
if(event.type == SDL_MOUSEBUTTONDOWN) {
|
||||
x = event.button.x;
|
||||
y = event.button.y;
|
||||
} else {
|
||||
x = (long)event.user.data1;
|
||||
y = (long)event.user.data2;
|
||||
}
|
||||
const SDL_Rect& loc = inner_location();
|
||||
|
||||
if(!games_.empty() && point_in_rect(x, y, loc)) {
|
||||
for(size_t i = visible_range_.first; i != visible_range_.second; ++i) {
|
||||
const SDL_Rect& item_rect = get_item_rect(i);
|
||||
|
||||
if(point_in_rect(x, y, item_rect)) {
|
||||
set_focus(true);
|
||||
set_dirty();
|
||||
selected_ = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(event.type == DOUBLE_CLICK_EVENT) {
|
||||
if (ignore_next_doubleclick_) {
|
||||
ignore_next_doubleclick_ = false;
|
||||
} else if(selection_is_joinable() || selection_is_observable()) {
|
||||
double_clicked_ = true;
|
||||
last_was_doubleclick_ = true;
|
||||
}
|
||||
} else if (last_was_doubleclick_) {
|
||||
// If we have a double click as the next event, it means
|
||||
// this double click was generated from a click that
|
||||
// already has helped in generating a double click.
|
||||
SDL_Event ev;
|
||||
SDL_PeepEvents(&ev, 1, SDL_PEEKEVENT,
|
||||
SDL_EVENTMASK(DOUBLE_CLICK_EVENT));
|
||||
if (ev.type == DOUBLE_CLICK_EVENT) {
|
||||
ignore_next_doubleclick_ = true;
|
||||
}
|
||||
last_was_doubleclick_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void gamebrowser::set_game_items(const config& cfg, const config& game_config)
|
||||
{
|
||||
games_.clear();
|
||||
config::child_list games = cfg.get_children("game");
|
||||
config::child_iterator game;
|
||||
|
||||
for(game = games.begin(); game != games.end(); ++game) {
|
||||
games_.push_back(game_item());
|
||||
|
||||
games_.back().map_data = (**game)["map_data"];
|
||||
if(games_.back().map_data == "")
|
||||
games_.back().map_data = read_map((**game)["map"]);
|
||||
|
||||
if(games_.back().map_data != "") {
|
||||
try {
|
||||
gamemap map(game_config, games_.back().map_data);
|
||||
games_.back().mini_map = image::getMinimap(item_height_ - margin_, item_height_ - 2 * margin_, map, 0);
|
||||
} catch(gamemap::incorrect_format_exception &e) {
|
||||
std::cerr << "illegal map: " << e.msg_ << "\n";
|
||||
}
|
||||
}
|
||||
games_.back().name = (**game)["name"];
|
||||
const std::string& turn = (**game)["turn"];
|
||||
const std::string& slots = (**game)["slots"];
|
||||
games_.back().vacant_slots = lexical_cast_default<size_t>(slots, 0);
|
||||
if(turn != "")
|
||||
games_.back().status = _("Turn") + (" " + turn);
|
||||
else if(slots != "")
|
||||
games_.back().status = slots + " " + ngettext(_("Vacant Slot"), _("Vacant Slots"), games_.back().vacant_slots);
|
||||
|
||||
if((**game)["mp_use_map_settings"] == "yes") {
|
||||
games_.back().gold = _("Use map settings");
|
||||
games_.back().vision = _("Use map settings");
|
||||
games_.back().use_map_settings = true;
|
||||
} else {
|
||||
games_.back().use_map_settings = false;
|
||||
games_.back().gold = (**game)["mp_village_gold"];
|
||||
if((**game)["mp_fog"] == "yes") {
|
||||
games_.back().vision = _("Fog");
|
||||
games_.back().fog = true;
|
||||
if((**game)["mp_shroud"] == "yes") {
|
||||
games_.back().vision += "/";
|
||||
games_.back().vision += _("Shroud");
|
||||
games_.back().shroud = true;
|
||||
} else {
|
||||
games_.back().shroud = false;
|
||||
}
|
||||
} else if((**game)["mp_shroud"] == "yes") {
|
||||
games_.back().vision = _("Shroud");
|
||||
games_.back().fog = false;
|
||||
games_.back().shroud = true;
|
||||
} else {
|
||||
games_.back().vision = _("none");
|
||||
games_.back().fog = false;
|
||||
games_.back().shroud = false;
|
||||
}
|
||||
}
|
||||
games_.back().xp = (**game)["experience_modifier"] + "%";
|
||||
games_.back().observers = (**game)["observer"] != "no" ? true : false;
|
||||
}
|
||||
set_full_size(games_.size());
|
||||
set_shown_size(inner_location().h / item_height_);
|
||||
scroll(get_position());
|
||||
if(selected_ >= games_.size())
|
||||
selected_ = maximum<long>(static_cast<long>(games_.size()) - 1, 0);
|
||||
if(selected_ >= 0)
|
||||
adjust_position(selected_);
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
lobby::lobby_sorter::lobby_sorter(const config& cfg) : cfg_(cfg)
|
||||
{
|
||||
|
@ -109,7 +410,7 @@ lobby::lobby(display& disp, const config& cfg, chat& c, config& gamelist) :
|
|||
create_game_(disp.video(), _("Create Game")),
|
||||
quit_game_(disp.video(), _("Quit")),
|
||||
sorter_(gamelist),
|
||||
games_menu_(disp.video(), std::vector<std::string>(1,games_menu_heading()),false,-1,-1,&sorter_),
|
||||
games_menu_(disp.video()),
|
||||
current_game_(0)
|
||||
{
|
||||
game_config::debug = false;
|
||||
|
@ -151,126 +452,31 @@ void lobby::gamelist_updated(bool silent)
|
|||
// No gamelist yet. Do not update anything.
|
||||
return;
|
||||
}
|
||||
config::child_list games = list->get_children("game");
|
||||
config::child_iterator game;
|
||||
game_observers_.clear();
|
||||
game_vacant_slots_.clear();
|
||||
|
||||
for(game = games.begin(); game != games.end(); ++game) {
|
||||
|
||||
std::stringstream str;
|
||||
|
||||
std::string map_data = (**game)["map_data"];
|
||||
if(map_data == "") {
|
||||
map_data = read_map((**game)["map"]);
|
||||
}
|
||||
|
||||
if(map_data != "") {
|
||||
try {
|
||||
std::string& image_id = minimaps_[map_data];
|
||||
|
||||
if(image_id.empty()) {
|
||||
gamemap map(game_config(), map_data);
|
||||
const surface mini(image::getMinimap(100,100,map,0));
|
||||
|
||||
//generate a unique id to show the map as
|
||||
std::stringstream id;
|
||||
id << "addr " << mini.get();
|
||||
image_id = id.str();
|
||||
image::register_image(image_id, mini);
|
||||
}
|
||||
|
||||
str << "&" << image_id << COLUMN_SEPARATOR;
|
||||
|
||||
} catch(gamemap::incorrect_format_exception& e) {
|
||||
std::cerr << "illegal map: " << e.msg_ << "\n";
|
||||
}
|
||||
} else {
|
||||
str << "(" << _("Shroud") << ")" << COLUMN_SEPARATOR;
|
||||
}
|
||||
|
||||
std::string name = (**game)["name"];
|
||||
name.erase(std::remove_if(name.begin(),name.end(),font::is_format_char),name.end());
|
||||
|
||||
str << font::make_text_ellipsis(name, font::SIZE_NORMAL, xscale(300));
|
||||
|
||||
const std::string& turn = (**game)["turn"];
|
||||
const std::string& slots = (**game)["slots"];
|
||||
int nslots = lexical_cast_default<int>(slots, 0);
|
||||
|
||||
if(turn != "") {
|
||||
str << COLUMN_SEPARATOR << _("Turn") << " " << turn;
|
||||
} else if(slots != "") {
|
||||
str << COLUMN_SEPARATOR << slots << " " <<
|
||||
ngettext(_("Vacant Slot"), _("Vacant Slots"), nslots);
|
||||
}
|
||||
str << COLUMN_SEPARATOR << " " << (**game)["mp_village_gold"] << " "
|
||||
<< _("Gold") << " " << (**game)["experience_modifier"] << "% " << _("XP");
|
||||
if((**game)["mp_use_map_settings"] == "yes")
|
||||
str << " " << _("Use map settings");
|
||||
else if((**game)["mp_fog"] == "yes")
|
||||
str << " " << _("Fog");
|
||||
|
||||
game_strings.push_back(str.str());
|
||||
|
||||
game_vacant_slots_.push_back(slots != "" && slots != "0");
|
||||
game_observers_.push_back((**game)["observer"] != "no");
|
||||
}
|
||||
|
||||
if(game_strings.empty()) {
|
||||
game_strings.push_back(_("<no games open>"));
|
||||
}
|
||||
|
||||
//set the items, retaining the menu positioning if possible
|
||||
games_menu_.set_items(game_strings,true,true);
|
||||
|
||||
if(games_menu_.selection() >= 0 && games_menu_.selection() < int(game_vacant_slots_.size())) {
|
||||
wassert(game_vacant_slots_.size() == game_observers_.size());
|
||||
|
||||
join_game_.hide(!game_vacant_slots_[games_menu_.selection()]);
|
||||
observe_game_.hide(!game_observers_[games_menu_.selection()]);
|
||||
} else {
|
||||
join_game_.hide();
|
||||
observe_game_.hide();
|
||||
}
|
||||
|
||||
games_menu_.set_game_items(*list, game_config());
|
||||
join_game_.hide(!games_menu_.selection_is_joinable());
|
||||
observe_game_.hide(!games_menu_.selection_is_observable());
|
||||
}
|
||||
|
||||
void lobby::process_event()
|
||||
{
|
||||
games_menu_.process();
|
||||
join_game_.hide(!games_menu_.selection_is_joinable());
|
||||
observe_game_.hide(!games_menu_.selection_is_observable());
|
||||
|
||||
int selection = games_menu_.selection();
|
||||
const bool observe = observe_game_.pressed() || (games_menu_.selected() >= 0 && games_menu_.selection_is_observable() && !games_menu_.selection_is_joinable());
|
||||
const bool join = join_game_.pressed() || (games_menu_.selected() >= 0 && games_menu_.selection_is_joinable());
|
||||
|
||||
if(selection >= 0 && selection < int(game_vacant_slots_.size())) {
|
||||
if(selection != current_game_)
|
||||
current_game_ = selection;
|
||||
join_game_.hide(!game_vacant_slots_[selection]);
|
||||
observe_game_.hide(!game_observers_[selection]);
|
||||
} else {
|
||||
join_game_.hide();
|
||||
observe_game_.hide();
|
||||
}
|
||||
|
||||
const bool games_available = game_vacant_slots_.empty() == false;
|
||||
wassert(!games_available || selection >= 0 && selection < int(game_vacant_slots_.size()));
|
||||
const bool double_click = games_menu_.double_clicked();
|
||||
const bool observe = observe_game_.pressed() || games_available && !game_vacant_slots_[selection] && double_click && game_observers_[selection];
|
||||
|
||||
if(games_available && (observe || ((join_game_.pressed() || double_click) && game_vacant_slots_[selection]))) {
|
||||
const size_t index = size_t(games_menu_.selection());
|
||||
if(join || observe) {
|
||||
const config* game = gamelist().child("gamelist");
|
||||
if (game != NULL) {
|
||||
const config::const_child_itors i = game->child_range("game");
|
||||
wassert(index < size_t(i.second - i.first));
|
||||
const std::string& id = (**(i.first+index))["id"];
|
||||
const std::string& id = (**(i.first + games_menu_.selected()))["id"];
|
||||
|
||||
config response;
|
||||
config& join = response.add_child("join");
|
||||
join["id"] = id;
|
||||
network::send_data(response);
|
||||
|
||||
if (observe) {
|
||||
if(observe) {
|
||||
set_result(OBSERVE);
|
||||
} else {
|
||||
set_result(JOIN);
|
||||
|
|
|
@ -20,11 +20,62 @@
|
|||
#include "display.hpp"
|
||||
#include "multiplayer_ui.hpp"
|
||||
|
||||
#include "widgets/menu.hpp"
|
||||
#include "widgets/scrollarea.hpp"
|
||||
|
||||
// This module controls the multiplayer lobby. A section on the server which
|
||||
// allows players to chat, create games, and join games.
|
||||
namespace mp {
|
||||
class gamebrowser : public gui::scrollarea {
|
||||
public:
|
||||
struct game_item {
|
||||
surface mini_map;
|
||||
std::string map_data;
|
||||
std::string name;
|
||||
std::string map_info;
|
||||
std::string gold;
|
||||
std::string xp;
|
||||
std::string vision;
|
||||
std::string status;
|
||||
size_t vacant_slots;
|
||||
bool fog;
|
||||
bool shroud;
|
||||
bool observers;
|
||||
bool use_map_settings;
|
||||
};
|
||||
gamebrowser(CVideo& video);
|
||||
void scroll(int pos);
|
||||
void handle_event(const SDL_Event& event);
|
||||
void set_inner_location(const SDL_Rect& rect);
|
||||
void set_item_height(unsigned int height);
|
||||
void set_game_items(const config& cfg, const config& game_config);
|
||||
void draw();
|
||||
void draw_contents() const;
|
||||
void draw_item(size_t index) const;
|
||||
SDL_Rect get_item_rect(size_t index) const;
|
||||
bool empty() const { return games_.empty(); }
|
||||
bool selection_is_joinable() const { return empty() ? false : games_[selected_].vacant_slots; }
|
||||
bool selection_is_observable() const { return empty() ? false : games_[selected_].observers; }
|
||||
int selected() { return double_clicked_ && !empty() ? static_cast<int>(selected_) : -1; }
|
||||
protected:
|
||||
private:
|
||||
image::locator gold_icon_locator_;
|
||||
image::locator xp_icon_locator_;
|
||||
image::locator vision_icon_locator_;
|
||||
image::locator observer_icon_locator_;
|
||||
|
||||
unsigned int item_height_;
|
||||
int margin_;
|
||||
int h_padding_;
|
||||
int v_padding_;
|
||||
int header_height_;
|
||||
size_t selected_;
|
||||
std::pair<size_t, size_t> visible_range_;
|
||||
std::vector<game_item> games_;
|
||||
std::vector<size_t> redraw_items_;
|
||||
bool double_clicked_;
|
||||
bool ignore_next_doubleclick_;
|
||||
bool last_was_doubleclick_;
|
||||
};
|
||||
|
||||
class lobby : public ui
|
||||
{
|
||||
|
@ -62,7 +113,7 @@ private:
|
|||
gui::button quit_game_;
|
||||
|
||||
lobby_sorter sorter_;
|
||||
gui::menu games_menu_;
|
||||
gamebrowser games_menu_;
|
||||
int current_game_;
|
||||
|
||||
std::map<std::string,std::string> minimaps_;
|
||||
|
|
Loading…
Add table
Reference in a new issue