Freeciv21
Develop your civilization from humble roots to a global empire
page_pregame.cpp
Go to the documentation of this file.
1 /*
2  ____ Copyright (c) 1996-2023 Freeciv21 and Freeciv
3  / \__ contributors. This file is part of Freeciv21.
4 |\ / @ \ Freeciv21 is free software: you can redistribute it
5 \ \_______| \ .:|> and/or modify it under the terms of the GNU
6  \ ##| | \__/ General Public License as published by the Free
7  | ####\__/ \ Software Foundation, either version 3 of the License,
8  / / ## \| or (at your option) any later version.
9  / /__________\ \ You should have received a copy of the
10  L_JJ \__JJ GNU General Public License along with Freeciv21.
11  If not, see https://www.gnu.org/licenses/.
12  */
13 
14 #include "page_pregame.h"
15 // Qt
16 #include <QPainter>
17 #include <QScrollBar>
18 // utility
19 #include "fcintl.h"
20 // common
21 #include "chatline_common.h"
22 #include "colors_common.h"
23 #include "connectdlg_common.h"
24 #include "game.h"
25 // client
26 #include "chatline.h"
27 #include "client_main.h"
28 #include "dialogs.h"
29 #include "fc_client.h"
30 #include "icons.h"
31 #include "pregameoptions.h"
32 #include "tileset/sprite.h"
33 
34 page_pregame::page_pregame(QWidget *parent, fc_client *gui) : QWidget(parent)
35 {
36  king = gui;
37  ui.setupUi(this);
38 
39  QStringList player_widget_list;
40 
41  ui.chat_line->setProperty("doomchat", true);
42 
43  player_widget_list << _("Name") << _("Ready") << Q_("?player:Leader")
44  << _("Flag") << _("Border") << _("Nation") << _("Team")
45  << _("Host");
46  ui.start_players_tree->setColumnCount(player_widget_list.count());
47  ui.start_players_tree->setHeaderLabels(player_widget_list);
48  connect(ui.start_players_tree, &QWidget::customContextMenuRequested, this,
50  ui.bdisc->setText(_("Disconnect"));
51  ui.bdisc->setIcon(style()->standardPixmap(QStyle::SP_DialogCancelButton));
52  connect(ui.bdisc, &QAbstractButton::clicked, gui,
54  ui.bops->setText(_("Observe"));
55  ui.bops->setIcon(
56  fcIcons::instance()->getIcon(QStringLiteral("meeting-observer")));
57  connect(ui.bops, &QAbstractButton::clicked, this,
59  ui.bstart->setText(_("Start"));
60  ui.bstart->setIcon(style()->standardPixmap(QStyle::SP_DialogOkButton));
61  connect(ui.bstart, &QAbstractButton::clicked, this,
63  ui.pre_vote->hide();
65  setLayout(ui.gridLayout);
66 }
67 
68 page_pregame::~page_pregame() = default;
69 
70 void page_pregame::set_rulesets(int num_rulesets, QStringList rulesets)
71 {
72  ui.pr_options->set_rulesets(num_rulesets, rulesets);
73 }
74 
75 void page_pregame::update_vote() { ui.pre_vote->update_vote(); }
81 {
82  int conn_num, i;
83  QVariant qvar, qvar2;
84  bool is_ready;
85  QString host, nation, leader, team, str;
86  QTreeWidgetItem *item;
87  QTreeWidgetItem *item_r;
88  QList<QTreeWidgetItem *> items;
89  QList<QTreeWidgetItem *> recursed_items;
90  QTreeWidgetItem *player_item;
91  QTreeWidgetItem *global_item;
92  QTreeWidgetItem *detach_item;
93  int conn_id;
94  conn_num = conn_list_size(game.est_connections);
95 
96  if (conn_num == 0) {
97  return;
98  }
99 
100  ui.start_players_tree->clear();
101  qvar2 = 0;
102 
103  player_item = new QTreeWidgetItem();
104  player_item->setText(0, Q_("?header:Players"));
105  player_item->setData(0, Qt::UserRole, qvar2);
106 
107  i = 0;
108  players_iterate(pplayer) { i++; }
110  ui.pr_options->set_aifill(i);
115  players_iterate(pplayer)
116  {
117  host = QLatin1String("");
118  if (!player_has_flag(pplayer, PLRF_SCENARIO_RESERVED)) {
119  conn_id = -1;
120  conn_list_iterate(pplayer->connections, pconn)
121  {
122  if (pconn->playing == pplayer && !pconn->observer) {
123  conn_id = pconn->id;
124  host = pconn->addr;
125  break;
126  }
127  }
129  if (is_barbarian(pplayer)) {
130  continue;
131  }
132  if (is_ai(pplayer)) {
133  is_ready = true;
134  } else {
135  is_ready = pplayer->is_ready;
136  }
137 
138  if (pplayer->nation == NO_NATION_SELECTED) {
139  nation = _("Random");
140 
141  if (pplayer->was_created) {
142  leader = player_name(pplayer);
143  } else {
144  leader = QLatin1String("");
145  }
146  } else {
147  nation = nation_adjective_for_player(pplayer);
148  leader = player_name(pplayer);
149  }
150 
151  if (pplayer->team) {
152  team = team_name_translation(pplayer->team);
153  } else {
154  team = QLatin1String("");
155  }
156 
157  item = new QTreeWidgetItem();
158  for (int col = 0; col < 8; col++) {
159  switch (col) {
160  case 0:
161  str = pplayer->username;
162 
163  if (is_ai(pplayer)) {
164  str =
165  str + " <"
166  + (ai_level_translated_name(pplayer->ai_common.skill_level))
167  + ">";
168  item->setIcon(
169  col, fcIcons::instance()->getIcon(QStringLiteral("ai")));
170  } else {
171  item->setIcon(
172  col, fcIcons::instance()->getIcon(QStringLiteral("human")));
173  }
174 
175  item->setText(col, str);
176  qvar = QVariant::fromValue((void *) pplayer);
177  qvar2 = 1;
178  item->setData(0, Qt::UserRole, qvar2);
179  item->setData(1, Qt::UserRole, qvar);
180  break;
181  case 1:
182  if (is_ready) {
183  item->setText(col, _("Yes"));
184  } else {
185  item->setText(col, _("No"));
186  }
187  break;
188  case 2:
189  item->setText(col, leader);
190  break;
191  case 3: {
192  if (!pplayer->nation) {
193  break;
194  }
195  auto pixmap = get_nation_flag_sprite(tileset, pplayer->nation);
196  item->setData(col, Qt::DecorationRole, *pixmap);
197  } break;
198  case 4: {
199  if (!player_has_color(tileset, pplayer)) {
200  break;
201  }
202  auto pixmap = std::make_unique<QPixmap>(
203  ui.start_players_tree->header()->sectionSizeHint(col), 16);
204  pixmap->fill(Qt::transparent);
205 
206  QPainter p;
207  p.begin(pixmap.get());
208  p.fillRect(pixmap->width() / 2 - 8, 0, 16, 16, Qt::black);
209  p.fillRect(pixmap->width() / 2 - 7, 1, 14, 14,
210  get_player_color(tileset, pplayer));
211  p.end();
212  item->setData(col, Qt::DecorationRole, *pixmap);
213  } break;
214  case 5:
215  item->setText(col, nation);
216  break;
217  case 6:
218  item->setText(col, team);
219  break;
220  case 7:
221  item->setText(col, host);
222  break;
223  default:
224  break;
225  }
226  }
227 
231  recursed_items.clear();
232  conn_list_iterate(pplayer->connections, pconn)
233  {
234  if (pconn->id == conn_id) {
235  continue;
236  }
237  item_r = new QTreeWidgetItem();
238  item_r->setText(0, pconn->username);
239  item_r->setText(5, _("Observer"));
240  item_r->setText(7, pconn->addr);
241  recursed_items.append(item_r);
242  item->addChildren(recursed_items);
243  }
245  items.append(item);
246  }
247  }
249 
250  player_item->addChildren(items);
251  ui.start_players_tree->insertTopLevelItem(0, player_item);
252 
256  items.clear();
257  global_item = new QTreeWidgetItem();
258  global_item->setText(0, _("Global observers"));
259  qvar2 = 0;
260  global_item->setData(0, Qt::UserRole, qvar2);
261 
263  {
264  if (nullptr != pconn->playing || !pconn->observer) {
265  continue;
266  }
267  item = new QTreeWidgetItem();
268  for (int col = 0; col < 8; col++) {
269  switch (col) {
270  case 0:
271  item->setText(col, pconn->username);
272  break;
273  case 5:
274  item->setText(col, _("Observer"));
275  break;
276  case 7:
277  item->setText(col, pconn->addr);
278  break;
279  default:
280  break;
281  }
282  items.append(item);
283  }
284  }
286 
287  global_item->addChildren(items);
288  ui.start_players_tree->insertTopLevelItem(1, global_item);
289  items.clear();
290 
294  detach_item = new QTreeWidgetItem();
295  detach_item->setText(0, _("Detached"));
296  qvar2 = 0;
297  detach_item->setData(0, Qt::UserRole, qvar2);
298 
300  {
301  if (nullptr != pconn->playing || pconn->observer) {
302  continue;
303  }
304  item = new QTreeWidgetItem();
305  item->setText(0, pconn->username);
306  item->setText(7, pconn->addr);
307  items.append(item);
308  }
310 
311  detach_item->addChildren(items);
312  ui.start_players_tree->insertTopLevelItem(2, detach_item);
313  ui.start_players_tree->header()->setSectionResizeMode(
314  QHeaderView::ResizeToContents);
315  ui.start_players_tree->expandAll();
316  update_buttons();
317 }
318 
323 {
324  bool sensitive;
325  QString text;
326 
327  // Observe button
329  ui.bops->setText(_("Don't Observe"));
330  } else {
331  ui.bops->setText(_("Observe"));
332  }
333 
334  // Ready button
335  if (can_client_control()) {
336  sensitive = true;
337  if (client_player()->is_ready) {
338  text = _("Not ready");
339  } else {
340  int num_unready = 0;
341 
342  players_iterate(pplayer)
343  {
344  if (is_human(pplayer) && !pplayer->is_ready) {
345  num_unready++;
346  }
347  }
349 
350  if (num_unready > 1) {
351  text = _("Ready");
352  } else {
353  /* We are the last unready player so clicking here will
354  * immediately start the game. */
355  text = ("Start");
356  }
357  }
358  } else {
359  text = _("Start");
361  sensitive = true;
362  players_iterate(plr)
363  {
364  if (is_human(plr)) {
365  /* There's human controlled player(s) in game, so it's their
366  * job to start the game. */
367  sensitive = false;
368  break;
369  }
370  }
372  } else {
373  sensitive = false;
374  }
375  }
376  ui.bstart->setEnabled(sensitive);
377  ui.bstart->setText(text);
378 
379  sensitive = game.info.is_new_game;
380  ui.pr_options->setEnabled(sensitive);
381  ui.pr_options->update_buttons();
382  ui.pr_options->update_ai_level();
383 }
384 
389 {
390  QAction *action;
391  QMenu *menu, *submenu_AI, *submenu_team;
392  QPoint global_pos = ui.start_players_tree->mapToGlobal(pos);
393  QString me, splayer, str;
394  QStringList spl;
395  int hacky_counter = 0;
396  bool need_empty_team;
397  const char *level_cmd, *level_name;
398  int level, count;
399  QList<player *> selected_players;
400  QVariant qvar, qvar2;
401 
402  me = client.conn.username;
403  QList<QTreeWidgetItem *> sel_items =
404  ui.start_players_tree->selectedItems();
405 
406  menu = new QMenu(this);
407  submenu_AI = new QMenu(this);
408  submenu_team = new QMenu(this);
409 
410  if (sel_items.isEmpty()) {
411  return;
412  }
413 
414  for (auto *item : qAsConst(sel_items)) {
415  qvar = item->data(0, Qt::UserRole);
416  qvar2 = item->data(1, Qt::UserRole);
417 
422  if (qvar == 0) {
423  return;
424  }
425  if (qvar == 1) {
426  selected_players.append(static_cast<player *>(qvar2.value<void *>()));
427  }
428  }
429 
430  players_iterate(pplayer)
431  {
432  if (selected_players.contains(pplayer)) {
433  // I have no idea what Im doing = yes
434  ++hacky_counter;
435  splayer = QString(pplayer->name);
436  spl.append("\"" + splayer + "\"");
437  if (hacky_counter != sel_items.count()) {
438  continue;
439  }
440  }
441 
442  if (selected_players.contains(pplayer)) {
443  if (me != splayer && sel_items.count() == 1) {
444  str = QString(_("Observe"));
445  action = new QAction(str, ui.start_players_tree);
446  str = "/observe " + spl.first();
447  QObject::connect(action, &QAction::triggered,
448  [this, str]() { send_fake_chat_message(str); });
449  menu->addAction(action);
450 
451  if (ALLOW_CTRL <= client.conn.access_level) {
452  str = QString(_("Remove player"));
453  action = new QAction(str, ui.start_players_tree);
454  str = "/remove " + spl.first();
455  QObject::connect(action, &QAction::triggered,
456  [this, str]() { send_fake_chat_message(str); });
457  menu->addAction(action);
458  }
459  str = QString(_("Take this player"));
460  action = new QAction(str, ui.start_players_tree);
461  str = "/take " + spl.first();
462  QObject::connect(action, &QAction::triggered,
463  [this, str]() { send_fake_chat_message(str); });
464  menu->addAction(action);
465  }
466 
468  && sel_items.count() == 1) {
469  str = QString(_("Pick nation"));
470  action = new QAction(str, ui.start_players_tree);
471  str = QString(player_name(pplayer));
472  QObject::connect(action, &QAction::triggered, [str]() {
473  QString splayer;
474  players_iterate(pplayer)
475  {
476  splayer = QString(pplayer->name);
477  if (!splayer.compare(str)) {
478  popup_races_dialog(pplayer);
479  }
480  }
482  });
483  menu->addAction(action);
484  }
485 
486  if (is_ai(pplayer)) {
490  if (ALLOW_CTRL <= client.conn.access_level) {
491  submenu_AI->setTitle(_("Set difficulty"));
492  menu->addMenu(submenu_AI);
493 
494  for (level = 0; level < AI_LEVEL_COUNT; level++) {
495  if (is_settable_ai_level(static_cast<ai_level>(level))) {
496  level_name =
497  ai_level_translated_name(static_cast<ai_level>(level));
498  level_cmd = ai_level_cmd(static_cast<ai_level>(level));
499  action =
500  new QAction(QString(level_name), ui.start_players_tree);
501  QObject::connect(action, &QAction::triggered,
502  [this, spl, level_cmd]() {
503  for (const auto &sp : spl) {
504  QString str;
505  str = "/" + QString(level_cmd) + " " + sp;
507  }
508  });
509  submenu_AI->addAction(action);
510  }
511  }
512  }
513  }
514 
518  if (pplayer && game.info.is_new_game) {
519  menu->addMenu(submenu_team);
520  submenu_team->setTitle(_("Put on team"));
521  menu->addMenu(submenu_team);
522  count = pplayer->team ? player_list_size(team_members(pplayer->team))
523  : 0;
524  need_empty_team = (count != 1);
525  team_slots_iterate(tslot)
526  {
527  if (!team_slot_is_used(tslot)) {
528  if (!need_empty_team) {
529  continue;
530  }
531  need_empty_team = false;
532  }
533  str = team_slot_name_translation(tslot);
534  action = new QAction(str, ui.start_players_tree);
535  QObject::connect(
536  action, &QAction::triggered, [this, spl, tslot]() {
537  for (const auto &sp : spl) {
538  QString str = "/team" + sp + " \""
539  + QString(team_slot_rule_name(tslot)) + "\"";
541  }
542  });
543  submenu_team->addAction(action);
544  }
546  }
547 
548  if (ALLOW_CTRL <= client.conn.access_level && nullptr != pplayer
549  && sel_items.count() == 1) {
550  str = QString(_("Aitoggle player"));
551  action = new QAction(str, ui.start_players_tree);
552  QObject::connect(action, &QAction::triggered, [this, spl]() {
553  for (const auto &sp : spl) {
554  QString str = "/aitoggle " + sp;
556  }
557  });
558  menu->addAction(action);
559  }
560  }
561  }
563 
564  menu->popup(global_pos);
565 }
566 
571 {
573 }
574 
579  const struct text_tag_list *tags)
580 {
581  QColor col = ui.output_window->palette().color(QPalette::Text);
582  QString str = apply_tags(message, tags, col);
583 
584  if (ui.output_window != nullptr) {
585  ui.output_window->append(str);
586  ui.output_window->verticalScrollBar()->setSliderPosition(
587  ui.output_window->verticalScrollBar()->maximum());
588  }
589 }
590 
595 {
597  if (game.info.is_new_game) {
598  send_chat("/take -");
599  } else {
600  send_chat("/detach");
601  }
602  ui.bops->setText(_("Don't Observe"));
603  } else {
604  send_chat("/observe");
605  ui.bops->setText(_("Observe"));
606  }
607 }
608 
613 {
614  if (can_client_control()) {
615  dsend_packet_player_ready(&client.conn, player_number(client_player()),
616  !client_player()->is_ready);
617  } else {
618  dsend_packet_player_ready(&client.conn, 0, true);
619  }
620 }
QString apply_tags(QString str, const struct text_tag_list *tags, QColor bg_color)
Applies tags to text.
Definition: chatline.cpp:710
int send_chat(const char *message)
Send the message as a chat to the server.
void send_chat_message(const QString &message)
Sends commands to server, but first searches for custom keys, if it finds then it makes custom action...
Definition: chatline.cpp:86
QIcon getIcon(const QString &id)
Returns icon by given name.
Definition: icons.cpp:125
static fcIcons * instance()
Returns instance of fc_icons.
Definition: icons.cpp:36
void slot_disconnect()
Disconnect from server and return to MAIN PAGE.
Definition: fc_client.cpp:323
Ui::FormPagePregame ui
Definition: page_pregame.h:41
void update_buttons()
Updates observe button in case user started observing manually.
void send_fake_chat_message(const QString &message)
Slot to send fake chat messages.
void slot_pregame_start()
User clicked Start in START_PAGE.
fc_client * king
Definition: page_pregame.h:42
void start_page_menu(QPoint pos)
Context menu on some player, arg Qpoint specifies some pixel on screen.
void set_rulesets(int num_rulesets, QStringList rulesets)
void update_vote()
void slot_pregame_observe()
User clicked Observe button in START_PAGE.
~page_pregame() override
void chat_message_received(const QString &message, const struct text_tag_list *tags) override
Appends text to chat window.
page_pregame(QWidget *, fc_client *)
void update_start_page()
Updates start page (start page = client connected to server, but game not started)
bool client_is_global_observer()
Returns whether client is global observer.
struct player * client_player()
Either controlling or observing.
struct civclient client
bool client_is_observer()
Returns whether client is observer.
bool can_client_control()
Returns TRUE iff the client can control player.
bool player_has_color(const struct tileset *t, const struct player *pplayer)
Return whether the player has a color assigned yet.
QColor get_player_color(const struct tileset *t, const struct player *pplayer)
Return the color of the player.
bool can_client_access_hack()
Returns TRUE if the client has hack access.
#define conn_list_iterate(connlist, pconn)
Definition: connection.h:99
#define conn_list_iterate_end
Definition: connection.h:101
void popup_races_dialog(struct player *pplayer)
Popup the nation selection dialog.
Definition: dialogs.cpp:997
#define Q_(String)
Definition: fcintl.h:53
#define _(String)
Definition: fcintl.h:50
struct civ_game game
Definition: game.cpp:47
static mpgui * gui
Definition: mpgui_qt.cpp:47
const char * nation_adjective_for_player(const struct player *pplayer)
Return the (translated) adjective for the given nation of a player.
Definition: nation.cpp:146
bool can_conn_edit_players_nation(const struct connection *pconn, const struct player *pplayer)
Return TRUE iff the editor is allowed to edit the player's nation in pregame.
Definition: nation.cpp:1046
#define NO_NATION_SELECTED
Definition: nation.h:21
bool is_settable_ai_level(enum ai_level level)
Return is AI can be set to given level.
Definition: player.cpp:1825
int player_number(const struct player *pplayer)
Return the player index/number/id.
Definition: player.cpp:756
bool player_has_flag(const struct player *pplayer, enum plr_flag_id flag)
Check if player has given flag.
Definition: player.cpp:1888
const char * player_name(const struct player *pplayer)
Return the leader name of the player.
Definition: player.cpp:816
#define ai_level_cmd(_level_)
Definition: player.h:551
#define players_iterate_end
Definition: player.h:520
#define players_iterate(_pplayer)
Definition: player.h:514
static bool is_barbarian(const struct player *pplayer)
Definition: player.h:474
#define is_ai(plr)
Definition: player.h:227
#define is_human(plr)
Definition: player.h:226
struct setting_list * level[OLEVELS_NUM]
Definition: settings.cpp:167
struct conn_list * est_connections
Definition: game.h:88
struct packet_game_info info
Definition: game.h:80
struct conn_list * all_connections
Definition: game.h:87
struct connection conn
Definition: client_main.h:89
enum cmdlevel access_level
Definition: connection.h:164
bool observer
Definition: connection.h:138
char username[MAX_LEN_NAME]
Definition: connection.h:151
Definition: climisc.h:66
Definition: player.h:231
Definition: team.cpp:35
const char * team_name_translation(const struct team *pteam)
Returns the name (translated) of the team.
Definition: team.cpp:393
const char * team_slot_name_translation(const struct team_slot *tslot)
Returns the name (translated) of the slot.
Definition: team.cpp:237
const char * team_slot_rule_name(const struct team_slot *tslot)
Returns the name (untranslated) of the slot.
Definition: team.cpp:217
const struct player_list * team_members(const struct team *pteam)
Returns the member list of the team.
Definition: team.cpp:427
bool team_slot_is_used(const struct team_slot *tslot)
Returns TRUE is this slot is "used" i.e.
Definition: team.cpp:144
#define team_slots_iterate_end
Definition: team.h:66
#define team_slots_iterate(_tslot)
Definition: team.h:62
const QPixmap * get_nation_flag_sprite(const struct tileset *t, const struct nation_type *pnation)
Return the sprite for the nation.
Definition: tilespec.cpp:3367