diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index c7a2b4744c..89acf9834e 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -110,21 +110,36 @@ target_sources( audio/audio.cpp audio/audio_none.cpp + tileset/drawn_sprite.cpp + tileset/layer_abstract_activities.cpp tileset/layer_background.cpp tileset/layer_base_flags.cpp + tileset/layer_city.cpp + tileset/layer_city_size.cpp tileset/layer_darkness.cpp + tileset/layer_infrawork.cpp + tileset/layer_editor.cpp + tileset/layer_fog.cpp + tileset/layer_goto.cpp + tileset/layer_grid.cpp + tileset/layer_overlays.cpp + tileset/layer_roads.cpp tileset/layer_special.cpp tileset/layer_terrain.cpp tileset/layer_units.cpp + tileset/layer_water.cpp + tileset/layer_workertask.cpp tileset/layer.cpp tileset/sprite.cpp tileset/tilespec.cpp + utils/collated_sort_proxy.cpp utils/colorizer.cpp utils/improvement_seller.cpp utils/unit_quick_menu.cpp utils/unit_utils.cpp + views/view_cities.cpp views/view_cities_data.cpp views/view_economics.cpp @@ -136,6 +151,7 @@ target_sources( views/view_research.cpp views/view_research_reqtree.cpp views/view_units.cpp + widgets/decorations.cpp widgets/city/city_icon_widget.cpp widgets/city/governor_widget.cpp diff --git a/client/citybar.cpp b/client/citybar.cpp index a824fcd34e..6a80aa62d3 100644 --- a/client/citybar.cpp +++ b/client/citybar.cpp @@ -489,13 +489,13 @@ QRect traditional_citybar_painter::paint(QPainter &painter, unsigned long count = unit_list_size(pcity->tile->units); count = qBound(0UL, count, - static_cast(citybar->occupancy.size - 1)); - first.add_icon(citybar->occupancy.p[count]); + static_cast(citybar->occupancy.size() - 1)); + first.add_icon(citybar->occupancy[count]); } else { if (pcity->client.occupied) { first.add_icon(citybar->occupied); } else { - first.add_icon(citybar->occupancy.p[0]); + first.add_icon(citybar->occupancy[0]); } } @@ -642,7 +642,7 @@ QRect polished_citybar_painter::paint(QPainter &painter, // Decide on the target height. It's the max of the font sizes and the // occupied indicator (we assume all indicators have the same size). // It's used to scale the flag, progress bars and production. - double target_height = citybar->occupancy.p[0]->height(); + double target_height = citybar->occupancy[0]->height(); target_height = std::max(target_height, QFontMetricsF(get_font(FONT_CITY_NAME)).height()); target_height = std::max(target_height, @@ -717,14 +717,15 @@ QRect polished_citybar_painter::paint(QPainter &painter, // Occupied indicator if (can_player_see_units_in_city(client.conn.playing, pcity)) { unsigned long count = unit_list_size(pcity->tile->units); - count = qBound(0UL, count, - static_cast(citybar->occupancy.size - 1)); - line.add_icon(citybar->occupancy.p[count]); + count = + qBound(0UL, count, + static_cast(citybar->occupancy.size() - 1)); + line.add_icon(citybar->occupancy[count]); } else { if (pcity->client.occupied) { line.add_icon(citybar->occupied); } else { - line.add_icon(citybar->occupancy.p[0]); + line.add_icon(citybar->occupancy[0]); } } diff --git a/client/citydlg.cpp b/client/citydlg.cpp index 0b2e58ccd0..2251f94352 100644 --- a/client/citydlg.cpp +++ b/client/citydlg.cpp @@ -207,7 +207,7 @@ QPixmap unit_list_widget::create_unit_image(const unit *punit) } unit_pixmap.fill(Qt::transparent); - put_unit(punit, &unit_pixmap, 0, 0); + put_unit(punit, &unit_pixmap, QPoint()); if (m_show_upkeep) { put_unit_city_overlays(punit, &unit_pixmap, 0, diff --git a/client/client_main.cpp b/client/client_main.cpp index 43a9b1f3db..6212ce0a32 100644 --- a/client/client_main.cpp +++ b/client/client_main.cpp @@ -374,7 +374,9 @@ int client_main(int argc, char *argv[]) {{"S", "Sound"}, _("Read sound tags from FILE."), "FILE"}, {{"m", "music"}, _("Read music tags from FILE."), "FILE"}, {{"t", "tiles"}, _("Use data file FILE.tilespec for tiles."), "FILE"}, - {{"w", "warnings"}, _("Warn about deprecated modpack constructs.")}}); + {{"w", "warnings"}, _("Warn about deprecated modpack constructs.")}, + {"debug-tileset", _("Prints tileset debugging information.")}}); + parser.addPositionalArgument( _("url"), _("Server information in URL format."), _("[url]")); if (!ok) { @@ -384,8 +386,16 @@ int client_main(int argc, char *argv[]) parser.process(app); // Process the parsed options - if (!log_init(parser.value(QStringLiteral("debug")))) { - exit(EXIT_FAILURE); + + // Init log level + { + QStringList extra; + if (!parser.isSet(QStringLiteral("debug-tileset"))) { + extra.push_back(QStringLiteral("freeciv.tileset.info = false")); + } + if (!log_init(parser.value(QStringLiteral("debug")), extra)) { + exit(EXIT_FAILURE); + } } if (parser.isSet(QStringLiteral("version"))) { fc_fprintf(stderr, "%s %s\n", freeciv_name_version(), client_string); diff --git a/client/dialogs.cpp b/client/dialogs.cpp index b0235e6953..b40b737ec7 100644 --- a/client/dialogs.cpp +++ b/client/dialogs.cpp @@ -48,7 +48,7 @@ #include "page_game.h" #include "qtg_cxxside.h" #include "text.h" -#include "tileset/sprite.h" +#include "tileset/layer_city.h" #include "tileset/tilespec.h" #include "unithudselector.h" #include "unitselect.h" @@ -372,7 +372,6 @@ races_dialog::races_dialog(struct player *pplayer, QWidget *parent) QGroupBox *no_name; QTableWidgetItem *item; QHeaderView *header; - QSize size; QString title; QLabel *ns_label; @@ -452,12 +451,10 @@ races_dialog::races_dialog(struct player *pplayer, QWidget *parent) if (i >= 0) { item = new QTableWidgetItem; styles->insertRow(i); - auto pix = get_sample_city_sprite(tileset, i); - item->setData(Qt::DecorationRole, *pix); + auto pix = tileset_layer_city(tileset)->sample_sprite(i); + item->setData(Qt::DecorationRole, pix); item->setData(Qt::UserRole, style_number(pstyle)); - size.setWidth(pix->width()); - size.setHeight(pix->height()); - item->setSizeHint(size); + item->setSizeHint(pix.size()); styles->setItem(i, 0, item); item = new QTableWidgetItem; item->setText(style_name_translation(pstyle)); @@ -1212,7 +1209,7 @@ void choice_dialog::set_layout() pix = new QPixmap(tileset_unit_width(tileset), tileset_unit_height(tileset)); pix->fill(Qt::transparent); - put_unit(targeted_unit, pix, 0, 0); + put_unit(targeted_unit, pix, QPoint()); target_unit_button->setIcon(QIcon(*pix)); delete pix; target_unit_button->setIconSize(QSize(96, 96)); @@ -1364,7 +1361,7 @@ void choice_dialog::next_unit() pix = new QPixmap(tileset_unit_width(tileset), tileset_unit_height(tileset)); pix->fill(Qt::transparent); - put_unit(targeted_unit, pix, 0, 0); + put_unit(targeted_unit, pix, QPoint()); target_unit_button->setIcon(QIcon(*pix)); delete pix; switch_target(); @@ -1395,7 +1392,7 @@ void choice_dialog::prev_unit() pix = new QPixmap(tileset_unit_width(tileset), tileset_unit_height(tileset)); pix->fill(Qt::transparent); - put_unit(targeted_unit, pix, 0, 0); + put_unit(targeted_unit, pix, QPoint()); target_unit_button->setIcon(QIcon(*pix)); delete pix; switch_target(); diff --git a/client/goto.h b/client/goto.h index 435ef86e44..fcbaece129 100644 --- a/client/goto.h +++ b/client/goto.h @@ -16,6 +16,7 @@ class PFPath; struct tile; struct unit; struct unit_list; +struct unit_order; enum goto_tile_state { GTS_TURN_STEP, diff --git a/client/helpdlg.cpp b/client/helpdlg.cpp index 5cfa329d7b..06a78d4b24 100644 --- a/client/helpdlg.cpp +++ b/client/helpdlg.cpp @@ -34,7 +34,6 @@ #include "fc_client.h" #include "fonts.h" #include "helpdlg.h" -#include "tileset/sprite.h" #include "tileset/tilespec.h" #include "views/view_map_common.h" @@ -1170,7 +1169,7 @@ QPixmap *terrain_canvas(struct terrain *terrain, for (i = 0; i < 3; ++i) { auto sprites = fill_basic_terrain_layer_sprite_array(tileset, i, terrain); - put_drawn_sprites(canvas, 0, canvas_y, sprites, false); + put_drawn_sprites(canvas, QPoint(0, canvas_y), sprites, false); } pextra = nullptr; @@ -1183,12 +1182,12 @@ QPixmap *terrain_canvas(struct terrain *terrain, extra_type_by_cause_iterate_end; fc_assert_ret_val(pextra, nullptr); auto sprites = fill_basic_extra_sprite_array(tileset, pextra); - put_drawn_sprites(canvas, 0, canvas_y, sprites, false); + put_drawn_sprites(canvas, QPoint(0, canvas_y), sprites, false); } if (resource != nullptr) { auto sprites = fill_basic_extra_sprite_array(tileset, resource); - put_drawn_sprites(canvas, 0, canvas_y, sprites, false); + put_drawn_sprites(canvas, QPoint(0, canvas_y), sprites, false); } return canvas; @@ -1245,7 +1244,6 @@ QLayout *help_widget::create_terrain_widget(const QString &title, { QGraphicsDropShadowEffect *effect; QLabel *label; - QPixmap *spr; QHBoxLayout *layout = new QHBoxLayout(); QHBoxLayout *layout1 = new QHBoxLayout(); QHBoxLayout *layout2 = new QHBoxLayout(); @@ -1267,19 +1265,14 @@ QLayout *help_widget::create_terrain_widget(const QString &title, make_helplabel(_("Output becomes: "), tooltip, layout2); make_helplabel(QString::number(food), tooltip, layout2) ->setProperty("foodlab", "true"); - spr = tiles_lookup_sprite_tag_alt(tileset, LOG_VERBOSE, "citybar.food", - "citybar.food", "", "", false); - make_helppiclabel(spr, tooltip, layout2); + auto sprites = get_citybar_sprites(tileset); + make_helppiclabel(sprites->food, tooltip, layout2); make_helplabel(QString::number(sh), tooltip, layout2) ->setProperty("shieldlab", "true"); - spr = tiles_lookup_sprite_tag_alt(tileset, LOG_VERBOSE, "citybar.shields", - "upkeep.shield", "", "", false); - make_helppiclabel(spr, tooltip, layout2); + make_helppiclabel(sprites->shields, tooltip, layout2); make_helplabel(QString::number(eco), tooltip, layout2) ->setProperty("ecolab", "true"); - spr = tiles_lookup_sprite_tag_alt(tileset, LOG_VERBOSE, "citybar.trade", - "upkeep.gold", "", "", false); - make_helppiclabel(spr, tooltip, layout2); + make_helppiclabel(sprites->trade, tooltip, layout2); w2->setLayout(layout2); layout->addWidget(w1, Qt::AlignVCenter); diff --git a/client/hudwidget.cpp b/client/hudwidget.cpp index a4ef2702d4..83825dc920 100644 --- a/client/hudwidget.cpp +++ b/client/hudwidget.cpp @@ -662,7 +662,7 @@ void hud_units::update_actions() unit_pixmap = new QPixmap(tileset_unit_width(tileset), tileset_unit_height(tileset)); unit_pixmap->fill(Qt::transparent); - put_unit(punit, unit_pixmap, 0, 0); + put_unit(punit, unit_pixmap, QPoint()); img = unit_pixmap->toImage(); crop = zealous_crop_rect(img); cropped_img = img.copy(crop); @@ -746,7 +746,7 @@ void hud_units::update_actions() tileset_tile_height(tileset)); } tile_pixmap->fill(QColor(0, 0, 0, 0)); - put_terrain(punit->tile, tile_pixmap, 0, 0); + put_terrain(punit->tile, tile_pixmap, QPoint()); img = tile_pixmap->toImage(); crop = zealous_crop_rect(img); cropped_img = img.copy(crop); @@ -1406,7 +1406,7 @@ void hud_unit_combat::init_images(bool redraw) defender_pixmap.fill(Qt::transparent); if (defender != nullptr) { if (!redraw) { - put_unit(defender, &defender_pixmap, 0, 0); + put_unit(defender, &defender_pixmap, QPoint()); } else { defender_pixmap = *get_unittype_sprite(tileset, type_defender, direction8_invalid()); @@ -1430,7 +1430,7 @@ void hud_unit_combat::init_images(bool redraw) attacker_pixmap.fill(Qt::transparent); if (attacker != nullptr) { if (!redraw) { - put_unit(attacker, &attacker_pixmap, 0, 0); + put_unit(attacker, &attacker_pixmap, QPoint()); } else { attacker_pixmap = *get_unittype_sprite(tileset, type_attacker, direction8_invalid()); diff --git a/client/overview_common.cpp b/client/overview_common.cpp index 8875a6ea7a..cebca753fd 100644 --- a/client/overview_common.cpp +++ b/client/overview_common.cpp @@ -301,7 +301,7 @@ static void put_overview_tile_area(QPixmap *pcanvas, const tile *ptile, p.fillRect(x, y, w, h, overview_tile_color(ptile)); if (gui_options->overview.fog && TILE_KNOWN_UNSEEN == client_tile_get_known(ptile)) { - p.drawPixmap(x, y, w, h, *get_basic_fog_sprite(tileset)); + p.fillRect(x, y, w, h, QColor(0, 0, 0, 128)); } p.end(); } diff --git a/client/packhand.cpp b/client/packhand.cpp index 53e08403ed..10bc84bc51 100644 --- a/client/packhand.cpp +++ b/client/packhand.cpp @@ -841,9 +841,6 @@ void handle_city_info(const struct packet_city_info *packet) } pcity->client.walls = packet->walls; - if (pcity->client.walls > NUM_WALL_TYPES) { - pcity->client.walls = NUM_WALL_TYPES; - } pcity->style = packet->style; pcity->capital = packet->capital; if (packet->capital == CAPITAL_PRIMARY) { @@ -3303,6 +3300,10 @@ void handle_ruleset_description_part( */ void handle_rulesets_ready() { + // Init extras - has to come after terrain + extra_type_iterate(pextra) { tileset_setup_extra(tileset, pextra); } + extra_type_iterate_end; + // Setup extra hiders caches extra_type_iterate(pextra) { @@ -3356,8 +3357,7 @@ void handle_rulesets_ready() // Adjust editor for changed ruleset. editor_ruleset_changed(); - /* We are not going to crop any more sprites from big sprites, free them. - */ + // We are not going to crop any more sprites from big sprites, free them. finish_loading_sprites(tileset); game.client.ruleset_ready = true; @@ -4039,8 +4039,6 @@ void handle_ruleset_extra(const struct packet_ruleset_extra *p) pextra->conflicts = p->conflicts; pextra->helptext = packet_strvec_extract(p->helptext); - - tileset_setup_extra(tileset, pextra); } /** diff --git a/client/tileset/drawn_sprite.cpp b/client/tileset/drawn_sprite.cpp index 5fd217ab0e..f492a8fb60 100644 --- a/client/tileset/drawn_sprite.cpp +++ b/client/tileset/drawn_sprite.cpp @@ -18,11 +18,20 @@ drawn_sprite::drawn_sprite(const struct tileset *ts, const QPixmap *sprite, bool foggable, int offset_x, int offset_y) : sprite(sprite), foggable(foggable && tileset_use_hard_coded_fog(ts)), - offset_x(offset_x), offset_y(offset_y) + offset(offset_x, offset_y) { fc_assert(sprite); } +/** + * Instantiates a drawn sprite, ensuring that it's never null. + */ +drawn_sprite::drawn_sprite(const struct tileset *ts, const QPixmap *sprite, + bool foggable, const QPoint &offset) + : drawn_sprite(ts, sprite, foggable, offset.x(), offset.y()) +{ +} + /** * Calculates the bounding rectangle of the given sprite array. */ @@ -30,8 +39,7 @@ QRect sprite_array_bounds(const std::vector &sprs) { QRect bounds; for (const auto &sprite : sprs) { - bounds |= QRect(QPoint(sprite.offset_x, sprite.offset_y), - sprite.sprite->size()); + bounds |= QRect(sprite.offset, sprite.sprite->size()); } return bounds; } diff --git a/client/tileset/drawn_sprite.h b/client/tileset/drawn_sprite.h index eaf0399256..f34f57d0bc 100644 --- a/client/tileset/drawn_sprite.h +++ b/client/tileset/drawn_sprite.h @@ -1,12 +1,14 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021 Freeciv21 contributors - * SPDX-FileCopyrightText: 2023 Louis Moureaux + * SPDX-FileCopyrightText: Freeciv21 contributors + * SPDX-FileCopyrightText: Louis Moureaux * * SPDX-License-Identifier: GPLv3-or-later */ #pragma once +#include + #include class QPixmap; @@ -18,12 +20,16 @@ struct drawn_sprite { explicit drawn_sprite(const tileset *ts, const QPixmap *sprite, bool foggable = true, int offset_x = 0, int offset_y = 0); + explicit drawn_sprite(const tileset *ts, const QPixmap *sprite, + bool foggable, const QPoint &offset); drawn_sprite(const drawn_sprite &other) = default; drawn_sprite(drawn_sprite &&other) = default; + drawn_sprite &operator=(const drawn_sprite &other) = default; + drawn_sprite &operator=(drawn_sprite &&other) = default; const QPixmap *sprite; - bool foggable; // Set to FALSE for sprites that are never fogged. - int offset_x, offset_y; // offset from tile origin + bool foggable; ///< Set to FALSE for sprites that are never fogged. + QPoint offset; ///< offset from tile origin }; QRect sprite_array_bounds(const std::vector &sprs); diff --git a/client/tileset/layer.cpp b/client/tileset/layer.cpp index 0380741f46..b266e86c5e 100644 --- a/client/tileset/layer.cpp +++ b/client/tileset/layer.cpp @@ -11,21 +11,14 @@ \_____/ / If not, see https://www.gnu.org/licenses/. \____/ ********************************************************/ +#include "layer.h" + #include "control.h" +#include "options.h" #include "tilespec.h" -#include "layer.h" -#include "log.h" - namespace freeciv { -std::vector -layer::fill_sprite_array(const tile *ptile, const tile_edge *pedge, - const tile_corner *pcorner, const unit *punit) const -{ - return ::fill_sprite_array(m_ts, m_layer, ptile, pedge, pcorner, punit); -} - /** * @brief Whether a unit should be drawn. * @param ptile The tile where to draw (can be null) @@ -80,4 +73,13 @@ bool layer::solid_background(const tile *ptile, const unit *punit, return do_draw_unit(ptile, punit) || (gui_options->draw_cities && pcity); } +/** + * \brief Shortcut to load a sprite from the tileset. + */ +QPixmap *layer::load_sprite(const QStringList &possible_names, bool required, + bool verbose) const +{ + return ::load_sprite(tileset(), possible_names, required, verbose); +} + } // namespace freeciv diff --git a/client/tileset/layer.h b/client/tileset/layer.h index 8154dbaa22..55dfd3a098 100644 --- a/client/tileset/layer.h +++ b/client/tileset/layer.h @@ -18,6 +18,8 @@ class QPixmap; struct city; +struct citystyle; +struct extra_type; struct player; struct terrain; struct tileset; @@ -47,6 +49,34 @@ struct tile_corner { const struct tile *tile[NUM_CORNER_TILES]; }; +#define SPECENUM_NAME extrastyle_id +#define SPECENUM_VALUE0 ESTYLE_ROAD_ALL_SEPARATE +#define SPECENUM_VALUE0NAME "RoadAllSeparate" +#define SPECENUM_VALUE1 ESTYLE_ROAD_PARITY_COMBINED +#define SPECENUM_VALUE1NAME "RoadParityCombined" +#define SPECENUM_VALUE2 ESTYLE_ROAD_ALL_COMBINED +#define SPECENUM_VALUE2NAME "RoadAllCombined" +#define SPECENUM_VALUE3 ESTYLE_RIVER +#define SPECENUM_VALUE3NAME "River" +#define SPECENUM_VALUE4 ESTYLE_SINGLE1 +#define SPECENUM_VALUE4NAME "Single1" +#define SPECENUM_VALUE5 ESTYLE_SINGLE2 +#define SPECENUM_VALUE5NAME "Single2" +#define SPECENUM_VALUE6 ESTYLE_3LAYER +#define SPECENUM_VALUE6NAME "3Layer" +#define SPECENUM_VALUE7 ESTYLE_CARDINALS +#define SPECENUM_VALUE7NAME "Cardinals" +#define SPECENUM_COUNT ESTYLE_COUNT +#include "specenum_gen.h" + +// This the way directional indices are now encoded: +#define MAX_INDEX_CARDINAL 64 +#define MAX_INDEX_HALF 16 +#define MAX_INDEX_VALID 256 + +// Numbers on the map are shown in base 10 +#define NUM_TILES_DIGITS 10 + /* Items on the mapview are drawn in layers. Each entry below represents * one layer. The names are basically arbitrary and just correspond to * groups of elements in fill_sprite_array(). Callers of fill_sprite_array @@ -134,9 +164,25 @@ class layer { */ virtual ~layer() = default; + /** + * Returns the list of sprites drawn by this layer somewhere on the map. + * Only one of ptile, pedge, or pcorner should be non-null. + */ virtual std::vector fill_sprite_array(const tile *ptile, const tile_edge *pedge, - const tile_corner *pcorner, const unit *punit) const; + const tile_corner *pcorner, const unit *punit) const + { + Q_UNUSED(ptile); + Q_UNUSED(pedge); + Q_UNUSED(pcorner); + Q_UNUSED(punit); + return {}; + } + + /** + * Loads all sprites that do not depend on the ruleset. + */ + virtual void load_sprites() {} /** * Initializes data specific to one player. This allows to cache tiles @@ -148,12 +194,34 @@ class layer { /** * Frees data initialized by initialize_player. Note that this takes the - * player ID and not a pointer to the player. + * player ID and not a pointer to the player; the player may have never + * existed. * * \see initialize_player */ virtual void free_player(int player_id) { Q_UNUSED(player_id); } + /** + * Initializes data for a city style. + */ + virtual void initialize_city_style(const citystyle &style, int index) + { + Q_UNUSED(style); + Q_UNUSED(index); + } + + /** + * Initializes extra-specific data. This is called after terrains have been + * loaded, and only for extras with a graphic (graphic_str != none). + */ + virtual void initialize_extra(const extra_type *extra, const QString &tag, + extrastyle_id style) + { + Q_UNUSED(extra); + Q_UNUSED(tag); + Q_UNUSED(style); + } + /** * Initializes terrain-specific data. */ @@ -176,6 +244,9 @@ class layer { bool solid_background(const tile *ptile, const unit *punit, const city *pcity) const; + QPixmap *load_sprite(const QStringList &possible_names, + bool required = false, bool verbose = true) const; + private: struct tileset *m_ts; mapview_layer m_layer; diff --git a/client/tileset/layer_abstract_activities.cpp b/client/tileset/layer_abstract_activities.cpp new file mode 100644 index 0000000000..beb8d2b4e2 --- /dev/null +++ b/client/tileset/layer_abstract_activities.cpp @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPLv3-or-later +// SPDX-FileCopyrightText: Freeciv authors +// SPDX-FileCopyrightText: Freeciv21 authors +// SPDX-FileCopyrightText: Louis Moureaux + +#include "layer_abstract_activities.h" + +#include "tilespec.h" + +namespace freeciv { + +/** + * \class layer_abstract_activities + * \brief An abstract class for layers that need sprites for unit activities. + */ + +/** + * Loads the sprites in memory. + */ +void layer_abstract_activities::load_sprites() +{ + m_activities[ACTIVITY_MINE] = load_sprite({"unit.plant"}, true); + m_activities[ACTIVITY_PLANT] = m_activities[ACTIVITY_MINE]; + m_activities[ACTIVITY_IRRIGATE] = load_sprite({"unit.irrigate"}, true); + m_activities[ACTIVITY_CULTIVATE] = m_activities[ACTIVITY_IRRIGATE]; + m_activities[ACTIVITY_PILLAGE] = load_sprite({"unit.pillage"}, true); + m_activities[ACTIVITY_FORTIFIED] = load_sprite({"unit.fortified"}, true); + m_activities[ACTIVITY_FORTIFYING] = load_sprite({"unit.fortifying"}, true); + m_activities[ACTIVITY_SENTRY] = load_sprite({"unit.sentry"}, true); + m_activities[ACTIVITY_GOTO] = load_sprite({"unit.goto"}, true); + m_activities[ACTIVITY_TRANSFORM] = load_sprite({"unit.transform"}, true); + m_activities[ACTIVITY_CONVERT] = load_sprite({"unit.convert"}, true); +} + +/** + * Loads the extra activity and remove activity sprites. + */ +void layer_abstract_activities::initialize_extra(const extra_type *extra, + const QString &tag, + extrastyle_id style) +{ + fc_assert(extra->id == m_extra_activities.size()); + + if (!fc_strcasecmp(extra->activity_gfx, "none")) { + m_extra_activities.push_back(nullptr); + } else { + QStringList tags = { + extra->activity_gfx, + extra->act_gfx_alt, + extra->act_gfx_alt2, + }; + m_extra_activities.push_back(load_sprite(tags, true)); + } + + if (!fc_strcasecmp(extra->rmact_gfx, "none")) { + m_extra_rm_activities.push_back(nullptr); + } else { + QStringList tags = { + extra->rmact_gfx, + extra->rmact_gfx_alt, + }; + m_extra_rm_activities.push_back(load_sprite(tags, true)); + } +} + +/** + * Resets data about extras + */ +void layer_abstract_activities::reset_ruleset() +{ + m_extra_activities.clear(); + m_extra_rm_activities.clear(); +} + +/** + * Returns the sprite used to represent a given activity on the map. + */ +QPixmap * +layer_abstract_activities::activity_sprite(unit_activity activity, + const extra_type *target) const +{ + // Nicely inconsistent + switch (activity) { + case ACTIVITY_POLLUTION: + case ACTIVITY_FALLOUT: + return m_extra_rm_activities[extra_index(target)]; + break; + case ACTIVITY_BASE: + case ACTIVITY_GEN_ROAD: + return m_extra_activities[extra_index(target)]; + break; + default: + break; + } + + return target ? m_extra_activities[extra_index(target)] + : m_activities[activity]; +} + +} // namespace freeciv diff --git a/client/tileset/layer_abstract_activities.h b/client/tileset/layer_abstract_activities.h new file mode 100644 index 0000000000..f0fc492555 --- /dev/null +++ b/client/tileset/layer_abstract_activities.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPLv3-or-later +// SPDX-FileCopyrightText: Freeciv authors +// SPDX-FileCopyrightText: Freeciv21 authors +// SPDX-FileCopyrightText: Louis Moureaux + +#pragma once + +#include + +#include "fc_types.h" +#include "layer.h" + +class QPixmap; +struct tileset; + +namespace freeciv { + +class layer_abstract_activities : public layer { +public: + /// Inherited constructor. + using layer::layer; + + void load_sprites() override; + + void initialize_extra(const extra_type *extra, const QString &tag, + extrastyle_id style) override; + + void reset_ruleset() override; + + QPixmap *activity_sprite(unit_activity id, const extra_type *extra) const; + +private: + std::array m_activities = {nullptr}; + std::vector m_extra_activities, m_extra_rm_activities; +}; + +} // namespace freeciv diff --git a/client/tileset/layer_base_flags.cpp b/client/tileset/layer_base_flags.cpp index 4f817591d1..f1e72fb615 100644 --- a/client/tileset/layer_base_flags.cpp +++ b/client/tileset/layer_base_flags.cpp @@ -26,10 +26,8 @@ namespace freeciv { * Map layer that draws flags for bases that have @ref EF_SHOW_FLAG set. */ -layer_base_flags::layer_base_flags(struct tileset *ts, int offset_x, - int offset_y) - : freeciv::layer(ts, LAYER_BASE_FLAGS), m_offset_x(offset_x), - m_offset_y(offset_y) +layer_base_flags::layer_base_flags(struct tileset *ts, const QPoint &offset) + : freeciv::layer(ts, LAYER_BASE_FLAGS), m_offset(offset) { } @@ -70,7 +68,7 @@ std::vector layer_base_flags::fill_sprite_array( return {drawn_sprite( tileset(), get_nation_flag_sprite(tileset(), nation_of_player(eowner)), - true, m_offset_x, m_offset_y)}; + true, m_offset)}; } } } diff --git a/client/tileset/layer_base_flags.h b/client/tileset/layer_base_flags.h index 4803e8b051..4e485dd632 100644 --- a/client/tileset/layer_base_flags.h +++ b/client/tileset/layer_base_flags.h @@ -15,11 +15,13 @@ #include "fc_types.h" #include "layer.h" +#include + namespace freeciv { class layer_base_flags : public layer { public: - explicit layer_base_flags(struct tileset *ts, int offset_x, int offset_y); + explicit layer_base_flags(struct tileset *ts, const QPoint &offset); virtual ~layer_base_flags() = default; std::vector @@ -28,7 +30,7 @@ class layer_base_flags : public layer { const unit *punit) const override; private: - int m_offset_x, m_offset_y; + QPoint m_offset; }; } // namespace freeciv diff --git a/client/tileset/layer_city.cpp b/client/tileset/layer_city.cpp new file mode 100644 index 0000000000..fbd74bfc56 --- /dev/null +++ b/client/tileset/layer_city.cpp @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: GPLv3-or-later +// SPDX-FileCopyrightText: Freeciv authors +// SPDX-FileCopyrightText: Freeciv21 authors +// SPDX-FileCopyrightText: Louis Moureaux + +#include "layer_city.h" + +#include "city.h" +#include "citybar.h" +#include "rgbcolor.h" +#include "style.h" +#include "tilespec.h" +#include "utils/colorizer.h" + +namespace freeciv { + +layer_city::layer_city(struct tileset *ts, const QPoint &city_offset, + const QPoint &city_flag_offset, + const QPoint &occupied_offset) + : freeciv::layer(ts, LAYER_CITY1), m_city_offset(city_offset), + m_city_flag_offset(city_flag_offset), + m_occupied_offset(occupied_offset) +{ +} + +/** + * Loads the disorder and happy sprites. + */ +void layer_city::load_sprites() +{ + m_disorder = load_sprite({QStringLiteral("city.disorder")}, true); + m_happy = load_sprite({QStringLiteral("city.happy")}, false); +} + +/** + * Loads a set of size-dependent city sprites, with tags of the form + * `graphic_tag_size`. + */ +layer_city::styles layer_city::load_city_size_sprites(const QString &tag, + const citystyle &style) +{ + auto gfx_in_use = style.graphic; + auto sprites = layer_city::styles(); + + for (int size = 0; size < MAX_CITY_SIZE; size++) { + const auto buffer = QStringLiteral("%1_%2_%3") + .arg(gfx_in_use, tag, QString::number(size)); + if (const auto sprite = load_sprite({buffer}, true, false)) { + sprites.push_back(std::make_unique( + *sprite, tileset_replaced_hue(tileset()))); + } else if (size == 0) { + if (gfx_in_use == style.graphic) { + // Try again with graphic_alt. + size--; + gfx_in_use = style.graphic_alt; + } else { + // Don't load any others if the 0 element isn't there. + break; + } + } else { + // Stop loading as soon as we don't find the next one. + break; + } + } + if (sprites.empty() && strlen(style.graphic_alt) + && style.graphic_alt != QStringLiteral("-")) { + tileset_error(tileset(), QtInfoMsg, + "Could not find any sprite matching %s_%s_0 or %s_%s_0", + style.graphic, qUtf8Printable(tag), style.graphic_alt, + qUtf8Printable(tag)); + } else if (sprites.empty()) { + // No graphic_alt + tileset_error(tileset(), QtInfoMsg, + "Could not find any sprite matching %s_%s_0", + style.graphic, qUtf8Printable(tag)); + } else { + tileset_error(tileset(), QtInfoMsg, "Loaded %d %s_%s* sprites", + sprites.size(), gfx_in_use, qUtf8Printable(tag)); + } + + return sprites; +} + +/** + * Loads all sprites for a city style. + */ +void layer_city::initialize_city_style(const citystyle &style, int index) +{ + fc_assert_ret(index == m_tile.size()); + + m_tile.push_back(load_city_size_sprites(QStringLiteral("city"), style)); + m_single_wall.push_back( + load_city_size_sprites(QStringLiteral("wall"), style)); + m_occupied.push_back( + load_city_size_sprites(QStringLiteral("occupied"), style)); + + for (int i = 0; i < m_walls.size(); i++) { + m_walls[i].push_back( + load_city_size_sprites(QStringLiteral("bldg_%1").arg(i), style)); + } + + if (m_tile.back().empty()) { + tileset_error(tileset(), LOG_FATAL, + _("City style \"%s\": no city graphics."), + city_style_rule_name(index)); + } + if (m_occupied.back().empty()) { + tileset_error(tileset(), LOG_FATAL, + _("City style \"%s\": no occupied graphics."), + city_style_rule_name(index)); + } +} + +namespace /* anonymous */ { +/** + * Return the sprite in the city_sprite listing that corresponds to this city + * - based on city style and size. + * + * See also load_city_sprite. + */ +static const QPixmap * +get_city_sprite(const layer_city::city_sprite &city_sprite, + const struct city *pcity) +{ + // get style and match the best tile based on city size + int style = style_of_city(pcity); + int img_index; + + fc_assert_ret_val(style < city_sprite.size(), nullptr); + + const auto num_thresholds = city_sprite[style].size(); + const auto &thresholds = city_sprite[style]; + + if (num_thresholds == 0) { + return nullptr; + } + + // Get the sprite with the index defined by the effects. + img_index = pcity->client.city_image; + if (img_index == -100) { + /* The server doesn't know the right value as this is from and old + * savegame. Guess here based on *client* side information as was done in + * versions where information was not saved to savegame - this should + * give us right answer of what city looked like by the time it was put + * under FoW. */ + img_index = get_city_bonus(pcity, EFT_CITY_IMAGE); + } + img_index = CLIP(0, img_index, num_thresholds - 1); + + const auto owner = + pcity->owner; // city_owner asserts when there is no owner + auto color = QColor(); + if (owner && owner->rgb) { + color.setRgb(owner->rgb->r, owner->rgb->g, owner->rgb->b); + } + return thresholds[img_index]->pixmap(color); +} +} // anonymous namespace + +/** + * Fill in the given sprite array with any needed city sprites. The city size + * is not included. + */ +std::vector +layer_city::fill_sprite_array(const tile *ptile, const tile_edge *pedge, + const tile_corner *pcorner, + const unit *punit) const +{ + if (!gui_options->draw_cities) { + return {}; + } + + auto pcity = tile_city(ptile); + if (!pcity) { + return {}; + } + + std::vector sprs; + + // The flag, if needed + if (!citybar_painter::current()->has_flag()) { + sprs.emplace_back( + tileset(), get_city_flag_sprite(tileset(), pcity), true, + tileset_full_tile_offset(tileset()) + m_city_flag_offset); + } + + auto more = fill_sprite_array_no_flag( + pcity, !citybar_painter::current()->has_units()); + sprs.insert(sprs.end(), more.begin(), more.end()); + + return sprs; +} + +/** + * Fill in the given sprite array with any city sprites. Doesn't account for + * options->draw_cities. The flag and city size are not included, and the + * occupied city indicator is only shown if requested. + */ +std::vector +layer_city::fill_sprite_array_no_flag(const city *pcity, + bool show_occupied) const +{ + auto walls = pcity->client.walls; + if (walls >= NUM_WALL_TYPES) { // Failsafe + walls = 1; + } + + std::vector sprs; + + /* For iso-view the city.wall graphics include the full city, whereas for + * non-iso view they are an overlay on top of the base city graphic. This + * leads to the mess below. */ + + // Non-isometric: city & Isometric: city without walls + if (!tileset_is_isometric(tileset()) || walls <= 0) { + sprs.emplace_back(tileset(), get_city_sprite(m_tile, pcity), true, + tileset_full_tile_offset(tileset()) + m_city_offset); + } + // Isometric: walls and city together + if (tileset_is_isometric(tileset()) && walls > 0) { + auto sprite = get_city_sprite(m_walls[walls - 1], pcity); + if (!sprite) { + sprite = get_city_sprite(m_single_wall, pcity); + } + + if (sprite) { + sprs.emplace_back(tileset(), sprite, true, + tileset_full_tile_offset(tileset()) + m_city_offset); + } + } + + // Occupied flag + if (show_occupied && pcity->client.occupied) { + sprs.emplace_back(tileset(), get_city_sprite(m_occupied, pcity), true, + tileset_full_tile_offset(tileset()) + + m_occupied_offset); + } + + // Non-isometric: add walls on top + if (!tileset_is_isometric(tileset()) && walls > 0) { + auto sprite = get_city_sprite(m_walls[walls - 1], pcity); + if (!sprite) { + sprite = get_city_sprite(m_single_wall, pcity); + } + + if (sprite) { + sprs.emplace_back(tileset(), sprite, true, + tileset_full_tile_offset(tileset())); + } + } + + // Happiness + if (pcity->client.unhappy) { + sprs.emplace_back(tileset(), m_disorder, true, + tileset_full_tile_offset(tileset())); + } else if (m_happy && pcity->client.happy) { + sprs.emplace_back(tileset(), m_happy, true, + tileset_full_tile_offset(tileset())); + } + + return sprs; +} + +/** + * All sprites depend on the ruleset-defined city styles and need to be + * reset. + */ +void layer_city::reset_ruleset() +{ + m_tile.clear(); + + for (auto &wall : m_walls) { + wall.clear(); + } + m_single_wall.clear(); + m_occupied.clear(); +} + +} // namespace freeciv diff --git a/client/tileset/layer_city.h b/client/tileset/layer_city.h new file mode 100644 index 0000000000..05a366f8a1 --- /dev/null +++ b/client/tileset/layer_city.h @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPLv3-or-later +// SPDX-FileCopyrightText: Freeciv authors +// SPDX-FileCopyrightText: Freeciv21 authors +// SPDX-FileCopyrightText: Louis Moureaux + +#pragma once + +#include "layer.h" +#include "utils/colorizer.h" + +#include + +#include +#include + +class QPixmap; + +namespace freeciv { + +class layer_city : public layer { +public: + using styles = std::vector>; + using city_sprite = std::vector; + constexpr static int NUM_WALL_TYPES = 7; + + explicit layer_city(struct tileset *ts, const QPoint &city_offset, + const QPoint &city_flag_offset, + const QPoint &occupied_offset); + virtual ~layer_city() = default; + + void load_sprites() override; + + void initialize_city_style(const citystyle &style, int index) override; + + std::vector + fill_sprite_array(const tile *ptile, const tile_edge *pedge, + const tile_corner *pcorner, + const unit *punit) const override; + + std::vector + fill_sprite_array_no_flag(const city *pcity, bool show_occupied) const; + + /** + * Returns an example of what a city might look like. + */ + QPixmap sample_sprite(int style_index = 0) const + { + // Pick the largest size -- looks more interesting. + return m_tile[style_index].back().get()->base(); + } + + void reset_ruleset() override; + +private: + layer_city::styles load_city_size_sprites(const QString &tag, + const citystyle &style); + + QPoint m_city_offset, m_city_flag_offset, m_occupied_offset; + QPixmap *m_disorder, *m_happy; + + city_sprite m_tile, m_single_wall, m_occupied; + std::array m_walls; +}; + +} // namespace freeciv diff --git a/client/tileset/layer_city_size.cpp b/client/tileset/layer_city_size.cpp new file mode 100644 index 0000000000..f3a826a401 --- /dev/null +++ b/client/tileset/layer_city_size.cpp @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPLv3-or-later +// SPDX-FileCopyrightText: Freeciv authors +// SPDX-FileCopyrightText: Freeciv21 authors +// SPDX-FileCopyrightText: Louis Moureaux + +#include "layer_city_size.h" + +#include "citybar.h" +#include "tilespec.h" + +namespace freeciv { + +layer_city_size::layer_city_size(struct tileset *ts, const QPoint &offset) + : freeciv::layer(ts, LAYER_CITY2), m_offset(offset) +{ +} + +void layer_city_size::load_sprites() +{ + // Digit sprites. We have a cascade of fallbacks. + QStringList patterns = {QStringLiteral("city.size_%1")}; + assign_digit_sprites(tileset(), m_units.data(), m_tens.data(), + m_hundreds.data(), patterns); +} + +/** + * Fill in the given sprite array with any needed city size sprites. + */ +std::vector +layer_city_size::fill_sprite_array(const tile *ptile, const tile_edge *pedge, + const tile_corner *pcorner, + const unit *punit) const +{ + if (!gui_options->draw_cities || citybar_painter::current()->has_size()) { + return {}; + } + + auto pcity = tile_city(ptile); + if (!pcity) { + return {}; + } + + // Now draw + std::vector sprs; + auto size = city_size_get(pcity); + + // Units + auto sprite = m_units[size % NUM_TILES_DIGITS]; + sprs.emplace_back(tileset(), sprite, false, m_offset); + + // Tens + size /= NUM_TILES_DIGITS; + if (size > 0 && (sprite = m_tens[size % NUM_TILES_DIGITS])) { + sprs.emplace_back(tileset(), sprite, false, m_offset); + } + + // Hundreds (optional) + size /= NUM_TILES_DIGITS; + if (size > 0 && (sprite = m_hundreds[size % NUM_TILES_DIGITS])) { + sprs.emplace_back(tileset(), sprite, false, m_offset); + // Divide for the warning: warn for thousands if we had a hundreds sprite + size /= NUM_TILES_DIGITS; + } + + // Warn if the city is too large (only once by tileset). + if (size > 0 && !m_warned) { + tileset_error( + tileset(), QtWarningMsg, + _("Tileset \"%s\" doesn't support big city sizes, such as %d. " + "Size not displayed as expected."), + tileset_name_get(tileset()), size); + m_warned = true; + } + + return sprs; +} + +} // namespace freeciv diff --git a/client/tileset/layer_city_size.h b/client/tileset/layer_city_size.h new file mode 100644 index 0000000000..93ba8d6067 --- /dev/null +++ b/client/tileset/layer_city_size.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPLv3-or-later +// SPDX-FileCopyrightText: Freeciv authors +// SPDX-FileCopyrightText: Freeciv21 authors +// SPDX-FileCopyrightText: Louis Moureaux + +#pragma once + +#include "layer.h" + +#include + +#include + +class QPixmap; + +namespace freeciv { + +class layer_city_size : public layer { +public: + explicit layer_city_size(struct tileset *ts, const QPoint &offset); + virtual ~layer_city_size() = default; + + void load_sprites() override; + + std::vector + fill_sprite_array(const tile *ptile, const tile_edge *pedge, + const tile_corner *pcorner, + const unit *punit) const override; + +private: + mutable bool m_warned = false; ///< Did we warn the user? + + QPoint m_offset; + std::array m_units, m_tens, m_hundreds; +}; + +} // namespace freeciv diff --git a/client/tileset/layer_darkness.cpp b/client/tileset/layer_darkness.cpp index cec94cc13e..da04aefdf9 100644 --- a/client/tileset/layer_darkness.cpp +++ b/client/tileset/layer_darkness.cpp @@ -14,8 +14,6 @@ #include "layer_darkness.h" #include "climap.h" -#include "control.h" -#include "extras.h" #include "game.h" #include "map.h" #include "sprite_g.h" @@ -23,11 +21,73 @@ namespace freeciv { -layer_darkness::layer_darkness(struct tileset *ts) - : freeciv::layer(ts, LAYER_DARKNESS), m_style(DARKNESS_NONE), m_sprites{} +layer_darkness::layer_darkness(struct tileset *ts, darkness_style style) + : freeciv::layer(ts, LAYER_DARKNESS), m_style(style), m_sprites{} { } +void layer_darkness::load_sprites() +{ + switch (m_style) { + case freeciv::DARKNESS_NONE: + // Nothing. + break; + case freeciv::DARKNESS_ISORECT: { + // Isometric: take a single tx.darkness tile and split it into 4. + QPixmap *darkness = load_sprite({QStringLiteral("tx.darkness")}); + const int ntw = tileset_tile_width(tileset()), + nth = tileset_tile_height(tileset()); + int offsets[4][2] = { + {ntw / 2, 0}, {0, nth / 2}, {ntw / 2, nth / 2}, {0, 0}}; + + if (!darkness) { + tileset_error(tileset(), LOG_FATAL, _("Sprite tx.darkness missing.")); + } + for (int i = 0; i < 4; i++) { + const auto sprite = std::unique_ptr( + crop_sprite(darkness, offsets[i][0], offsets[i][1], ntw / 2, + nth / 2, nullptr, 0, 0)); + set_sprite(i, *sprite); + } + } break; + case freeciv::DARKNESS_CARD_SINGLE: + for (int i = 0; i < tileset_num_cardinal_dirs(tileset()); i++) { + enum direction8 dir = tileset_cardinal_dirs(tileset())[i]; + + auto buffer = + QStringLiteral("tx.darkness_%1").arg(dir_get_tileset_name(dir)); + + const auto sprite = load_sprite({buffer}); + if (sprite) { + set_sprite(i, *sprite); + } else { + tileset_error(tileset(), LOG_FATAL, + _("Sprite for tag '%s' missing."), + qUtf8Printable(buffer)); + } + } + break; + case freeciv::DARKNESS_CARD_FULL: + for (int i = 1; tileset_num_index_cardinals(tileset()); i++) { + auto buffer = QStringLiteral("tx.darkness_%1") + .arg(cardinal_index_str(tileset(), i)); + + const auto sprite = load_sprite({buffer}); + if (sprite) { + set_sprite(i, *sprite); + } else { + tileset_error(tileset(), LOG_FATAL, + _("Sprite for tag '%s' missing."), + qUtf8Printable(buffer)); + } + } + break; + case freeciv::DARKNESS_CORNER: + // Handled in LAYER_FOG. + break; + }; +} + std::vector layer_darkness::fill_sprite_array(const tile *ptile, const tile_edge *pedge, const tile_corner *pcorner, @@ -95,7 +155,7 @@ layer_darkness::fill_sprite_array(const tile *ptile, const tile_edge *pedge, } break; case DARKNESS_CORNER: - // Handled separately. + // Handled in LAYER_FOG. break; }; diff --git a/client/tileset/layer_darkness.h b/client/tileset/layer_darkness.h index dbf592c1fd..99089c48c8 100644 --- a/client/tileset/layer_darkness.h +++ b/client/tileset/layer_darkness.h @@ -45,19 +45,20 @@ namespace freeciv { class layer_darkness : public layer { public: - explicit layer_darkness(struct tileset *ts); + explicit layer_darkness(struct tileset *ts, darkness_style style); virtual ~layer_darkness() = default; /** - * Sets the way in which the darkness is drawn. + * Loads all the sprites needed to draw the darkness. */ - void set_darkness_style(darkness_style style) { m_style = style; } + void load_sprites() override; - /** - * Gets the way in which the darkness is drawn. - */ - darkness_style style() const { return m_style; } + std::vector + fill_sprite_array(const tile *ptile, const tile_edge *pedge, + const tile_corner *pcorner, + const unit *punit) const override; +private: /** * Sets one of the sprites used to draw the darkness. */ @@ -66,12 +67,6 @@ class layer_darkness : public layer { m_sprites[index] = p; } - std::vector - fill_sprite_array(const tile *ptile, const tile_edge *pedge, - const tile_corner *pcorner, - const unit *punit) const override; - -private: darkness_style m_style; // First unused diff --git a/client/tileset/layer_editor.cpp b/client/tileset/layer_editor.cpp new file mode 100644 index 0000000000..63099eb5ea --- /dev/null +++ b/client/tileset/layer_editor.cpp @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPLv3-or-later +// SPDX-FileCopyrightText: Freeciv authors +// SPDX-FileCopyrightText: Freeciv21 authors +// SPDX-FileCopyrightText: Louis Moureaux + +#include "layer_editor.h" + +#include "editor.h" +#include "map.h" +#include "sprite_g.h" +#include "tilespec.h" + +#include + +namespace freeciv { + +layer_editor::layer_editor(struct tileset *ts) + : freeciv::layer(ts, LAYER_EDITOR) +{ +} + +void layer_editor::load_sprites() +{ + // FIXME: Use better sprites. + auto color = load_sprite({"colors.overlay_0"}, true); + auto unworked = load_sprite({"mask.unworked_tile"}, true); + + auto W = tileset_tile_width(tileset()), H = tileset_tile_height(tileset()); + auto mask = std::unique_ptr( + crop_sprite(color, 0, 0, W, H, get_mask_sprite(tileset()), 0, 0)); + auto selected = std::unique_ptr( + crop_sprite(mask.get(), 0, 0, W, H, unworked, 0, 0)); + m_selected = *selected; + + // FIXME: Use a more representative sprite. + m_starting_position = load_sprite({"user.attention"}, true); +} + +std::vector +layer_editor::fill_sprite_array(const tile *ptile, const tile_edge *pedge, + const tile_corner *pcorner, + const unit *punit) const +{ + if (!ptile && !editor_is_active()) { + return {}; + } + + std::vector sprs; + + if (editor_tile_is_selected(ptile)) { + sprs.emplace_back(tileset(), &m_selected); + } + + if (nullptr != map_startpos_get(ptile)) { + sprs.emplace_back(tileset(), m_starting_position); + } + + return sprs; +} + +} // namespace freeciv diff --git a/client/tileset/layer_editor.h b/client/tileset/layer_editor.h new file mode 100644 index 0000000000..0a554de5d0 --- /dev/null +++ b/client/tileset/layer_editor.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPLv3-or-later +// SPDX-FileCopyrightText: Freeciv authors +// SPDX-FileCopyrightText: Freeciv21 authors +// SPDX-FileCopyrightText: Louis Moureaux + +#pragma once + +#include "layer.h" + +#include + +namespace freeciv { + +class layer_editor : public layer { +public: + explicit layer_editor(struct tileset *ts); + virtual ~layer_editor() = default; + + void load_sprites() override; + + std::vector + fill_sprite_array(const tile *ptile, const tile_edge *pedge, + const tile_corner *pcorner, + const unit *punit) const override; + +private: + QPixmap m_selected, *m_starting_position; +}; + +} // namespace freeciv diff --git a/client/tileset/layer_fog.cpp b/client/tileset/layer_fog.cpp new file mode 100644 index 0000000000..a0543693f5 --- /dev/null +++ b/client/tileset/layer_fog.cpp @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPLv3-or-later +// SPDX-FileCopyrightText: Freeciv authors +// SPDX-FileCopyrightText: Freeciv21 authors +// SPDX-FileCopyrightText: Louis Moureaux + +#include "layer_fog.h" + +#include "climap.h" +#include "colors_common.h" +#include "tilespec.h" + +namespace freeciv { + +layer_fog::layer_fog(struct tileset *ts, fog_style style, + darkness_style darkness) + : freeciv::layer(ts, LAYER_FOG), m_style(style), m_darkness(darkness) +{ +} + +void layer_fog::load_sprites() +{ + m_fog_sprite = load_sprite({"tx.fog"}, true); + + if (m_darkness == DARKNESS_CORNER) { + for (int i = 0; i < 81 /* 3^4 */; i++) { + // Unknown, fog, known. + const QChar ids[] = {'u', 'f', 'k'}; + auto buf = QStringLiteral("t.fog"); + int values[4], k = i; + + for (int vi = 0; vi < 4; vi++) { + values[vi] = k % 3; + k /= 3; + + buf += QStringLiteral("_") + ids[values[vi]]; + } + fc_assert(k == 0); + + m_darkness_sprites.push_back(load_sprite({buf})); + } + } +} + +std::vector +layer_fog::fill_sprite_array(const tile *ptile, const tile_edge *pedge, + const tile_corner *pcorner, + const unit *punit) const +{ + std::vector sprs; + + if (m_style == FOG_SPRITE && gui_options->draw_fog_of_war && ptile + && client_tile_get_known(ptile) == TILE_KNOWN_UNSEEN) { + sprs.emplace_back(tileset(), m_darkness_sprites[0]); + } + + if (m_darkness == DARKNESS_CORNER && pcorner + && gui_options->draw_fog_of_war) { + int tileno = 0; + + for (int i = 3; i >= 0; i--) { + const int unknown = 0, fogged = 1, known = 2; + int value = -1; + + if (!pcorner->tile[i]) { + value = fogged; + } else { + switch (client_tile_get_known(pcorner->tile[i])) { + case TILE_KNOWN_SEEN: + value = known; + break; + case TILE_KNOWN_UNSEEN: + value = fogged; + break; + case TILE_UNKNOWN: + value = unknown; + break; + } + } + fc_assert(value >= 0 && value < 3); + + tileno = tileno * 3 + value; + } + + if (m_darkness_sprites[tileno]) { + sprs.emplace_back(tileset(), m_darkness_sprites[tileno]); + } + } + + return sprs; +} + +} // namespace freeciv diff --git a/client/tileset/layer_fog.h b/client/tileset/layer_fog.h new file mode 100644 index 0000000000..8067bc4c88 --- /dev/null +++ b/client/tileset/layer_fog.h @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPLv3-or-later +// SPDX-FileCopyrightText: Freeciv authors +// SPDX-FileCopyrightText: Freeciv21 authors +// SPDX-FileCopyrightText: Louis Moureaux + +#pragma once + +#include "layer.h" +#include "layer_darkness.h" + +#include + +#include + +namespace freeciv { + +#define SPECENUM_NAME fog_style +// Fog is automatically appended by the code. +#define SPECENUM_VALUE0 FOG_AUTO +#define SPECENUM_VALUE0NAME "Auto" +// A single fog sprite is provided by the tileset (tx.fog). +#define SPECENUM_VALUE1 FOG_SPRITE +#define SPECENUM_VALUE1NAME "Sprite" +// No fog, or fog derived from darkness style. +#define SPECENUM_VALUE2 FOG_DARKNESS +#define SPECENUM_VALUE2NAME "Darkness" +#include "specenum_gen.h" + +class layer_fog : public layer { +public: + explicit layer_fog(struct tileset *ts, fog_style style, + darkness_style darkness); + virtual ~layer_fog() = default; + + void load_sprites() override; + + std::vector + fill_sprite_array(const tile *ptile, const tile_edge *pedge, + const tile_corner *pcorner, + const unit *punit) const override; + +private: + fog_style m_style; + darkness_style m_darkness; + QPixmap *m_fog_sprite; + std::vector m_darkness_sprites; //< For darkness style = CORNER +}; + +} // namespace freeciv diff --git a/client/tileset/layer_goto.cpp b/client/tileset/layer_goto.cpp new file mode 100644 index 0000000000..7ea3d414ef --- /dev/null +++ b/client/tileset/layer_goto.cpp @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPLv3-or-later +// SPDX-FileCopyrightText: Freeciv authors +// SPDX-FileCopyrightText: Freeciv21 authors +// SPDX-FileCopyrightText: Louis Moureaux + +#include "layer_goto.h" + +#include "goto.h" +#include "tilespec.h" + +namespace freeciv { + +layer_goto::layer_goto(struct tileset *ts) : freeciv::layer(ts, LAYER_GOTO) +{ +} + +void layer_goto::load_sprites() +{ + // Digit sprites. We have a cascade of fallbacks. + QStringList patterns = {QStringLiteral("path.turns_%1"), + QStringLiteral("city.size_%1")}; + assign_digit_sprites(tileset(), m_states[GTS_MP_LEFT].turns.data(), + m_states[GTS_MP_LEFT].turns_tens.data(), + m_states[GTS_MP_LEFT].turns_hundreds.data(), + patterns); + patterns.prepend(QStringLiteral("path.steps_%1")); + assign_digit_sprites(tileset(), m_states[GTS_TURN_STEP].turns.data(), + m_states[GTS_TURN_STEP].turns_tens.data(), + m_states[GTS_TURN_STEP].turns_hundreds.data(), + patterns); + patterns.prepend(QStringLiteral("path.exhausted_mp_%1")); + assign_digit_sprites(tileset(), m_states[GTS_EXHAUSTED_MP].turns.data(), + m_states[GTS_EXHAUSTED_MP].turns_tens.data(), + m_states[GTS_EXHAUSTED_MP].turns_hundreds.data(), + patterns); + + // Turn steps + m_states[GTS_MP_LEFT].specific = load_sprite({"path.normal"}); + m_states[GTS_EXHAUSTED_MP].specific = load_sprite({"path.exhausted_mp"}); + m_states[GTS_TURN_STEP].specific = load_sprite({"path.step"}); + + // Waypoint + m_waypoint = load_sprite({"path.waypoint"}, true); +} + +/** + * Fill in the given sprite array with any needed goto sprites. + */ +std::vector +layer_goto::fill_sprite_array(const tile *ptile, const tile_edge *pedge, + const tile_corner *pcorner, + const unit *punit) const +{ + if (!ptile || !goto_is_active()) { + return {}; + } + + std::vector sprs; + + enum goto_tile_state state; + int length; + bool waypoint; + if (!goto_tile_state(ptile, &state, &length, &waypoint)) { + return {}; + } + + if (length >= 0) { + fc_assert_ret_val(state >= 0, {}); + fc_assert_ret_val(state < m_states.size(), {}); + + if (auto sprite = m_states[state].specific) { + sprs.emplace_back(tileset(), sprite, false, 0, 0); + } + + // Units + auto sprite = m_states[state].turns[length % NUM_TILES_DIGITS]; + sprs.emplace_back(tileset(), sprite); + + // Tens + length /= NUM_TILES_DIGITS; + if (length > 0 + && (sprite = + m_states[state].turns_tens[length % NUM_TILES_DIGITS])) { + sprs.emplace_back(tileset(), sprite); + } + + // Hundreds (optional) + length /= NUM_TILES_DIGITS; + if (length > 0 + && (sprite = + m_states[state].turns_hundreds[length % NUM_TILES_DIGITS])) { + sprs.emplace_back(tileset(), sprite); + // Divide for the warning: warn for thousands if we had a hundreds + // sprite + length /= NUM_TILES_DIGITS; + } + + // Warn if the path is too long (only once by tileset). + if (length > 0 && !m_warned) { + tileset_error( + tileset(), QtWarningMsg, + _("Tileset \"%s\" doesn't support long goto paths, such as %d. " + "Path not displayed as expected."), + tileset_name_get(tileset()), length); + m_warned = true; + } + } + + if (waypoint) { + sprs.emplace_back(tileset(), m_waypoint, false, 0, 0); + } + + return sprs; +} + +} // namespace freeciv diff --git a/client/tileset/layer_goto.h b/client/tileset/layer_goto.h new file mode 100644 index 0000000000..5685e5b2fd --- /dev/null +++ b/client/tileset/layer_goto.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPLv3-or-later +// SPDX-FileCopyrightText: Freeciv authors +// SPDX-FileCopyrightText: Freeciv21 authors +// SPDX-FileCopyrightText: Louis Moureaux + +#pragma once + +#include "goto.h" +#include "layer.h" + +#include + +class QPixmap; + +namespace freeciv { + +class layer_goto : public layer { +public: + explicit layer_goto(struct tileset *ts); + virtual ~layer_goto() = default; + + void load_sprites() override; + + std::vector + fill_sprite_array(const tile *ptile, const tile_edge *pedge, + const tile_corner *pcorner, + const unit *punit) const override; + +private: + mutable bool m_warned = false; ///< Did we warn the user? + QPixmap *m_waypoint; + + struct tile_state_sprites { + QPixmap *specific; + std::array turns, turns_tens, + turns_hundreds; + }; + std::array m_states; +}; + +} // namespace freeciv diff --git a/client/tileset/layer_grid.cpp b/client/tileset/layer_grid.cpp new file mode 100644 index 0000000000..d8f6ce374b --- /dev/null +++ b/client/tileset/layer_grid.cpp @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPLv3-or-later +// SPDX-FileCopyrightText: Freeciv authors +// SPDX-FileCopyrightText: Freeciv21 authors +// SPDX-FileCopyrightText: Louis Moureaux + +#include "layer_grid.h" + +#include "citydlg_g.h" +#include "client_main.h" +#include "climap.h" +#include "control.h" +#include "movement.h" +#include "sprite_g.h" +#include "tilespec.h" +#include "views/view_map_common.h" // mapdeco + +#include + +namespace freeciv { + +layer_grid::layer_grid(struct tileset *ts, mapview_layer layer) + : freeciv::layer(ts, layer) +{ +} + +void layer_grid::load_sprites() +{ + // This must correspond to enum edge_type. + const auto edge_name = + std::array{QStringLiteral("ns"), QStringLiteral("we"), + QStringLiteral("ud"), QStringLiteral("lr")}; + + m_unavailable = load_sprite({"grid.unavailable"}, true); + m_nonnative = load_sprite({"grid.nonnative"}, false); + + for (int i = 0; i < EDGE_COUNT; i++) { + int be; + + if (i == EDGE_UD && tileset_hex_width(tileset()) == 0) { + continue; + } else if (i == EDGE_LR && tileset_hex_height(tileset()) == 0) { + continue; + } + + auto name = QStringLiteral("grid.main.%1").arg(edge_name[i]); + m_main[i] = load_sprite({name}, true); + + name = QStringLiteral("grid.city.%1").arg(edge_name[i]); + m_city[i] = load_sprite({name}, true); + + name = QStringLiteral("grid.worked.%1").arg(edge_name[i]); + m_worked[i] = load_sprite({name}, true); + + name = QStringLiteral("grid.selected.%1").arg(edge_name[i]); + m_selected[i] = load_sprite({name}, true); + + for (be = 0; be < 2; be++) { + name = QStringLiteral("grid.borders.%1").arg(edge_name[i][be]); + m_basic_borders[i][be] = load_sprite({name}, true); + } + } +} + +/** + * Initializes border data for a player. + */ +void layer_grid::initialize_player(const player *player) +{ + // Get the color. In pregame, players have no color so we just use red. + const auto c = player_has_color(tileset(), player) + ? get_player_color(tileset(), player) + : Qt::red; + + QPixmap color(tileset_tile_width(tileset()), + tileset_tile_height(tileset())); + color.fill(c); + + for (int i = 0; i < EDGE_COUNT; i++) { + for (int j = 0; j < 2; j++) { + if (m_basic_borders[i][j]) { + m_borders[player_index(player)][i][j].reset(crop_sprite( + &color, 0, 0, tileset_tile_width(tileset()), + tileset_tile_height(tileset()), m_basic_borders[i][j], 0, 0)); + } + } + } +} + +/** + * Frees border data for a player. + */ +void layer_grid::free_player(int player_id) +{ + for (auto &edge : m_borders.at(player_id)) { + for (auto &pix : edge) { + pix = nullptr; + } + } +} + +/** + * Fill in the grid sprites for the given tile, city, and unit. + */ +std::vector +layer_grid::fill_sprite_array(const tile *ptile, const tile_edge *pedge, + const tile_corner *pcorner, + const unit *punit) const +{ + // LAYER_GRID1 is used for isometric tilesets only + if (tileset_is_isometric(tileset()) && type() != LAYER_GRID1) { + return {}; + } + + // LAYER_GRID2 is used for non-isometric tilesets only + if (!tileset_is_isometric(tileset()) && type() != LAYER_GRID2) { + return {}; + } + + const auto citymode = is_any_city_dialog_open(); + + std::vector sprs; + + if (pedge) { + bool known[NUM_EDGE_TILES], city[NUM_EDGE_TILES]; + bool unit[NUM_EDGE_TILES], worked[NUM_EDGE_TILES]; + + for (int i = 0; i < NUM_EDGE_TILES; i++) { + int dummy_x, dummy_y; + const struct tile *tile = pedge->tile[i]; + struct player *powner = tile ? tile_owner(tile) : nullptr; + + known[i] = tile && client_tile_get_known(tile) != TILE_UNKNOWN; + unit[i] = false; + if (tile && !citymode) { + for (const auto pfocus_unit : get_units_in_focus()) { + if (unit_drawn_with_city_outline(pfocus_unit, false)) { + struct tile *utile = unit_tile(pfocus_unit); + int radius = game.info.init_city_radius_sq + + get_target_bonus_effects( + nullptr, unit_owner(pfocus_unit), nullptr, + nullptr, nullptr, utile, nullptr, nullptr, + nullptr, nullptr, nullptr, EFT_CITY_RADIUS_SQ); + + if (city_tile_to_city_map(&dummy_x, &dummy_y, radius, utile, + tile)) { + unit[i] = true; + break; + } + } + } + } + worked[i] = false; + + city[i] = (tile + && (powner == nullptr || client_player() == nullptr + || powner == client.conn.playing) + && player_in_city_map(client.conn.playing, tile)); + if (city[i]) { + if (citymode) { + /* In citymode, we only draw worked tiles for this city - other + * tiles may be marked as unavailable. */ + worked[i] = (tile_worked(tile) == citymode); + } else { + worked[i] = (nullptr != tile_worked(tile)); + } + } + // Draw city grid for main citymap + if (tile && citymode + && city_base_to_city_map(&dummy_x, &dummy_y, citymode, tile)) { + sprs.emplace_back(tileset(), m_selected[pedge->type]); + } + } + if (mapdeco_is_highlight_set(pedge->tile[0]) + || mapdeco_is_highlight_set(pedge->tile[1])) { + sprs.emplace_back(tileset(), m_selected[pedge->type]); + } else { + if (gui_options->draw_map_grid) { + if (worked[0] || worked[1]) { + sprs.emplace_back(tileset(), m_worked[pedge->type]); + } else if (city[0] || city[1]) { + sprs.emplace_back(tileset(), m_city[pedge->type]); + } else if (known[0] || known[1]) { + sprs.emplace_back(tileset(), m_main[pedge->type]); + } + } + if (gui_options->draw_city_outlines) { + if (XOR(city[0], city[1])) { + sprs.emplace_back(tileset(), m_city[pedge->type]); + } + if (XOR(unit[0], unit[1])) { + sprs.emplace_back(tileset(), m_worked[pedge->type]); + } + } + } + + if (gui_options->draw_borders && BORDERS_DISABLED != game.info.borders + && known[0] && known[1]) { + struct player *owner0 = tile_owner(pedge->tile[0]); + struct player *owner1 = tile_owner(pedge->tile[1]); + + if (owner0 != owner1) { + if (owner0) { + int plrid = player_index(owner0); + sprs.emplace_back(tileset(), + m_borders[plrid][pedge->type][0].get()); + } + if (owner1) { + int plrid = player_index(owner1); + sprs.emplace_back(tileset(), + m_borders[plrid][pedge->type][1].get()); + } + } + } + } else if (nullptr != ptile + && TILE_UNKNOWN != client_tile_get_known(ptile)) { + int cx, cy; + + if (citymode + // test to ensure valid coordinates? + && city_base_to_city_map(&cx, &cy, citymode, ptile) + && !client_city_can_work_tile(citymode, ptile)) { + sprs.emplace_back(tileset(), m_unavailable); + } + + if (gui_options->draw_native && citymode == nullptr) { + bool native = true; + for (const auto pfocus : get_units_in_focus()) { + if (!is_native_tile(unit_type_get(pfocus), ptile)) { + native = false; + break; + } + } + + if (!native) { + if (m_nonnative != nullptr) { + sprs.emplace_back(tileset(), m_nonnative); + } else { + sprs.emplace_back(tileset(), m_unavailable); + } + } + } + } + + return sprs; +} + +} // namespace freeciv diff --git a/client/tileset/layer_grid.h b/client/tileset/layer_grid.h new file mode 100644 index 0000000000..c21dae9164 --- /dev/null +++ b/client/tileset/layer_grid.h @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPLv3-or-later +// SPDX-FileCopyrightText: Freeciv authors +// SPDX-FileCopyrightText: Freeciv21 authors +// SPDX-FileCopyrightText: Louis Moureaux + +#pragma once + +#include "fc_types.h" +#include "layer.h" + +#include +#include + +class QPixmap; + +namespace freeciv { + +class layer_grid : public layer { +public: + explicit layer_grid(struct tileset *ts, mapview_layer layer); + virtual ~layer_grid() = default; + + void load_sprites() override; + + std::vector + fill_sprite_array(const tile *ptile, const tile_edge *pedge, + const tile_corner *pcorner, + const unit *punit) const override; + + void initialize_player(const player *player) override; + void free_player(int player_id) override; + +private: + std::array m_city = {nullptr}, m_main = {nullptr}, + m_selected = {nullptr}, + m_worked = {nullptr}; + QPixmap *m_unavailable, *m_nonnative; + + // Without player colors + using edge_data = std::array; + std::array m_basic_borders = { + edge_data{nullptr, nullptr}}; + + // With player colors + using unique_edge_data = std::array, 2>; + /// Indices: [player][edge][in/out] + std::array, MAX_NUM_PLAYER_SLOTS> + m_borders; +}; + +} // namespace freeciv diff --git a/client/tileset/layer_infrawork.cpp b/client/tileset/layer_infrawork.cpp new file mode 100644 index 0000000000..5fb7cbbb17 --- /dev/null +++ b/client/tileset/layer_infrawork.cpp @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPLv3-or-later +// SPDX-FileCopyrightText: Freeciv authors +// SPDX-FileCopyrightText: Freeciv21 authors +// SPDX-FileCopyrightText: Louis Moureaux + +#include "layer_infrawork.h" + +#include "citydlg_g.h" +#include "tilespec.h" +#include "workertask.h" + +/** + * \class freeciv::layer_infrawork + * \brief Draws infrastructure (extras) being placed. + */ + +namespace freeciv { + +/** + * Constructor + */ +layer_infrawork::layer_infrawork(struct tileset *ts, + const QPoint &activity_offset) + : freeciv::layer_abstract_activities(ts, LAYER_INFRAWORK), + m_activity_offset(activity_offset) +{ +} + +std::vector +layer_infrawork::fill_sprite_array(const tile *ptile, const tile_edge *pedge, + const tile_corner *pcorner, + const unit *punit) const +{ + Q_UNUSED(pedge); + Q_UNUSED(pcorner); + + // Should we draw anything in the first place? + if (!ptile || !ptile->placing) { + return {}; + } + + // Now we draw + std::vector sprs; + + if (auto sprite = activity_sprite(ACTIVITY_IDLE, ptile->placing)) { + sprs.emplace_back(tileset(), sprite, true, + tileset_full_tile_offset(tileset()) + + m_activity_offset); + } + + return sprs; +} + +} // namespace freeciv diff --git a/client/tileset/layer_infrawork.h b/client/tileset/layer_infrawork.h new file mode 100644 index 0000000000..016223eae2 --- /dev/null +++ b/client/tileset/layer_infrawork.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPLv3-or-later +// SPDX-FileCopyrightText: Freeciv authors +// SPDX-FileCopyrightText: Freeciv21 authors +// SPDX-FileCopyrightText: Louis Moureaux + +#pragma once + +#include "fc_types.h" +#include "layer_abstract_activities.h" +#include "unit.h" + +#include + +namespace freeciv { + +class layer_infrawork : public layer_abstract_activities { +public: + explicit layer_infrawork(struct tileset *ts, + const QPoint &activity_offset); + virtual ~layer_infrawork() = default; + + std::vector + fill_sprite_array(const tile *ptile, const tile_edge *pedge, + const tile_corner *pcorner, + const unit *punit) const override; + +private: + QPoint m_activity_offset; +}; + +} // namespace freeciv diff --git a/client/tileset/layer_overlays.cpp b/client/tileset/layer_overlays.cpp new file mode 100644 index 0000000000..36ee618fd4 --- /dev/null +++ b/client/tileset/layer_overlays.cpp @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPLv3-or-later +// SPDX-FileCopyrightText: Freeciv authors +// SPDX-FileCopyrightText: Freeciv21 authors +// SPDX-FileCopyrightText: Louis Moureaux + +#include "layer_overlays.h" + +#include "citydlg_g.h" +#include "climap.h" +#include "log.h" +#include "sprite_g.h" +#include "tilespec.h" +#include "views/view_map_common.h" + +namespace freeciv { + +layer_overlays::layer_overlays(struct tileset *ts) + : freeciv::layer(ts, LAYER_OVERLAYS) +{ +} + +void layer_overlays::load_sprites() +{ + // Tile output counters + for (int i = 0;; ++i) { + auto buffer = QStringLiteral("city.t_food_%1").arg(i); + if (auto sprite = load_sprite({buffer})) { + m_food.push_back(sprite); + } else { + break; + } + } + for (int i = 0;; ++i) { + auto buffer = QStringLiteral("city.t_shields_%1").arg(i); + if (auto sprite = load_sprite({buffer})) { + m_prod.push_back(sprite); + } else { + break; + } + } + for (int i = 0;; ++i) { + auto buffer = QStringLiteral("city.t_trade_%1").arg(i); + if (auto sprite = load_sprite({buffer})) { + m_trade.push_back(sprite); + } else { + break; + } + } + + // Overlay colors + std::vector colors; + for (int i = 0;; ++i) { + auto buffer = QStringLiteral("colors.overlay_%1").arg(i); + auto sprite = load_sprite({buffer}); + if (!sprite) { + break; + } + colors.push_back(sprite); + } + if (colors.empty()) { + tileset_error(tileset(), LOG_FATAL, + _("Missing overlay-color sprite colors.overlay_0.")); + } + + auto worked_base = load_sprite({"mask.worked_tile"}, true); + auto unworked_base = load_sprite({"mask.unworked_tile"}, true); + + // Chop up and build the overlay graphics. + auto W = tileset_tile_width(tileset()), H = tileset_tile_height(tileset()); + for (const auto &color : colors) { + auto color_mask = std::unique_ptr( + crop_sprite(color, 0, 0, W, H, get_mask_sprite(tileset()), 0, 0)); + m_worked.emplace_back( + crop_sprite(color_mask.get(), 0, 0, W, H, worked_base, 0, 0)); + m_unworked.emplace_back( + crop_sprite(color_mask.get(), 0, 0, W, H, unworked_base, 0, 0)); + } +} + +/** + * Fill in the given sprite array with any needed overlays sprites. + */ +std::vector +layer_overlays::fill_sprite_array(const tile *ptile, const tile_edge *pedge, + const tile_corner *pcorner, + const unit *punit) const +{ + std::vector sprs; + + if (!ptile || client_tile_get_known(ptile) == TILE_UNKNOWN) { + return {}; + } + + unit *psettler = nullptr; + const auto citymode = is_any_city_dialog_open(); + auto pcity = + citymode ? citymode : find_city_or_settler_near_tile(ptile, &psettler); + + // The code below does not work if pcity is invisible. Make sure it is not. + fc_assert_action(!pcity || pcity->tile, pcity = nullptr); + + int city_x, city_y; + if (pcity && city_base_to_city_map(&city_x, &city_y, pcity, ptile)) { + const auto pwork = tile_worked(ptile); + if (!citymode && pcity->client.colored) { + // Add citymap overlay for a city. + int idx = pcity->client.color_index % m_worked.size(); + if (pwork && pwork == pcity) { + sprs.emplace_back(tileset(), m_worked[idx].get()); + } else if (city_can_work_tile(pcity, ptile)) { + sprs.emplace_back(tileset(), m_unworked[idx].get()); + } + } else if (pwork && pwork == pcity + && (citymode || gui_options->draw_city_output)) { + // Add on the tile output sprites. + const int ox = tileset_is_isometric(tileset()) + ? tileset_tile_width(tileset()) / 3 + : 0; + const int oy = tileset_is_isometric(tileset()) + ? -tileset_tile_height(tileset()) / 3 + : 0; + + if (!m_food.empty()) { + int food = city_tile_output_now(pcity, ptile, O_FOOD); + food = CLIP(0, food / game.info.granularity, m_food.size() - 1); + sprs.emplace_back(tileset(), m_food[food], true, ox, oy); + } + if (!m_prod.empty()) { + int shields = city_tile_output_now(pcity, ptile, O_SHIELD); + shields = + CLIP(0, shields / game.info.granularity, m_prod.size() - 1); + sprs.emplace_back(tileset(), m_prod[shields], true, ox, oy); + } + if (!m_trade.empty()) { + int trade = city_tile_output_now(pcity, ptile, O_TRADE); + trade = CLIP(0, trade / game.info.granularity, m_trade.size() - 1); + sprs.emplace_back(tileset(), m_trade[trade], true, ox, oy); + } + } + } else if (psettler && psettler->client.colored) { + // Add citymap overlay for a unit. + auto idx = psettler->client.color_index % m_unworked.size(); + sprs.emplace_back(tileset(), m_unworked[idx].get()); + } + + // Crosshair sprite + if (mapdeco_is_crosshair_set(ptile)) { + sprs.emplace_back(tileset(), get_attention_crosshair_sprite(tileset())); + } + + return sprs; +} + +} // namespace freeciv diff --git a/client/tileset/layer_overlays.h b/client/tileset/layer_overlays.h new file mode 100644 index 0000000000..ea9345f76c --- /dev/null +++ b/client/tileset/layer_overlays.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPLv3-or-later +// SPDX-FileCopyrightText: Freeciv authors +// SPDX-FileCopyrightText: Freeciv21 authors +// SPDX-FileCopyrightText: Louis Moureaux + +#pragma once + +#include "layer.h" + +#include + +#include + +namespace freeciv { + +class layer_overlays : public layer { +public: + explicit layer_overlays(struct tileset *ts); + virtual ~layer_overlays() = default; + + void load_sprites() override; + + std::vector + fill_sprite_array(const tile *ptile, const tile_edge *pedge, + const tile_corner *pcorner, + const unit *punit) const override; + +private: + std::vector> m_worked, m_unworked; + std::vector m_food, m_prod, m_trade; +}; + +} // namespace freeciv diff --git a/client/tileset/layer_roads.cpp b/client/tileset/layer_roads.cpp new file mode 100644 index 0000000000..041c1b2468 --- /dev/null +++ b/client/tileset/layer_roads.cpp @@ -0,0 +1,507 @@ +// SPDX-License-Identifier: GPLv3-or-later +// SPDX-FileCopyrightText: Freeciv authors +// SPDX-FileCopyrightText: Freeciv21 authors +// SPDX-FileCopyrightText: Louis Moureaux + +#include "layer_roads.h" + +#include "bitvector.h" +#include "extras.h" +#include "fc_types.h" +#include "layer.h" +#include "terrain.h" +#include "tilespec.h" +#include "unit.h" + +#include + +/** + * \class freeciv::layer_roads + * \brief Draws "road" extras on the map. + * + * Road extras are those with style RoadAllSeparate, RoadParityCombined, and + * RoadAllCombined. + */ + +namespace freeciv { + +/** + * Constructor + */ +layer_roads::layer_roads(struct tileset *ts) + : freeciv::layer(ts, LAYER_ROADS) +{ +} + +/** + * Collects all extras to be drawn. + */ +void layer_roads::initialize_extra(const extra_type *extra, + const QString &tag, extrastyle_id style) +{ + // Make sure they are all sized properly. + m_all_combined.resize(terrain_count()); + m_all_separate.resize(terrain_count()); + m_parity_combined.resize(terrain_count()); + + // This would get a bit repetitive without a macro +#define INIT(vector, func) \ + /* Make sure we have enough space */ \ + terrain_type_iterate(terrain) \ + { \ + /* Add an entry for this extra */ \ + auto &data = vector[terrain_index(terrain)].emplace_back(); \ + /* Initializes it */ \ + initialize_corners(data, extra, tag, terrain); \ + func(data, tag, terrain); \ + } \ + terrain_type_iterate_end; + + if (style == ESTYLE_ROAD_ALL_COMBINED) { + INIT(m_all_combined, initialize_all_combined); + } else if (style == ESTYLE_ROAD_ALL_SEPARATE) { + INIT(m_all_separate, initialize_all_separate); + } else if (style == ESTYLE_ROAD_PARITY_COMBINED) { + INIT(m_parity_combined, initialize_parity_combined); + } +#undef INIT +} + +/** + * Initializes "corner" sprite data. + */ +void layer_roads::initialize_corners(corner_sprites &data, + const extra_type *extra, + const QString &tag, + const terrain *terrain) +{ + data.extra = extra; + + // Load special corner road sprites + for (int i = 0; i < tileset_num_valid_dirs(tileset()); ++i) { + auto dir = tileset_valid_dirs(tileset())[i]; + if (!is_cardinal_tileset_dir(tileset(), dir)) { + QString dtn = QStringLiteral("_c_%1").arg(dir_get_tileset_name(dir)); + data.corners[dir] = + load_sprite(make_tag_terrain_list(tag, dtn, terrain), false); + } + } +} + +/** + * Initializes sprite data for RoadAllCombined. + */ +void layer_roads::initialize_all_combined(all_combined_data &data, + const QString &tag, + const terrain *terrain) +{ + // Just go around clockwise, with all combinations. + auto count = 1 << tileset_num_valid_dirs(tileset()); + for (int i = 0; i < count; ++i) { + QString idx_str = + QStringLiteral("_%1").arg(valid_index_str(tileset(), i)); + data.sprites[i] = + load_sprite(make_tag_terrain_list(tag, idx_str, terrain), true); + } +} + +/** + * Initializes sprite data for RoadAllSeparate. + */ +void layer_roads::initialize_all_separate(all_separate_data &data, + const QString &tag, + const terrain *terrain) +{ + // Load the isolated sprite + data.isolated = + load_sprite(make_tag_terrain_list(tag, "_isolated", terrain), true); + + // Load the directional sprite options, one per direction + for (int i = 0; i < tileset_num_valid_dirs(tileset()); ++i) { + auto dir = tileset_valid_dirs(tileset())[i]; + QString dir_name = QStringLiteral("_%1").arg(dir_get_tileset_name(dir)); + data.sprites[i] = + load_sprite(make_tag_terrain_list(tag, dir_name, terrain), true); + } +} + +/** + * Initializes sprite data for RoadAllSeparate. + */ +void layer_roads::initialize_parity_combined(parity_combined_data &data, + const QString &tag, + const terrain *terrain) +{ + // Load the isolated sprite + data.isolated = + load_sprite(make_tag_terrain_list(tag, "_isolated", terrain), true); + + int num_index = 1 << (tileset_num_valid_dirs(tileset()) / 2); + + // Load the directional sprites + // The comment below exemplifies square tiles: + // additional sprites for each road type: 16 each for cardinal and diagonal + // directions. Each set of 16 provides a NSEW-indexed sprite to provide + // connectors for all rails in the cardinal/diagonal directions. The 0 + // entry is unused (the "isolated" sprite is used instead). */ + for (int i = 1; i < num_index; i++) { + QString cs = QStringLiteral("_c_"); + QString ds = QStringLiteral("_d_"); + + for (int j = 0; j < tileset_num_valid_dirs(tileset()) / 2; j++) { + int value = (i >> j) & 1; + cs += QStringLiteral("%1%2") + .arg(dir_get_tileset_name( + tileset_valid_dirs(tileset())[2 * j])) + .arg(value); + ds += QStringLiteral("%1%2") + .arg(dir_get_tileset_name( + tileset_valid_dirs(tileset())[2 * j + 1])) + .arg(value); + } + + data.sprites.first[i] = + load_sprite(make_tag_terrain_list(tag, cs, terrain), true); + data.sprites.second[i] = + load_sprite(make_tag_terrain_list(tag, ds, terrain), true); + } +} + +/* Define some helper functions to decide where to draw roads. */ +namespace { +/** + * Checks if the extra is hidden by another extra. + */ +bool is_hidden(const extra_type *extra, const bv_extras extras) +{ + extra_type_list_iterate(extra->hiders, hider) + { + if (BV_ISSET(extras, extra_index(hider))) { + return true; + } + } + extra_type_list_iterate_end; + + return false; +} + +/** + * Checks if an extra connects from a tile to the given direction. + */ +bool connects(const struct tileset *t, const extra_type *extra, + direction8 dir, const bv_extras extras, + const bv_extras extras_near, const terrain *terrain_near) +{ + // If the extra isn't on the starting tile, it doesn't connect. + if (!BV_ISSET(extras, extra_index(extra))) { + return false; + } + + // Some extras (e.g. rivers) only connect in cardinal directions. + if (is_cardinal_only_road(extra) + && is_cardinal_tileset_dir(t, static_cast(dir))) { + return false; + } + + // Check extras we integrate with. An extra always integrates with itself. + extra_type_list_iterate(extra_road_get(extra)->integrators, iextra) + { + if (BV_ISSET(extras_near, extra_index(iextra))) { + return true; + } + } + extra_type_list_iterate_end; + + // The extra may connect to adjacent land tiles. + return extra_has_flag(extra, EF_CONNECT_LAND) && terrain_near != T_UNKNOWN + && terrain_type_terrain_class(terrain_near) != TC_OCEAN; +} + +/** + * Checks if an extra should be drawn in a direction; that is it connects to + * the other tile and and is not hidden by another extra. + */ +bool should_draw(const struct tileset *t, const extra_type *extra, + direction8 dir, const bv_extras extras, + const bv_extras extras_near, terrain *terrain_near) +{ + // Only draw if the extra connects to the other tile. + if (!connects(t, extra, dir, extras, extras_near, terrain_near)) { + return false; + } + + // Only draw if the connection is not hidden by another extra. + extra_type_list_iterate(extra->hiders, hider) + { + if (connects(t, hider, dir, extras, extras_near, terrain_near)) { + return false; + } + } + extra_type_list_iterate_end; + + // Passes all the tests, draw it. + return true; +} + +/** + * Returns data needed to draw roads on a tile. This is the directions in + * which roads should be drawn, and whether an "isolated" road should be + * drawn. + */ +std::tuple, bool> +road_data(const struct tileset *t, const tile *ptile, + const extra_type *extra) +{ + const auto extras = *tile_extras(ptile); + + // Don't bother doing anything if the extra isn't on the tile in the first + // place. + if (!BV_ISSET(extras, extra_index(extra))) { + return make_tuple(std::bitset(), false); + } + + const auto terrain = tile_terrain(ptile); + struct terrain *terrain_near[DIR8_MAGIC_MAX] = {nullptr}; + bv_extras extras_near[DIR8_MAGIC_MAX]; + build_tile_data(ptile, terrain, terrain_near, extras_near); + + // Check connections to adjacent tiles. + std::bitset draw; + for (int i = 0; i < tileset_num_valid_dirs(t); ++i) { + auto dir = tileset_valid_dirs(t)[i]; + draw[i] = should_draw(t, extra, static_cast(dir), extras, + extras_near[dir], terrain_near[dir]); + } + + // Draw the isolated sprite if we don't draw anything in any direction and + // it's not hidden by something else. + auto isolated = !draw.any() + && (!tile_city(ptile) || !gui_options->draw_cities) + && !is_hidden(extra, extras); + + return make_tuple(draw, isolated); +} + +} // anonymous namespace + +/** + * Returns the sprites to draw roads. + */ +std::vector +layer_roads::fill_sprite_array(const tile *ptile, const tile_edge *pedge, + const tile_corner *pcorner, + const unit *punit) const +{ + Q_UNUSED(pedge); + Q_UNUSED(pcorner); + + // Should we draw anything in the first place? + if (!ptile) { + return {}; + } + + const auto terrain = tile_terrain(ptile); + if (!tile_terrain(ptile)) { + return {}; + } + + // Now we draw + std::vector sprs; + + // Loop over all extras (picking the data for the correct terrain) and draw + // them. + for (const auto &data : m_all_combined[terrain_index(terrain)]) { + if (is_extra_drawing_enabled(data.extra)) { + fill_corners(sprs, data, ptile); + fill_all_combined(sprs, data, ptile); + } + } + for (const auto &data : m_all_separate[terrain_index(terrain)]) { + if (is_extra_drawing_enabled(data.extra)) { + fill_corners(sprs, data, ptile); + fill_all_separate(sprs, data, ptile); + } + } + for (const auto &data : m_parity_combined[terrain_index(terrain)]) { + if (is_extra_drawing_enabled(data.extra)) { + fill_corners(sprs, data, ptile); + fill_parity_combined(sprs, data, ptile); + } + } + + return sprs; +} + +/** + * Fills "corner" sprites that help complete diagonal roads where they + * overlap with adjacent tiles. Only relevant for overhead square tilesets. + */ +void layer_roads::fill_corners(std::vector &sprs, + const corner_sprites &data, + const tile *ptile) const +{ + if (is_cardinal_only_road(data.extra)) { + return; + } + if (tileset_hex_height(tileset()) != 0 + || tileset_hex_width(tileset()) != 0) { + return; + } + + /* Roads going diagonally adjacent to this tile need to be + * partly drawn on this tile. */ + + const auto terrain = tile_terrain(ptile); + struct terrain *terrain_near[DIR8_MAGIC_MAX] = {nullptr}; + bv_extras extras_near[DIR8_MAGIC_MAX]; + build_tile_data(ptile, terrain, terrain_near, extras_near); + + const auto num_valid_dirs = tileset_num_valid_dirs(tileset()); + const auto valid_dirs = tileset_valid_dirs(tileset()); + + /* Draw the corner sprite if: + * - There is a diagonal connection between two adjacent tiles. + * - There is no diagonal connection that intersects this connection. + * + * That is, if we are drawing tile X below: + * + * +--+--+ + * |NW|N | + * +--+--+ + * | W|X | + * +--+--+ + * + * We draw a corner on X if: + * - W and N are connected; + * - X and NW are not. + * This gets a bit more complicated with hiders, thus we call + * should_draw(). + */ + for (int i = 0; i < num_valid_dirs; ++i) { + enum direction8 dir = valid_dirs[i]; + + if (!is_cardinal_tileset_dir(tileset(), dir)) { + // Draw corner sprites for this non-cardinal direction (= NW on the + // schema). + + // = W on the schema + auto cardinal_before = + valid_dirs[(i + num_valid_dirs - 1) % num_valid_dirs]; + // = N on the schema + auto cardinal_after = valid_dirs[(i + 1) % num_valid_dirs]; + + /* should_draw() needs to know the direction in which the road would go + * to account for cardinal-only roads and hiders. We assume that all + * roads are bidirectional, that is going from N to W is the same as + * going from W to N. This may break with roads connecting to land, + * but it is a rare occurrence. + * + * We already know the non-cardinal direction where we do not want a + * connection (NW above). To compute the other one, remark that going + * from the W tile to the N tile is going NE, or N+1 counting + * clockwise. This is what we compute here: */ + auto relative_dir = valid_dirs[(i + 2) % num_valid_dirs]; + + if (data.corners[dir] + && !should_draw(tileset(), data.extra, dir, *tile_extras(ptile), + extras_near[dir], terrain_near[dir]) + && should_draw(tileset(), data.extra, relative_dir, + extras_near[cardinal_before], + extras_near[cardinal_after], + terrain_near[cardinal_after])) { + sprs.emplace_back(tileset(), data.corners[dir]); + } + } + } +} + +/** + * Fill sprites for extras with type RoadAllCombined. It is a very simple + * method that lets us simply retrieve entire finished tiles, with a bitwise + * index of the presence of roads in each direction. + */ +void layer_roads::fill_all_combined(std::vector &sprs, + const all_combined_data &data, + const tile *ptile) const +{ + auto [draw, isolated] = road_data(tileset(), ptile, data.extra); + + // Draw the sprite + if (draw.any() || isolated) { + sprs.emplace_back(tileset(), data.sprites[draw.to_ulong()]); + } +} + +/** + * Fill sprites for extras with type RoadAllSeparate. We simply draw one + * sprite for every connection. This means we only need a few sprites, but a + * lot of drawing is necessary and it generally doesn't look very good. + */ +void layer_roads::fill_all_separate(std::vector &sprs, + const all_separate_data &data, + const tile *ptile) const +{ + auto [draw, isolated] = road_data(tileset(), ptile, data.extra); + + // Draw the sprites + for (int i = 0; i < tileset_num_valid_dirs(tileset()); ++i) { + auto dir = tileset_valid_dirs(tileset())[i]; + if (draw[dir]) { + sprs.emplace_back(tileset(), data.sprites[dir]); + } + } + + // Draw the isolated sprite if needed + if (isolated) { + sprs.emplace_back(tileset(), data.isolated); + } +} + +/** + * Fill sprites for extras with type RoadAllSeparate. We draw one sprite for + * cardinal road connections, one sprite for diagonal road connections. This + * means we need about 4x more sprites than in style 0, but up to 4x less + * drawing is needed. The drawing quality may also be improved. + */ +void layer_roads::fill_parity_combined(std::vector &sprs, + const parity_combined_data &data, + const tile *ptile) const +{ + auto [draw, isolated] = road_data(tileset(), ptile, data.extra); + + // Pick up the sprites + int even_tileno = 0, odd_tileno = 0; + for (int i = 0; i < tileset_num_valid_dirs(tileset()) / 2; i++) { + auto even = 2 * i; + auto odd = 2 * i + 1; + + if (draw[even]) { + even_tileno |= 1 << i; + } + if (draw[odd]) { + odd_tileno |= 1 << i; + } + } + + // Draw the cardinal/even roads first + if (even_tileno != 0) { + sprs.emplace_back(tileset(), data.sprites.first[even_tileno]); + } + if (odd_tileno != 0) { + sprs.emplace_back(tileset(), data.sprites.second[odd_tileno]); + } + + // Draw the isolated sprite if needed + if (isolated) { + sprs.emplace_back(tileset(), data.isolated); + } +} + +void layer_roads::reset_ruleset() +{ + m_all_combined.clear(); + m_all_separate.clear(); + m_parity_combined.clear(); +} + +} // namespace freeciv diff --git a/client/tileset/layer_roads.h b/client/tileset/layer_roads.h new file mode 100644 index 0000000000..33b760bdcc --- /dev/null +++ b/client/tileset/layer_roads.h @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPLv3-or-later +// SPDX-FileCopyrightText: Freeciv authors +// SPDX-FileCopyrightText: Freeciv21 authors +// SPDX-FileCopyrightText: Louis Moureaux + +#pragma once + +#include "fc_types.h" +#include "layer.h" + +#include +#include +#include +#include + +namespace freeciv { + +class layer_roads : public layer { + /// Stores the data common to all road types. + struct corner_sprites { + const extra_type *extra = nullptr; + QPixmap *isolated = nullptr; + std::array corners = {nullptr}; + }; + + /// Helper. + template struct data : corner_sprites { + Sprites sprites; + }; + + /// Data for RoadAllCombined. + using all_combined_data = data>; + /// Data for RoadAllSeparate. + using all_separate_data = data>; + /// Data for RoadParityCombined. .first = even, .second = odd + using parity_combined_data = + data, + std::array>>; + +public: + explicit layer_roads(struct tileset *ts); + virtual ~layer_roads() = default; + + void initialize_extra(const extra_type *extra, const QString &tag, + extrastyle_id style) override; + + std::vector + fill_sprite_array(const tile *ptile, const tile_edge *pedge, + const tile_corner *pcorner, + const unit *punit) const override; + + void reset_ruleset() override; + +private: + void initialize_corners(corner_sprites &data, const extra_type *extra, + const QString &tag, const terrain *terrain); + void fill_corners(std::vector &sprs, + const corner_sprites &data, const tile *ptile) const; + + void initialize_all_combined(all_combined_data &data, const QString &tag, + const terrain *terrain); + void fill_all_combined(std::vector &sprs, + const all_combined_data &data, + const tile *ptile) const; + + void initialize_all_separate(all_separate_data &data, const QString &tag, + const terrain *terrain); + void fill_all_separate(std::vector &sprs, + const all_separate_data &data, + const tile *ptile) const; + + void initialize_parity_combined(parity_combined_data &data, + const QString &tag, + const terrain *terrain); + void fill_parity_combined(std::vector &sprs, + const parity_combined_data &data, + const tile *ptile) const; + + // All sprites depend on the terrain (first index) and extra (stored in the + // data structures, one structure per extra). + std::vector> m_all_combined; + std::vector> m_all_separate; + std::vector> m_parity_combined; +}; + +} // namespace freeciv diff --git a/client/tileset/layer_special.cpp b/client/tileset/layer_special.cpp index 8efd957936..0428dae915 100644 --- a/client/tileset/layer_special.cpp +++ b/client/tileset/layer_special.cpp @@ -15,7 +15,6 @@ #include "extras.h" #include "game.h" // For extra_type_iterate -#include "sprite_g.h" #include "tilespec.h" namespace freeciv { @@ -27,15 +26,39 @@ layer_special::layer_special(struct tileset *ts, mapview_layer layer) || layer == LAYER_SPECIAL3); } +/** + * Loads sprites for the extra if it has ESTYLE_SINGLE1/2 or ESTYLE_3LAYER. + */ +void layer_special::initialize_extra(const extra_type *extra, + const QString &tag, extrastyle_id style) +{ + // SINGLE1 extras have a sprite on special layer 1 only + // SINGLE2 extras have a sprite on special layer 2 only + // 3LAYER extras have a sprite on all 3 layers + if (style == ESTYLE_SINGLE1 && type() == LAYER_SPECIAL1) { + set_sprite(extra, tag); + } else if (style == ESTYLE_SINGLE2 && type() == LAYER_SPECIAL2) { + set_sprite(extra, tag); + } else if (style == ESTYLE_3LAYER) { + auto full_tag_name = QStringLiteral("%1_bg").arg(tag); + if (type() == LAYER_SPECIAL2) { + full_tag_name = QStringLiteral("%1_mg").arg(tag); + } else if (type() == LAYER_SPECIAL3) { + full_tag_name = QStringLiteral("%1_fg").arg(tag); + } + + set_sprite(extra, full_tag_name, tileset_full_tile_offset(tileset())); + } +} + void layer_special::set_sprite(const extra_type *extra, const QString &tag, - int offset_x, int offset_y) + const QPoint &offset) { fc_assert_ret(extra != nullptr); - auto sprite = load_sprite(tileset(), tag); - if (sprite) { - m_sprites.at(extra->id) = std::make_unique( - tileset(), sprite, true, offset_x, offset_y); + if (auto sprite = load_sprite({tag})) { + m_sprites.at(extra->id) = + std::make_unique(tileset(), sprite, true, offset); } } diff --git a/client/tileset/layer_special.h b/client/tileset/layer_special.h index f29a40eb15..79d0164262 100644 --- a/client/tileset/layer_special.h +++ b/client/tileset/layer_special.h @@ -27,8 +27,11 @@ class layer_special : public layer { explicit layer_special(struct tileset *ts, mapview_layer layer); virtual ~layer_special() = default; + void initialize_extra(const extra_type *extra, const QString &tag, + extrastyle_id style) override; + void set_sprite(const extra_type *extra, const QString &tag, - int offset_x = 0, int offset_y = 0); + const QPoint &offset = QPoint()); std::vector fill_sprite_array(const tile *ptile, const tile_edge *pedge, diff --git a/client/tileset/layer_terrain.cpp b/client/tileset/layer_terrain.cpp index 2fb4da25f0..5e89627519 100644 --- a/client/tileset/layer_terrain.cpp +++ b/client/tileset/layer_terrain.cpp @@ -342,7 +342,7 @@ void layer_terrain::initialize_cell_whole_match_none(const terrain *terrain, .arg(m_number) .arg(info.sprite_name) .arg(i + 1); - auto sprite = load_sprite(tileset(), buffer); + auto sprite = load_sprite({buffer}); if (!sprite) { break; } @@ -510,9 +510,8 @@ void layer_terrain::initialize_cell_corner_match_full(const terrain *terrain, auto buffer = QStringLiteral("t.l%1.cellgroup_%2_%3_%4_%5") .arg(QString::number(m_number), n->name[0], e->name[0], s->name[0], w->name[0]); - auto sprite = load_sprite(tileset(), buffer); - if (sprite) { + if (auto sprite = load_sprite({buffer})) { // Crop the sprite to separate this cell. const int W = sprite->width(); const int H = sprite->height(); @@ -526,13 +525,12 @@ void layer_terrain::initialize_cell_corner_match_full(const terrain *terrain, // We allocated new sprite with crop_sprite. Store its address so we // can free it. m_allocated.emplace_back(sprite); + info.sprites.push_back(sprite); } else { tileset_error(tileset(), LOG_ERROR, "Terrain graphics sprite for tag \"%s\" missing.", qUtf8Printable(buffer)); } - - info.sprites.push_back(sprite); } } @@ -623,8 +621,7 @@ void layer_terrain::initialize_cell_hex_corner(const terrain *terrain, .arg(groups[indices[idir][2]]->name[0]); } - auto sprite = load_sprite(tileset(), buffer); - if (sprite) { + if (auto sprite = load_sprite({buffer})) { // Crop the sprite to separate this cell. const int W = tileset_tile_width(tileset()); const int H = tileset_tile_height(tileset()); @@ -641,13 +638,12 @@ void layer_terrain::initialize_cell_hex_corner(const terrain *terrain, // We allocated a new sprite with crop_sprite. Store its address so // we can free it. m_allocated.emplace_back(sprite); + info.sprites.push_back(sprite); } else { tileset_error(tileset(), LOG_ERROR, "Terrain graphics sprite for tag \"%s\" missing.", qUtf8Printable(buffer)); } - - info.sprites.push_back(sprite); } } } @@ -734,8 +730,7 @@ layer_terrain::fill_sprite_array(const tile *ptile, const tile_edge *pedge, // FIXME: this should avoid calling load_sprite since it's slow and // increases the refcount without limit. if (QPixmap * sprite; - ptile->spec_sprite - && (sprite = load_sprite(tileset(), ptile->spec_sprite))) { + ptile->spec_sprite && (sprite = load_sprite({ptile->spec_sprite}))) { if (m_number == 0) { sprites.emplace_back(tileset(), sprite); } diff --git a/client/tileset/layer_units.cpp b/client/tileset/layer_units.cpp index 602ec94ca2..8c70cd9981 100644 --- a/client/tileset/layer_units.cpp +++ b/client/tileset/layer_units.cpp @@ -7,7 +7,9 @@ #include "layer_units.h" #include "control.h" +#include "movement.h" #include "options.h" +#include "rgbcolor.h" #include "tilespec.h" /** @@ -20,11 +22,73 @@ namespace freeciv { /** * Constructor */ -layer_units::layer_units(struct tileset *ts, mapview_layer layer) - : freeciv::layer(ts, layer) +layer_units::layer_units(struct tileset *ts, mapview_layer layer, + const QPoint &activity_offset, + const QPoint &select_offset, + const QPoint &unit_offset, + const QPoint &unit_flag_offset) + : freeciv::layer_abstract_activities(ts, layer), + m_activity_offset(activity_offset), m_select_offset(select_offset), + m_unit_offset(unit_offset), m_unit_flag_offset(unit_flag_offset) { } +/** + * Loads all static sprites needed by this layer (activities etc). + */ +void layer_units::load_sprites() +{ + layer_abstract_activities::load_sprites(); + + m_auto_attack = load_sprite({"unit.auto_attack"}, true); + m_auto_explore = load_sprite({"unit.auto_explore"}, true); + m_auto_settler = load_sprite({"unit.auto_settler"}, true); + m_connect = load_sprite({"unit.connect"}, true); + m_loaded = load_sprite({"unit.loaded"}, true); + m_lowfuel = load_sprite({"unit.lowfuel"}, true); + m_patrol = load_sprite({"unit.patrol"}, true); + m_stack = load_sprite({"unit.stack"}, true); + m_tired = load_sprite({"unit.tired"}, true); + m_action_decision_want = load_sprite({"unit.action_decision_want"}, false); + + static_assert(MAX_NUM_BATTLEGROUPS < NUM_TILES_DIGITS); + for (int i = 0; i < MAX_NUM_BATTLEGROUPS; i++) { + QStringList buffer = {QStringLiteral("unit.battlegroup_%1").arg(i), + QStringLiteral("city.size_%1").arg(i + 1)}; + m_battlegroup[i] = load_sprite({buffer}, true); + } + + for (int i = 0; i <= 100; i++) { + auto name = QStringLiteral("unit.hp_%1").arg(i); + if (auto sprite = load_sprite({name}, true, false)) { + m_hp_bar.push_back(sprite); + } + } + if (m_hp_bar.empty()) { + tileset_error(tileset(), QtFatalMsg, + "No unit.hp_* sprite in the tileset."); + } else { + tileset_error(tileset(), QtInfoMsg, "Loaded %d unit.hp* sprites.", + m_hp_bar.size()); + } + + for (int i = 0; i < MAX_VET_LEVELS; i++) { + /* Veteran level sprites are optional. For instance "green" units + * usually have no special graphic. */ + auto name = QStringLiteral("unit.vet_%1").arg(i); + m_veteran_level[i] = load_sprite({name}); + } + + for (int i = 0;; i++) { + auto buffer = QStringLiteral("unit.select%1").arg(QString::number(i)); + auto sprite = load_sprite({buffer}); + if (!sprite) { + break; + } + m_select.push_back(sprite); + } +} + std::vector layer_units::fill_sprite_array(const tile *ptile, const tile_edge *pedge, const tile_corner *pcorner, @@ -43,9 +107,152 @@ layer_units::fill_sprite_array(const tile *ptile, const tile_edge *pedge, return {}; } + // Now we draw std::vector sprs; - fill_unit_sprite_array(tileset(), sprs, ptile, punit); + const auto full_offset = tileset_full_tile_offset(tileset()); + + const auto type = unit_type_get(punit); + + // Selection animation. The blinking unit is handled separately, inside + // get_drawable_unit(). + if (ptile && unit_is_in_focus(punit) && !m_select.empty()) { + sprs.emplace_back(tileset(), m_select[focus_unit_state()], true, + m_select_offset); + } + + // Flag + if (!ptile || !tile_city(ptile)) { + if (!gui_options->solid_color_behind_units) { + sprs.emplace_back(tileset(), + get_unit_nation_flag_sprite(tileset(), punit), true, + full_offset + m_unit_flag_offset); + } else { + // Taken care of in layer_background. + } + } + + // Add the sprite for the unit type. + { + const auto rgb = punit->owner ? punit->owner->rgb : nullptr; + const auto color = rgb ? QColor(rgb->r, rgb->g, rgb->b) : QColor(); + const auto uspr = + get_unittype_sprite(tileset(), type, punit->facing, color); + sprs.emplace_back(tileset(), uspr, true, full_offset + m_unit_offset); + } + + // Loaded + if (unit_transported(punit)) { + sprs.emplace_back(tileset(), m_loaded, true, full_offset); + } + + // Activity + if (auto sprite = + activity_sprite(punit->activity, punit->activity_target)) { + sprs.emplace_back(tileset(), sprite, true, + full_offset + m_activity_offset); + } + + // Automated units + add_automated_sprite(sprs, punit, full_offset); + + // Goto/patrol/connect + add_orders_sprite(sprs, punit, full_offset); + + // Wants a decision? + if (m_action_decision_want && should_ask_server_for_actions(punit)) { + sprs.emplace_back(tileset(), m_action_decision_want, true, + full_offset + m_activity_offset); + } + + // Battlegroup + if (punit->battlegroup != BATTLEGROUP_NONE) { + sprs.emplace_back(tileset(), m_battlegroup[punit->battlegroup], true, + full_offset); + } + + // Show a low-fuel graphic if the unit has 2 or fewer moves left. + if (m_lowfuel && utype_fuel(type) && punit->fuel == 1 + && punit->moves_left <= 2 * SINGLE_MOVE) { + sprs.emplace_back(tileset(), m_lowfuel, true, full_offset); + } + + // Out of moves + if (m_tired && punit->moves_left < SINGLE_MOVE && type->move_rate > 0) { + // Show a "tired" graphic if the unit has fewer than one move remaining, + // except for units for which it's full movement. + sprs.emplace_back(tileset(), m_tired, true, full_offset); + } + + // Stacks + if ((ptile && unit_list_size(ptile->units) > 1) + || punit->client.occupied) { + sprs.emplace_back(tileset(), m_stack, true, full_offset); + } + + // Veteran level + if (m_veteran_level[punit->veteran]) { + sprs.emplace_back(tileset(), m_veteran_level[punit->veteran], true, + full_offset); + } + + // HP + { + auto ihp = ((m_hp_bar.size() - 1) * punit->hp) / type->hp; + ihp = CLIP(0, ihp, m_hp_bar.size() - 1); // Safety + sprs.emplace_back(tileset(), m_hp_bar[ihp], true, full_offset); + } + return sprs; } +/** + * Adds the sprite used to represent an automated unit on the map to sprs. + */ +void layer_units::add_automated_sprite(std::vector &sprs, + const unit *punit, + const QPoint &full_offset) const +{ + QPixmap *sprite = nullptr; + QPoint offset; + + switch (punit->ssa_controller) { + case SSA_NONE: + break; + case SSA_AUTOSETTLER: + sprite = m_auto_settler; + break; + case SSA_AUTOEXPLORE: + sprite = m_auto_explore; + // Specified as an activity in the tileset. + offset = m_activity_offset; + break; + default: + sprite = m_auto_attack; // FIXME What's this hack? + break; + } + + if (sprite) { + sprs.emplace_back(tileset(), sprite, true, full_offset + offset); + } +} + +/** + * Adds the sprite used to represent unit orders to sprs. + */ +void layer_units::add_orders_sprite(std::vector &sprs, + const unit *punit, + const QPoint &full_offset) const +{ + if (unit_has_orders(punit)) { + if (punit->orders.repeat) { + sprs.emplace_back(tileset(), m_patrol, true, full_offset); + } else if (punit->activity != ACTIVITY_IDLE) { + sprs.emplace_back(tileset(), m_connect); + } else { + sprs.emplace_back(tileset(), activity_sprite(ACTIVITY_GOTO, nullptr), + true, full_offset + m_activity_offset); + } + } +} + } // namespace freeciv diff --git a/client/tileset/layer_units.h b/client/tileset/layer_units.h index d7dbf35bbd..8bf697e5d2 100644 --- a/client/tileset/layer_units.h +++ b/client/tileset/layer_units.h @@ -6,19 +6,66 @@ #pragma once -#include "layer.h" +#include "fc_types.h" +#include "layer_abstract_activities.h" +#include "unit.h" + +#include namespace freeciv { -class layer_units : public layer { +class layer_units : public layer_abstract_activities { public: - explicit layer_units(struct tileset *ts, mapview_layer layer); + explicit layer_units(struct tileset *ts, mapview_layer layer, + const QPoint &activity_offset, + const QPoint &select_offset, + const QPoint &unit_offset, + const QPoint &unit_flag_offset); virtual ~layer_units() = default; + void load_sprites() override; + std::vector fill_sprite_array(const tile *ptile, const tile_edge *pedge, const tile_corner *pcorner, const unit *punit) const override; + + // What follows is a bit hacky, but we can't do better until we have more + // general animation support. + + /** + * Returns the number of steps in the focused unit animation. + */ + auto focus_unit_state_count() const { return m_select.size(); } + + /** + * Returns the current state of the focused unit animation. + */ + int focus_unit_state() const { return m_focus_unit_state; } + + /** + * Returns the current state of the focused unit animation. + */ + int &focus_unit_state() { return m_focus_unit_state; } + +private: + void add_automated_sprite(std::vector &sprs, + const unit *punit, + const QPoint &full_offset) const; + void add_orders_sprite(std::vector &sprs, const unit *punit, + const QPoint &full_offset) const; + + int m_focus_unit_state = 0; ///< State of the focused unit animation. + + QPixmap *m_auto_attack, *m_auto_settler, *m_auto_explore, *m_connect, + *m_loaded, *m_lowfuel, *m_patrol, *m_stack, *m_tired, + *m_action_decision_want; + std::vector m_hp_bar, m_select; + std::array m_battlegroup = {nullptr}; + std::array m_veteran_level = {nullptr}; + + QPoint m_activity_offset, m_select_offset, m_unit_offset, + m_unit_flag_offset; }; } // namespace freeciv diff --git a/client/tileset/layer_water.cpp b/client/tileset/layer_water.cpp new file mode 100644 index 0000000000..0e8070c92b --- /dev/null +++ b/client/tileset/layer_water.cpp @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPLv3-or-later +// SPDX-FileCopyrightText: Freeciv authors +// SPDX-FileCopyrightText: Freeciv21 authors +// SPDX-FileCopyrightText: Louis Moureaux + +#include "layer_water.h" + +#include "colors_common.h" +#include "extras.h" +#include "fc_types.h" +#include "tilespec.h" + +namespace freeciv { + +layer_water::layer_water(struct tileset *ts) + : freeciv::layer(ts, LAYER_WATER) +{ +} + +/** + * Collects all extras to be drawn (the ones with ESTYLE_CARDINALS or + * ESTYLE_RIVER). + */ +void layer_water::initialize_extra(const extra_type *extra, + const QString &tag, extrastyle_id style) +{ + if (style == ESTYLE_CARDINALS) { + extra_data data; + data.extra = extra; + + terrain_type_iterate(terrain) + { + extra_data::terrain_data sprites; + + // We use direction-specific irrigation and farmland graphics, if they + // are available. If not, we just fall back to the basic irrigation + // graphics. + QStringList alt_tags = make_tag_terrain_list(tag, "", terrain); + for (int i = 0; i < tileset_num_index_cardinals(tileset()); i++) { + QStringList tags = make_tag_terrain_list( + tag, cardinal_index_str(tileset(), i), terrain); + sprites[i] = load_sprite(tags + alt_tags, true); + } + + data.sprites.push_back(sprites); + } + terrain_type_iterate_end; + + m_cardinals.push_back(data); + } else if (style == ESTYLE_RIVER) { + extra_data outlet, river; + river.extra = extra; + outlet.extra = extra; + + terrain_type_iterate(terrain) + { + extra_data::terrain_data outlet_sprites; + extra_data::terrain_data river_sprites; + + for (int i = 0; i < tileset_num_index_cardinals(tileset()); i++) { + auto suffix = + QStringLiteral("_s_%1").arg(cardinal_index_str(tileset(), i)); + river_sprites[i] = + load_sprite(make_tag_terrain_list(tag, suffix, terrain), true); + } + + for (int i = 0; i < tileset_num_cardinal_dirs(tileset()); i++) { + auto suffix = QStringLiteral("_outlet_%1") + .arg(dir_get_tileset_name( + tileset_cardinal_dirs(tileset())[i])); + outlet_sprites[i] = + load_sprite(make_tag_terrain_list(tag, suffix, terrain), true); + } + + outlet.sprites.push_back(outlet_sprites); + river.sprites.push_back(river_sprites); + } + terrain_type_iterate_end; + + m_outlets.emplace_back(outlet); + m_rivers.emplace_back(river); + } +} + +namespace /* anonymous */ { +/** + * Return the index of the sprite to be used for irrigation or farmland in + * this tile. + * + * We assume that the current tile has farmland or irrigation. We then + * choose a sprite (index) based upon which cardinally adjacent tiles have + * either farmland or irrigation (the two are considered interchangable for + * this). + */ +static int get_irrigation_index(const struct tileset *t, + const extra_type *pextra, + bv_extras *textras_near) +{ + int tileno = 0, i; + + for (i = 0; i < tileset_num_cardinal_dirs(t); i++) { + enum direction8 dir = tileset_cardinal_dirs(t)[i]; + + if (BV_ISSET(textras_near[dir], extra_index(pextra))) { + tileno |= 1 << i; + } + } + + return tileno; +} +} // anonymous namespace + +/** + * Fill in the farmland/irrigation sprite for the tile. + */ +void layer_water::fill_irrigation_sprite_array( + const struct tileset *t, std::vector &sprs, + bv_extras textras, bv_extras *textras_near, const terrain *pterrain, + const city *pcity) const +{ + // We don't draw the irrigation if there's a city (it just gets overdrawn + // anyway, and ends up looking bad). + if (!(pcity && gui_options->draw_cities)) { + for (auto cdata : m_cardinals) { + if (is_extra_drawing_enabled(cdata.extra)) { + int eidx = extra_index(cdata.extra); + + if (BV_ISSET(textras, eidx)) { + bool hidden = false; + + extra_type_list_iterate(cdata.extra->hiders, phider) + { + if (BV_ISSET(textras, extra_index(phider))) { + hidden = true; + break; + } + } + extra_type_list_iterate_end; + + if (!hidden) { + int idx = get_irrigation_index(t, cdata.extra, textras_near); + + sprs.emplace_back(t, + cdata.sprites[terrain_index(pterrain)][idx]); + } + } + } + } + } +} + +std::vector +layer_water::fill_sprite_array(const tile *ptile, const tile_edge *pedge, + const tile_corner *pcorner, + const unit *punit) const +{ + if (!ptile) { + return {}; + } + + const auto terrain = tile_terrain(ptile); + if (!terrain) { + return {}; + } + + const auto pcity = tile_city(ptile); + const auto extras = *tile_extras(ptile); + + struct terrain *terrain_near[8] = {nullptr}; // dummy + bv_extras extras_near[8]; + build_tile_data(ptile, terrain, terrain_near, extras_near); + + std::vector sprs; + + if (!solid_background(ptile, punit, pcity) + && terrain_type_terrain_class(terrain) == TC_OCEAN) { + for (int dir = 0; dir < tileset_num_cardinal_dirs(tileset()); dir++) { + int didx = tileset_cardinal_dirs(tileset())[dir]; + + for (auto outlet : m_outlets) { + int idx = extra_index(outlet.extra); + + if (BV_ISSET(extras_near[didx], idx)) { + sprs.emplace_back(tileset(), + outlet.sprites[terrain_index(terrain)][dir]); + } + } + } + } + + fill_irrigation_sprite_array(tileset(), sprs, extras, extras_near, terrain, + pcity); + + if (!solid_background(ptile, punit, pcity)) { + for (const auto &river : m_rivers) { + int idx = extra_index(river.extra); + + if (BV_ISSET(extras, idx)) { + // Draw rivers on top of irrigation. + int tileno = 0; + for (int i = 0; i < tileset_num_cardinal_dirs(tileset()); i++) { + enum direction8 cdir = tileset_cardinal_dirs(tileset())[i]; + + if (terrain_near[cdir] == nullptr + || terrain_type_terrain_class(terrain_near[cdir]) == TC_OCEAN + || BV_ISSET(extras_near[cdir], idx)) { + tileno |= 1 << i; + } + } + + sprs.emplace_back(tileset(), + river.sprites[terrain_index(terrain)][tileno]); + } + } + } + + return sprs; +} + +void layer_water::reset_ruleset() +{ + m_cardinals.clear(); + m_outlets.clear(); + m_rivers.clear(); +} + +} // namespace freeciv diff --git a/client/tileset/layer_water.h b/client/tileset/layer_water.h new file mode 100644 index 0000000000..29d88b731c --- /dev/null +++ b/client/tileset/layer_water.h @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPLv3-or-later +// SPDX-FileCopyrightText: Freeciv authors +// SPDX-FileCopyrightText: Freeciv21 authors +// SPDX-FileCopyrightText: Louis Moureaux + +#pragma once + +#include "fc_types.h" +#include "layer.h" + +#include + +namespace freeciv { + +class layer_water : public layer { + struct extra_data { + using terrain_data = std::array; + + const extra_type *extra; + std::vector sprites; + }; + +public: + explicit layer_water(struct tileset *ts); + virtual ~layer_water() = default; + + void initialize_extra(const extra_type *extra, const QString &tag, + extrastyle_id style) override; + + std::vector + fill_sprite_array(const tile *ptile, const tile_edge *pedge, + const tile_corner *pcorner, + const unit *punit) const override; + + void reset_ruleset() override; + +private: + void fill_irrigation_sprite_array(const struct tileset *t, + std::vector &sprs, + bv_extras textras, + bv_extras *textras_near, + const terrain *pterrain, + const city *pcity) const; + + std::vector m_cardinals, m_outlets, m_rivers; +}; + +} // namespace freeciv diff --git a/client/tileset/layer_workertask.cpp b/client/tileset/layer_workertask.cpp new file mode 100644 index 0000000000..b9b0f8498e --- /dev/null +++ b/client/tileset/layer_workertask.cpp @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPLv3-or-later +// SPDX-FileCopyrightText: Freeciv authors +// SPDX-FileCopyrightText: Freeciv21 authors +// SPDX-FileCopyrightText: Louis Moureaux + +#include "layer_workertask.h" + +#include "citydlg_g.h" +#include "tilespec.h" +#include "workertask.h" + +/** + * \class freeciv::layer_workertask + * \brief Draws tasks assigned by cities to autoworkers on the map. + */ + +namespace freeciv { + +/** + * Constructor + */ +layer_workertask::layer_workertask(struct tileset *ts, + const QPoint &activity_offset) + : freeciv::layer_abstract_activities(ts, LAYER_WORKERTASK), + m_activity_offset(activity_offset) +{ +} + +std::vector layer_workertask::fill_sprite_array( + const tile *ptile, const tile_edge *pedge, const tile_corner *pcorner, + const unit *punit) const +{ + Q_UNUSED(pedge); + Q_UNUSED(pcorner); + + // Should we draw anything in the first place? + const auto city = is_any_city_dialog_open(); + if (!city || !ptile) { + return {}; + } + + // Now we draw + std::vector sprs; + + worker_task_list_iterate(city->task_reqs, ptask) + { + if (ptask->ptile == ptile) { + if (auto sprite = activity_sprite(ptask->act, ptask->tgt)) { + sprs.emplace_back(tileset(), sprite, true, + tileset_full_tile_offset(tileset()) + + m_activity_offset); + } + } + } + worker_task_list_iterate_end; + + return sprs; +} + +} // namespace freeciv diff --git a/client/tileset/layer_workertask.h b/client/tileset/layer_workertask.h new file mode 100644 index 0000000000..60db367141 --- /dev/null +++ b/client/tileset/layer_workertask.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPLv3-or-later +// SPDX-FileCopyrightText: Freeciv authors +// SPDX-FileCopyrightText: Freeciv21 authors +// SPDX-FileCopyrightText: Louis Moureaux + +#pragma once + +#include "fc_types.h" +#include "layer_abstract_activities.h" +#include "unit.h" + +#include + +namespace freeciv { + +class layer_workertask : public layer_abstract_activities { +public: + explicit layer_workertask(struct tileset *ts, + const QPoint &activity_offset); + virtual ~layer_workertask() = default; + + std::vector + fill_sprite_array(const tile *ptile, const tile_edge *pedge, + const tile_corner *pcorner, + const unit *punit) const override; + +private: + QPoint m_activity_offset; +}; + +} // namespace freeciv diff --git a/client/tileset/tilespec.cpp b/client/tileset/tilespec.cpp index a71dd861ad..1ac02ea2f0 100644 --- a/client/tileset/tilespec.cpp +++ b/client/tileset/tilespec.cpp @@ -32,34 +32,21 @@ #include "bitvector.h" #include "capability.h" #include "city.h" -#include "deprecations.h" #include "fcintl.h" #include "log.h" -#include "name_translation.h" -#include "rand.h" #include "registry.h" #include "registry_ini.h" #include "shared.h" -#include "style.h" #include "support.h" -#include "workertask.h" // common -#include "effects.h" #include "game.h" // game.control.styles_count #include "government.h" #include "helpdata.h" -#include "map.h" -#include "movement.h" #include "nation.h" -#include "player.h" -#include "road.h" #include "specialist.h" -#include "unit.h" -#include "unitlist.h" /* client/include */ -#include "citydlg_g.h" #include "mapview_g.h" // for update_map_canvas_visible #include "menu_g.h" #include "sprite_g.h" @@ -71,21 +58,32 @@ #include "climisc.h" #include "colors_common.h" #include "control.h" // for fill_xxx -#include "editor.h" -#include "goto.h" #include "helpdlg.h" #include "layer_background.h" #include "layer_base_flags.h" +#include "layer_city.h" +#include "layer_city_size.h" #include "layer_darkness.h" +#include "layer_editor.h" +#include "layer_fog.h" +#include "layer_goto.h" +#include "layer_grid.h" +#include "layer_infrawork.h" +#include "layer_overlays.h" +#include "layer_roads.h" #include "layer_special.h" #include "layer_terrain.h" #include "layer_units.h" +#include "layer_water.h" +#include "layer_workertask.h" #include "options.h" // for fill_xxx, tileset options #include "page_game.h" #include "tilespec.h" #include "utils/colorizer.h" #include "views/view_map.h" +Q_LOGGING_CATEGORY(tileset_category, "freeciv.tileset"); + #define TILESPEC_CAPSTR \ "+Freeciv-tilespec-Devel-2019-Jul-03 duplicates_ok precise-hp-bars " \ "unlimited-unit-select-frames unlimited-upkeep-sprites hex_corner " \ @@ -133,23 +131,8 @@ /// The prefix for option sections in the tilespec file. const static char *const OPTION_SECTION_PREFIX = "option_"; -#define NUM_TILES_DIGITS 10 - -#define FULL_TILE_X_OFFSET ((t->normal_tile_width - t->full_tile_width) / 2) -#define FULL_TILE_Y_OFFSET (t->normal_tile_height - t->full_tile_height) - -// This must correspond to enum edge_type. -static const char edge_name[EDGE_COUNT][3] = {"ns", "we", "ud", "lr"}; - #define MAX_NUM_LAYERS 3 -using styles = std::vector>; -using city_sprite = std::vector; - -struct river_sprites { - QPixmap *spec[MAX_INDEX_CARDINAL], *outlet[MAX_INDEX_CARDINAL]; -}; - struct citizen_graphic { /* Each citizen type has up to MAX_NUM_CITIZEN_SPRITES different * sprites, as defined by the tileset. */ @@ -169,7 +152,7 @@ struct named_sprites { *dither_tile; // only used for isometric view struct { - QPixmap *tile, *worked_tile, *unworked_tile; + QPixmap *tile; } mask; const QPixmap *tech[A_LAST]; @@ -181,8 +164,8 @@ struct named_sprites { std::unique_ptr facing[U_LAST][DIR8_MAGIC_MAX]; } units; - struct sprite_vector nation_flag; - struct sprite_vector nation_shield; + std::vector nation_flag; + std::vector nation_shield; struct citizen_graphic citizen[CITIZEN_LAST], specialist[SP_MAX]; QPixmap *spaceship[SPACESHIP_COUNT]; @@ -191,85 +174,23 @@ struct named_sprites { QPixmap *frame[NUM_CURSOR_FRAMES]; } cursor[CURSOR_LAST]; struct { - struct sprite_vector unit; + std::vector unit; QPixmap *nuke; } explode; - struct { - QPixmap *vet_lev[MAX_VET_LEVELS], *auto_attack, *auto_settler, - *auto_explore, *fortified, *fortifying, - *go_to, // goto is a C keyword :-) - *irrigate, *plant, *pillage, *sentry, *stack, *loaded, *transform, - *connect, *patrol, *convert, *battlegroup[MAX_NUM_BATTLEGROUPS], - *action_decision_want, *lowfuel, *tired; - std::vector hp_bar, select; - } unit; struct { std::vector unhappy, output[O_LAST]; } upkeep; - struct { - QPixmap *disorder, *happy, *size[NUM_TILES_DIGITS], - *size_tens[NUM_TILES_DIGITS], *size_hundreds[NUM_TILES_DIGITS]; - std::vector tile_foodnum, tile_shieldnum, tile_tradenum; - city_sprite tile, single_wall, wall[NUM_WALL_TYPES], occupied; - struct sprite_vector worked_tile_overlay; - struct sprite_vector unworked_tile_overlay; - } city; struct citybar_sprites citybar; struct editor_sprites editor; - struct { - struct { - QPixmap *specific; - QPixmap *turns[NUM_TILES_DIGITS]; - QPixmap *turns_tens[NUM_TILES_DIGITS]; - QPixmap *turns_hundreds[NUM_TILES_DIGITS]; - } s[GTS_COUNT]; - QPixmap *waypoint; - } path; struct { QPixmap *attention; } user; - struct { - QPixmap *fog, **fullfog; - } tx; // terrain extra struct { QPixmap *activity, *rmact; - int extrastyle; - union { - QPixmap *cardinals[MAX_INDEX_CARDINAL]; - struct { - QPixmap - /* for extrastyles ESTYLE_ROAD_ALL_SEPARATE and - ESTYLE_ROAD_PARITY_COMBINED */ - *isolated, - *corner[8]; /* Indexed by direction; only non-cardinal dirs used. - */ - union { - // for ESTYLE_ROAD_ALL_SEPARATE - QPixmap *dir[8]; // all entries used - // ESTYLE_ROAD_PARITY_COMBINED - struct { - QPixmap *even[MAX_INDEX_HALF], // first unused - *odd[MAX_INDEX_HALF]; // first unused - } combo; - // ESTYLE_ALL_SEPARATE - QPixmap *total[MAX_INDEX_VALID]; - struct river_sprites rivers; - } ru; - } road; - } u[MAX_NUM_TERRAINS]; } extras[MAX_EXTRA_TYPES]; struct { - QPixmap *main[EDGE_COUNT], *city[EDGE_COUNT], *worked[EDGE_COUNT], - *unavailable, *nonnative, *selected[EDGE_COUNT], - *coastline[EDGE_COUNT], *borders[EDGE_COUNT][2]; + QPixmap *borders[EDGE_COUNT][2]; } grid; - struct { - struct sprite_vector overlays; - } colors; - struct { - QPixmap *grid_borders[EDGE_COUNT][2]; - QPixmap *color; - } player[MAX_NUM_PLAYER_SLOTS]; }; struct specfile { @@ -312,43 +233,30 @@ struct tileset { std::map options; std::vector> layers; - struct { - freeciv::layer_special *background, *middleground, *foreground; - } special_layers; std::array terrain_layers; - freeciv::layer_darkness *darkness_layer; + freeciv::layer_units *focus_units_layer; enum ts_type type; int hex_width, hex_height; int ts_topo_idx; - int normal_tile_width, normal_tile_height; - int full_tile_width, full_tile_height; - int unit_tile_width, unit_tile_height; - int small_sprite_width, small_sprite_height; + QSize normal_tile_size, full_tile_size, unit_tile_size, small_sprite_size; double preferred_scale; int max_upkeep_height; enum direction8 unit_default_orientation; - enum fog_style fogstyle; + freeciv::darkness_style darkness_style; + freeciv::fog_style fogstyle; - int unit_flag_offset_x, unit_flag_offset_y; - int city_flag_offset_x, city_flag_offset_y; - int unit_offset_x, unit_offset_y; - int city_offset_x, city_offset_y; - int city_size_offset_x, city_size_offset_y; + QPoint unit_flag_offset, city_flag_offset, unit_offset, city_offset, + city_size_offset; int citybar_offset_y; int tilelabel_offset_y; - int activity_offset_x; - int activity_offset_y; - int select_offset_x; - int select_offset_y; + QPoint activity_offset, select_offset, occupied_offset; int select_step_ms = 100; - int occupied_offset_x; - int occupied_offset_y; int unit_upkeep_offset_y; int unit_upkeep_small_offset_y; @@ -359,17 +267,14 @@ struct tileset { QSet *small_sprites; // This hash table maps tilespec tags to struct small_sprites. QHash *sprite_hash; - QHash *estyle_hash; + QHash *estyle_hash; struct named_sprites sprites; int replaced_hue; // -1 means no replacement struct color_system *color_system; - struct extra_type_list *style_lists[ESTYLE_COUNT]; }; struct tileset *tileset; -int focus_unit_state = 0; - static bool tileset_update = false; static struct tileset *tileset_read_toplevel(const QString &tileset_name, @@ -378,30 +283,6 @@ static struct tileset *tileset_read_toplevel(const QString &tileset_name, static bool tileset_setup_options(struct tileset *t, const section_file *file); -static void tileset_setup_base(struct tileset *t, - const struct extra_type *pextra, - const char *tag); - -static void tileset_setup_crossing_separate(struct tileset *t, - struct extra_type *pextra, - struct terrain *pterrain, - const char *tag); - -static void tileset_setup_crossing_parity(struct tileset *t, - struct extra_type *pextra, - struct terrain *pterrain, - const char *tag); - -static void tileset_setup_crossing_combined(struct tileset *t, - struct extra_type *pextra, - struct terrain *pterrain, - const char *tag); - -static void tileset_setup_river(struct tileset *t, struct extra_type *pextra, - struct terrain *pterrain, const char *tag); - -bool is_extra_drawing_enabled(struct extra_type *pextra); - static void tileset_player_free(struct tileset *t, int plrid); /** @@ -420,10 +301,22 @@ void tileset_error(struct tileset *t, QtMsgType level, const char *format, t->log.push_back(tileset_log_entry{level, buf}); } - log_base(level, "%s", qUtf8Printable(buf)); - - if (level == QtFatalMsg) { - exit(EXIT_FAILURE); + switch (level) { + case QtFatalMsg: + qFatal("%s", qUtf8Printable(buf)); + break; + case QtCriticalMsg: + qCCritical(tileset_category).noquote() << buf; + break; + case QtWarningMsg: + qCWarning(tileset_category).noquote() << buf; + break; + case QtInfoMsg: + qCInfo(tileset_category).noquote() << buf; + break; + case QtDebugMsg: + qCDebug(tileset_category).noquote() << buf; + break; } } @@ -477,7 +370,7 @@ int tileset_hex_height(const struct tileset *t) { return t->hex_height; } */ int tileset_tile_width(const struct tileset *t) { - return t->normal_tile_width; + return t->normal_tile_size.width(); } /** @@ -489,7 +382,7 @@ int tileset_tile_width(const struct tileset *t) */ int tileset_tile_height(const struct tileset *t) { - return t->normal_tile_height; + return t->normal_tile_size.height(); } /** @@ -500,7 +393,7 @@ int tileset_tile_height(const struct tileset *t) */ int tileset_full_tile_width(const struct tileset *t) { - return t->full_tile_width; + return t->full_tile_size.width(); } /** @@ -513,7 +406,18 @@ int tileset_full_tile_width(const struct tileset *t) */ int tileset_full_tile_height(const struct tileset *t) { - return t->full_tile_height; + return t->full_tile_size.height(); +} + +/** + * Return the x and y offsets of full tiles in the tileset. Use this to draw + * "full sprites". + */ +QPoint tileset_full_tile_offset(const struct tileset *t) +{ + return QPoint((t->normal_tile_size.width() - t->full_tile_size.width()) + / 2, + t->normal_tile_size.height() - t->full_tile_size.height()); } /** @@ -521,7 +425,7 @@ int tileset_full_tile_height(const struct tileset *t) */ int tileset_unit_width(const struct tileset *t) { - return t->unit_tile_width; + return t->unit_tile_size.width(); } /** @@ -529,7 +433,7 @@ int tileset_unit_width(const struct tileset *t) */ int tileset_unit_height(const struct tileset *t) { - return t->unit_tile_height; + return t->unit_tile_size.height(); } /** @@ -590,7 +494,7 @@ int tileset_unit_layout_offset_y(const struct tileset *t) */ int tileset_small_sprite_width(const struct tileset *t) { - return t->small_sprite_width; + return t->small_sprite_size.width(); } /** @@ -618,15 +522,7 @@ int tileset_tilelabel_offset_y(const struct tileset *t) */ int tileset_small_sprite_height(const struct tileset *t) { - return t->small_sprite_height; -} - -/** - Return the number of possible colors for city overlays. - */ -int tileset_num_city_colors(const struct tileset *t) -{ - return t->sprites.city.worked_tile_overlay.size; + return t->small_sprite_size.height(); } /** @@ -634,7 +530,7 @@ int tileset_num_city_colors(const struct tileset *t) */ bool tileset_use_hard_coded_fog(const struct tileset *t) { - return FOG_AUTO == t->fogstyle; + return t->fogstyle == freeciv::FOG_AUTO; } /** @@ -645,6 +541,12 @@ double tileset_preferred_scale(const struct tileset *t) return t->preferred_scale; } +/** + * Returns the hue (color) that should be replaced with the player color in + * player-dependent sprites. + */ +int tileset_replaced_hue(const struct tileset *t) { return t->replaced_hue; } + /** * @brief Returns the number of cardinal directions used by the tileset. * @see tileset_cardinal_dirs @@ -674,6 +576,24 @@ std::array tileset_cardinal_dirs(const struct tileset *t) return t->cardinal_tileset_dirs; } +/** + * @brief Returns the number of valid directions in the tileset. + */ +int tileset_num_valid_dirs(const struct tileset *t) +{ + return t->num_valid_tileset_dirs; +} + +/** + * @brief Returns the valid directions for the tileset. + * + * Only the first @ref tileset_num_valid_dirs items should be used. + */ +std::array tileset_valid_dirs(const struct tileset *t) +{ + return t->valid_tileset_dirs; +} + /** Initialize. */ @@ -690,7 +610,7 @@ static struct tileset *tileset_new() Return the tileset name of the direction. This is similar to dir_get_name but you shouldn't change this or all tilesets will break. */ -static QString dir_get_tileset_name(enum direction8 dir) +QString dir_get_tileset_name(enum direction8 dir) { switch (dir) { case DIR8_NORTH: @@ -710,7 +630,7 @@ static QString dir_get_tileset_name(enum direction8 dir) case DIR8_NORTHWEST: return QStringLiteral("nw"); } - qCritical("Wrong direction8 variant: %d.", dir); + qCCritical(tileset_category, "Wrong direction8 variant: %d.", dir); return QLatin1String(""); } @@ -752,8 +672,7 @@ static bool is_valid_tileset_dir(const struct tileset *t, "Cardinal", in this sense, means that a tile will share a border with another tile in the direction rather than sharing just a single vertex. */ -static bool is_cardinal_tileset_dir(const struct tileset *t, - enum direction8 dir) +bool is_cardinal_tileset_dir(const struct tileset *t, enum direction8 dir) { if (t->hex_width > 0 || t->hex_height > 0) { return is_valid_tileset_dir(t, dir); @@ -899,18 +818,10 @@ static bool check_tilespec_capabilities(struct section_file *file, */ static void tileset_free_toplevel(struct tileset *t) { - int i; - if (t->estyle_hash) { delete t->estyle_hash; t->estyle_hash = nullptr; } - for (i = 0; i < ESTYLE_COUNT; i++) { - if (t->style_lists[i] != nullptr) { - extra_type_list_destroy(t->style_lists[i]); - t->style_lists[i] = nullptr; - } - } if (t->color_system) { color_system_free(t->color_system); @@ -928,11 +839,9 @@ static void tileset_free_toplevel(struct tileset *t) */ void tileset_free(struct tileset *t) { - int i; - tileset_free_tiles(t); tileset_free_toplevel(t); - for (i = 0; i < ARRAY_SIZE(t->sprites.player); i++) { + for (int i = 0; i < MAX_NUM_PLAYER_SLOTS; i++) { tileset_player_free(t, i); } delete t->specfiles; @@ -984,7 +893,7 @@ bool tilespec_try_read(const QString &tileset_name, bool verbose, _("No usable default tileset found, aborting!")); } - qDebug("Trying tileset \"%s\".", tileset->name); + qCDebug(tileset_category, "Trying tileset \"%s\".", tileset->name); } else { original = true; } @@ -1010,7 +919,8 @@ bool tilespec_reread(const QString &name, bool game_fully_initialized) enum client_states state = client_state(); bool new_tileset_in_use; - qInfo(_("Loading tileset \"%s\"."), qUtf8Printable(name)); + qCInfo(tileset_category, _("Loading tileset \"%s\"."), + qUtf8Printable(name)); /* Step 0: Record old data. * @@ -1225,7 +1135,8 @@ static QPixmap *load_gfx_file(const char *gfx_filename) } } - qCritical("Could not load gfx file \"%s\".", gfx_filename); + qCCritical(tileset_category, "Could not load gfx file \"%s\".", + gfx_filename); return make_error_pixmap(); } @@ -1316,7 +1227,8 @@ static void scan_specfile(struct tileset *t, struct specfile *sf, sec_name) || !secfile_lookup_int(file, &dx, "%s.dx", sec_name) || !secfile_lookup_int(file, &dy, "%s.dy", sec_name)) { - qCritical("Grid \"%s\" invalid: %s", sec_name, secfile_error()); + qCCritical(tileset_category, "Grid \"%s\" invalid: %s", sec_name, + secfile_error()); continue; } @@ -1336,8 +1248,9 @@ static void scan_specfile(struct tileset *t, struct specfile *sf, sec_name, j) || !(tags = secfile_lookup_str_vec( file, &num_tags, "%s.tiles%d.tag", sec_name, j))) { - qCritical("Small sprite \"%s.tiles%d\" invalid: %s", sec_name, j, - secfile_error()); + qCCritical(tileset_category, + "Small sprite \"%s.tiles%d\" invalid: %s", sec_name, j, + secfile_error()); continue; } @@ -1387,8 +1300,9 @@ static void scan_specfile(struct tileset *t, struct specfile *sf, if (t->sprite_hash->contains(tags[k]) && !option.isEmpty()) { // Warn about duplicated sprites, except if it was enabled by // a user option (to override the default). - qCritical("warning: %s: already have a sprite for \"%s\".", - t->name, tags[k]); + qCCritical(tileset_category, + "warning: %s: already have a sprite for \"%s\".", + t->name, tags[k]); } t->sprite_hash->insert(tags[k], ss); } @@ -1419,8 +1333,9 @@ static void scan_specfile(struct tileset *t, struct specfile *sf, "extra.sprites%d.tag", i)) || !(filename = secfile_lookup_str(file, "extra.sprites%d.file", i))) { - qCritical("Extra sprite \"extra.sprites%d\" invalid: %s", i, - secfile_error()); + qCCritical(tileset_category, + "Extra sprite \"extra.sprites%d\" invalid: %s", i, + secfile_error()); continue; } @@ -1455,8 +1370,9 @@ static void scan_specfile(struct tileset *t, struct specfile *sf, if (!duplicates_ok) { for (k = 0; k < num_tags; k++) { if (t->sprite_hash->contains(tags[k])) { - qCritical("warning: %s: already have a sprite for \"%s\".", - t->name, tags[k]); + qCWarning(tileset_category, + "%s: already have a sprite for \"%s\".", t->name, + tags[k]); } t->sprite_hash->insert(tags[k], ss); } @@ -1490,48 +1406,49 @@ check_sprite_type(const char *sprite_type, const char *tile_section) if (fc_strcasecmp(sprite_type, "whole") == 0) { return freeciv::layer_terrain::CELL_WHOLE; } - qCritical("[%s] unknown sprite_type \"%s\".", tile_section, sprite_type); + qCCritical(tileset_category, "[%s] unknown sprite_type \"%s\".", + tile_section, sprite_type); return freeciv::layer_terrain::CELL_WHOLE; } static bool tileset_invalid_offsets(struct tileset *t, struct section_file *file) { - return !secfile_lookup_int(file, &t->unit_flag_offset_x, + return !secfile_lookup_int(file, &t->unit_flag_offset.rx(), "tilespec.unit_flag_offset_x") - || !secfile_lookup_int(file, &t->unit_flag_offset_y, + || !secfile_lookup_int(file, &t->unit_flag_offset.ry(), "tilespec.unit_flag_offset_y") - || !secfile_lookup_int(file, &t->city_flag_offset_x, + || !secfile_lookup_int(file, &t->city_flag_offset.rx(), "tilespec.city_flag_offset_x") - || !secfile_lookup_int(file, &t->city_flag_offset_y, + || !secfile_lookup_int(file, &t->city_flag_offset.ry(), "tilespec.city_flag_offset_y") - || !secfile_lookup_int(file, &t->unit_offset_x, + || !secfile_lookup_int(file, &t->unit_offset.rx(), "tilespec.unit_offset_x") - || !secfile_lookup_int(file, &t->unit_offset_y, + || !secfile_lookup_int(file, &t->unit_offset.ry(), "tilespec.unit_offset_y") - || !secfile_lookup_int(file, &t->activity_offset_x, + || !secfile_lookup_int(file, &t->activity_offset.rx(), "tilespec.activity_offset_x") - || !secfile_lookup_int(file, &t->activity_offset_y, + || !secfile_lookup_int(file, &t->activity_offset.ry(), "tilespec.activity_offset_y") - || !secfile_lookup_int(file, &t->select_offset_x, + || !secfile_lookup_int(file, &t->select_offset.rx(), "tilespec.select_offset_x") - || !secfile_lookup_int(file, &t->select_offset_y, + || !secfile_lookup_int(file, &t->select_offset.ry(), "tilespec.select_offset_y") - || !secfile_lookup_int(file, &t->city_offset_x, + || !secfile_lookup_int(file, &t->city_offset.rx(), "tilespec.city_offset_x") - || !secfile_lookup_int(file, &t->city_offset_y, + || !secfile_lookup_int(file, &t->city_offset.ry(), "tilespec.city_offset_y") - || !secfile_lookup_int(file, &t->city_size_offset_x, + || !secfile_lookup_int(file, &t->city_size_offset.rx(), "tilespec.city_size_offset_x") - || !secfile_lookup_int(file, &t->city_size_offset_y, + || !secfile_lookup_int(file, &t->city_size_offset.ry(), "tilespec.city_size_offset_y") || !secfile_lookup_int(file, &t->citybar_offset_y, "tilespec.citybar_offset_y") || !secfile_lookup_int(file, &t->tilelabel_offset_y, "tilespec.tilelabel_offset_y") - || !secfile_lookup_int(file, &t->occupied_offset_x, + || !secfile_lookup_int(file, &t->occupied_offset.rx(), "tilespec.occupied_offset_x") - || !secfile_lookup_int(file, &t->occupied_offset_y, + || !secfile_lookup_int(file, &t->occupied_offset.ry(), "tilespec.occupied_offset_y"); } @@ -1566,10 +1483,38 @@ static void tileset_add_layer(struct tileset *t, mapview_layer layer) case LAYER_BACKGROUND: t->layers.push_back(std::make_unique(t)); break; + case LAYER_CITY1: { + t->layers.emplace_back(std::make_unique( + t, t->city_offset, t->city_flag_offset, t->occupied_offset)); + } break; + case LAYER_CITY2: { + t->layers.emplace_back(std::make_unique( + t, tileset_full_tile_offset(t) + t->city_size_offset)); + } break; case LAYER_DARKNESS: { - auto l = std::make_unique(t); - t->darkness_layer = l.get(); - t->layers.emplace_back(std::move(l)); + t->layers.emplace_back( + std::make_unique(t, t->darkness_style)); + } break; + case LAYER_EDITOR: { + t->layers.emplace_back(std::make_unique(t)); + } break; + case LAYER_FOG: { + t->layers.emplace_back(std::make_unique( + t, t->fogstyle, t->darkness_style)); + } break; + case LAYER_GOTO: { + t->layers.emplace_back(std::make_unique(t)); + } break; + case LAYER_GRID1: + case LAYER_GRID2: { + t->layers.emplace_back(std::make_unique(t, layer)); + } break; + case LAYER_INFRAWORK: { + t->layers.emplace_back( + std::make_unique(t, t->activity_offset)); + } break; + case LAYER_OVERLAYS: { + t->layers.emplace_back(std::make_unique(t)); } break; case LAYER_TERRAIN1: { auto l = std::make_unique(t, 0); @@ -1586,30 +1531,38 @@ static void tileset_add_layer(struct tileset *t, mapview_layer layer) t->terrain_layers[2] = l.get(); t->layers.emplace_back(std::move(l)); } break; - case LAYER_SPECIAL1: { - auto l = std::make_unique(t, layer); - t->special_layers.background = l.get(); - t->layers.emplace_back(std::move(l)); - } break; - case LAYER_SPECIAL2: { - auto l = std::make_unique(t, layer); - t->special_layers.middleground = l.get(); - t->layers.emplace_back(std::move(l)); - } break; + case LAYER_SPECIAL1: + case LAYER_SPECIAL2: case LAYER_SPECIAL3: { - auto l = std::make_unique(t, layer); - t->special_layers.foreground = l.get(); - t->layers.emplace_back(std::move(l)); + t->layers.emplace_back( + std::make_unique(t, layer)); + } break; + case LAYER_ROADS: { + t->layers.emplace_back(std::make_unique(t)); } break; case LAYER_BASE_FLAGS: { auto l = std::make_unique( - t, FULL_TILE_X_OFFSET + t->city_flag_offset_x, - FULL_TILE_Y_OFFSET + t->city_flag_offset_y); + t, tileset_full_tile_offset(t) + t->city_flag_offset); t->layers.emplace_back(std::move(l)); } break; - case LAYER_UNIT: + case LAYER_UNIT: { + t->layers.emplace_back(std::make_unique( + t, layer, t->activity_offset, t->select_offset, t->unit_offset, + t->unit_flag_offset)); + } break; case LAYER_FOCUS_UNIT: { - t->layers.emplace_back(std::make_unique(t, layer)); + auto l = std::make_unique( + t, layer, t->activity_offset, t->select_offset, t->unit_offset, + t->unit_flag_offset); + t->focus_units_layer = l.get(); + t->layers.emplace_back(move(l)); + } break; + case LAYER_WATER: { + t->layers.emplace_back(std::make_unique(t)); + } break; + case LAYER_WORKERTASK: { + t->layers.emplace_back( + std::make_unique(t, t->activity_offset)); } break; default: t->layers.push_back(std::make_unique(t, layer)); @@ -1646,14 +1599,16 @@ static struct tileset *tileset_read_toplevel(const QString &tileset_name, fname = tilespec_fullname(tileset_name); if (!fname) { if (verbose) { - qCritical("Can't find tileset \"%s\".", qUtf8Printable(tileset_name)); + qCCritical(tileset_category, "Can't find tileset \"%s\".", + qUtf8Printable(tileset_name)); } return nullptr; } - qDebug("tilespec file is \"%s\".", fname); + qCDebug(tileset_category, "tilespec file is \"%s\".", fname); if (!(file = secfile_load(fname, true))) { - qCritical("Could not open '%s':\n%s", fname, secfile_error()); + qCCritical(tileset_category, "Could not open '%s':\n%s", fname, + secfile_error()); delete[] fname; return nullptr; } @@ -1716,21 +1671,23 @@ static struct tileset *tileset_read_toplevel(const QString &tileset_name, sz_strlcpy(t->name, tileset_name.toUtf8().data()); if (!secfile_lookup_int(file, &t->priority, "tilespec.priority") || !secfile_lookup_bool(file, &is_hex, "tilespec.is_hex")) { - qCritical("Tileset \"%s\" invalid: %s", t->name, secfile_error()); + qCCritical(tileset_category, "Tileset \"%s\" invalid: %s", t->name, + secfile_error()); tileset_stop_read(t, file, fname, sections, layer_order); return nullptr; } tstr = secfile_lookup_str(file, "tilespec.type"); if (tstr == nullptr) { - qCritical("Tileset \"%s\": no tileset type", t->name); + qCCritical(tileset_category, "Tileset \"%s\": no tileset type", t->name); tileset_stop_read(t, file, fname, sections, layer_order); return nullptr; } t->type = ts_type_by_name(tstr, fc_strcasecmp); if (!ts_type_is_valid(t->type)) { - qCritical("Tileset \"%s\": unknown tileset type \"%s\"", t->name, tstr); + qCCritical(tileset_category, + "Tileset \"%s\": unknown tileset type \"%s\"", t->name, tstr); tileset_stop_read(t, file, fname, sections, layer_order); return nullptr; } @@ -1746,7 +1703,8 @@ static struct tileset *tileset_read_toplevel(const QString &tileset_name, int hex_side; if (!secfile_lookup_int(file, &hex_side, "tilespec.hex_side")) { - qCritical("Tileset \"%s\" invalid: %s", t->name, secfile_error()); + qCCritical(tileset_category, "Tileset \"%s\" invalid: %s", t->name, + secfile_error()); tileset_stop_read(t, file, fname, sections, layer_order); return nullptr; } @@ -1796,55 +1754,58 @@ static struct tileset *tileset_read_toplevel(const QString &tileset_name, t->num_index_valid = 1 << t->num_valid_tileset_dirs; t->num_index_cardinal = 1 << t->num_cardinal_tileset_dirs; - if (!secfile_lookup_int(file, &t->normal_tile_width, + if (!secfile_lookup_int(file, &t->normal_tile_size.rwidth(), "tilespec.normal_tile_width") - || !secfile_lookup_int(file, &t->normal_tile_height, + || !secfile_lookup_int(file, &t->normal_tile_size.rheight(), "tilespec.normal_tile_height")) { - qCritical("Tileset \"%s\" invalid: %s", t->name, secfile_error()); + qCCritical(tileset_category, "Tileset \"%s\" invalid: %s", t->name, + secfile_error()); tileset_stop_read(t, file, fname, sections, layer_order); return nullptr; } if (t->type == TS_ISOMETRIC) { - t->full_tile_width = t->normal_tile_width; + t->full_tile_size.rwidth() = t->normal_tile_size.width(); if (tileset_hex_height(t) > 0) { - t->full_tile_height = t->normal_tile_height; + t->full_tile_size.rheight() = t->normal_tile_size.height(); } else { - t->full_tile_height = 3 * t->normal_tile_height / 2; + t->full_tile_size.rheight() = 3 * t->normal_tile_size.height() / 2; } } else { - t->full_tile_width = t->normal_tile_width; - t->full_tile_height = t->normal_tile_height; + t->full_tile_size = t->normal_tile_size; } - t->unit_tile_width = secfile_lookup_int_default(file, t->full_tile_width, - "tilespec.unit_width"); - t->unit_tile_height = secfile_lookup_int_default(file, t->full_tile_height, - "tilespec.unit_height"); + t->unit_tile_size.rwidth() = secfile_lookup_int_default( + file, t->full_tile_size.width(), "tilespec.unit_width"); + t->unit_tile_size.rheight() = secfile_lookup_int_default( + file, t->full_tile_size.height(), "tilespec.unit_height"); // Hue to be replaced in unit graphics t->replaced_hue = secfile_lookup_int_default(file, -1, "tilespec.replaced_hue"); - if (!secfile_lookup_int(file, &t->small_sprite_width, + if (!secfile_lookup_int(file, &t->small_sprite_size.rwidth(), "tilespec.small_tile_width") - || !secfile_lookup_int(file, &t->small_sprite_height, + || !secfile_lookup_int(file, &t->small_sprite_size.rheight(), "tilespec.small_tile_height")) { - qCritical("Tileset \"%s\" invalid: %s", t->name, secfile_error()); + qCCritical(tileset_category, "Tileset \"%s\" invalid: %s", t->name, + secfile_error()); tileset_stop_read(t, file, fname, sections, layer_order); return nullptr; } - qDebug("tile sizes %dx%d, %dx%d unit, %dx%d small", t->normal_tile_width, - t->normal_tile_height, t->full_tile_width, t->full_tile_height, - t->small_sprite_width, t->small_sprite_height); + qCDebug(tileset_category, "tile sizes %dx%d, %dx%d unit, %dx%d small", + t->normal_tile_size.width(), t->normal_tile_size.height(), + t->full_tile_size.width(), t->full_tile_size.height(), + t->small_sprite_size.width(), t->small_sprite_size.height()); tstr = secfile_lookup_str(file, "tilespec.fog_style"); if (tstr == nullptr) { - qCritical("Tileset \"%s\": no fog_style", t->name); + qCCritical(tileset_category, "Tileset \"%s\": no fog_style", t->name); tileset_stop_read(t, file, fname, sections, layer_order); return nullptr; } - t->fogstyle = fog_style_by_name(tstr, fc_strcasecmp); + t->fogstyle = freeciv::fog_style_by_name(tstr, fc_strcasecmp); if (!fog_style_is_valid(t->fogstyle)) { - qCritical("Tileset \"%s\": unknown fog_style \"%s\"", t->name, tstr); + qCCritical(tileset_category, "Tileset \"%s\": unknown fog_style \"%s\"", + t->name, tstr); tileset_stop_read(t, file, fname, sections, layer_order); return nullptr; } @@ -1857,7 +1818,8 @@ static struct tileset *tileset_read_toplevel(const QString &tileset_name, file, 100, 1, 10000, "tilespec.select_step_ms"); if (tileset_invalid_offsets(t, file)) { - qCritical("Tileset \"%s\" invalid: %s", t->name, secfile_error()); + qCCritical(tileset_category, "Tileset \"%s\" invalid: %s", t->name, + secfile_error()); tileset_stop_read(t, file, fname, sections, layer_order); return nullptr; } @@ -1885,6 +1847,31 @@ static struct tileset *tileset_read_toplevel(const QString &tileset_name, } } + tstr = secfile_lookup_str(file, "tilespec.darkness_style"); + if (tstr == nullptr) { + qCCritical(tileset_category, "Tileset \"%s\": no darkness_style", + t->name); + tileset_stop_read(t, file, fname, sections, layer_order); + return nullptr; + } + + t->darkness_style = freeciv::darkness_style_by_name(tstr, fc_strcasecmp); + if (!darkness_style_is_valid(t->darkness_style)) { + qCCritical(tileset_category, + "Tileset \"%s\": unknown darkness_style \"%s\"", t->name, + tstr); + tileset_stop_read(t, file, fname, sections, layer_order); + return nullptr; + } + + if (t->darkness_style == freeciv::DARKNESS_ISORECT + && (t->type == TS_OVERHEAD || t->hex_width > 0 || t->hex_height > 0)) { + qCCritical(tileset_category, + "Invalid darkness style set in tileset \"%s\".", t->name); + tileset_stop_read(t, file, fname, sections, layer_order); + return nullptr; + } + // Layer order num_layers = 0; layer_order = @@ -1898,14 +1885,16 @@ static struct tileset *tileset_read_toplevel(const QString &tileset_name, // Check for wrong layer names. if (!mapview_layer_is_valid(layer)) { - qCritical("layer_order: Invalid layer \"%s\"", layer_order[i]); + qCCritical(tileset_category, "layer_order: Invalid layer \"%s\"", + layer_order[i]); tileset_stop_read(t, file, fname, sections, layer_order); return nullptr; } // Check for duplicates. for (j = 0; j < i; j++) { if (order[j] == layer) { - qCritical("layer_order: Duplicate layer \"%s\"", layer_order[i]); + qCCritical(tileset_category, "layer_order: Duplicate layer \"%s\"", + layer_order[i]); tileset_stop_read(t, file, fname, sections, layer_order); return nullptr; } @@ -1926,8 +1915,8 @@ static struct tileset *tileset_read_toplevel(const QString &tileset_name, } } if (!found) { - qCritical("layer_order: Missing layer \"%s\"", - mapview_layer_name(static_cast(i))); + qCCritical(tileset_category, "layer_order: Missing layer \"%s\"", + mapview_layer_name(static_cast(i))); tileset_stop_read(t, file, fname, sections, layer_order); return nullptr; } @@ -1943,30 +1932,6 @@ static struct tileset *tileset_read_toplevel(const QString &tileset_name, } } - tstr = secfile_lookup_str(file, "tilespec.darkness_style"); - if (tstr == nullptr) { - qCritical("Tileset \"%s\": no darkness_style", t->name); - tileset_stop_read(t, file, fname, sections, layer_order); - return nullptr; - } - - auto darkness_style = freeciv::darkness_style_by_name(tstr, fc_strcasecmp); - if (!darkness_style_is_valid(darkness_style)) { - qCritical("Tileset \"%s\": unknown darkness_style \"%s\"", t->name, - tstr); - tileset_stop_read(t, file, fname, sections, layer_order); - return nullptr; - } - - if (darkness_style == freeciv::DARKNESS_ISORECT - && (t->type == TS_OVERHEAD || t->hex_width > 0 || t->hex_height > 0)) { - qCritical("Invalid darkness style set in tileset \"%s\".", t->name); - tileset_stop_read(t, file, fname, sections, layer_order); - return nullptr; - } - - t->darkness_layer->set_darkness_style(darkness_style); - // Terrain layer info. for (i = 0; i < MAX_NUM_LAYERS; i++) { std::size_t count = 0; @@ -2027,13 +1992,13 @@ static struct tileset *tileset_read_toplevel(const QString &tileset_name, auto offset_x = secfile_lookup_int_default( file, 0, "%s.layer%d_offset_x", sec_name, l); if (is_tall) { - offset_x += FULL_TILE_X_OFFSET; + offset_x += tileset_full_tile_offset(t).x(); } auto offset_y = secfile_lookup_int_default( file, 0, "%s.layer%d_offset_y", sec_name, l); if (is_tall) { - offset_y += FULL_TILE_Y_OFFSET; + offset_y += tileset_full_tile_offset(t).y(); } if (!t->terrain_layers[l]->set_tag_offsets(tag, offset_x, @@ -2099,24 +2064,20 @@ static struct tileset *tileset_read_toplevel(const QString &tileset_name, return nullptr; } - t->estyle_hash = new QHash; - - for (i = 0; i < ESTYLE_COUNT; i++) { - t->style_lists[i] = extra_type_list_new(); - } + t->estyle_hash = new QHash; for (i = 0; (extraname = secfile_lookup_str_default( file, nullptr, "extras.styles%d.name", i)); i++) { const char *style_name; - int style; style_name = secfile_lookup_str_default(file, "Single1", "extras.styles%d.style", i); - style = extrastyle_id_by_name(style_name, fc_strcasecmp); + auto style = extrastyle_id_by_name(style_name, fc_strcasecmp); if (t->estyle_hash->contains(extraname)) { - qCritical("warning: duplicate extrastyle entry [%s].", extraname); + qCCritical(tileset_category, + "warning: duplicate extrastyle entry [%s].", extraname); tileset_stop_read(t, file, fname, sections, layer_order); return nullptr; } @@ -2126,7 +2087,8 @@ static struct tileset *tileset_read_toplevel(const QString &tileset_name, spec_filenames = secfile_lookup_str_vec(file, &num_spec_files, "tilespec.files"); if (nullptr == spec_filenames || 0 == num_spec_files) { - qCritical("No tile graphics files specified in \"%s\"", fname); + qCCritical(tileset_category, + "No tile graphics files specified in \"%s\"", fname); tileset_stop_read(t, file, fname, sections, layer_order); return nullptr; } @@ -2143,7 +2105,8 @@ static struct tileset *tileset_read_toplevel(const QString &tileset_name, dname = fileinfoname(get_data_dirs(), spec_filenames[i]); if (dname.isEmpty()) { if (verbose) { - qCritical("Can't find spec file \"%s\".", spec_filenames[i]); + qCCritical(tileset_category, "Can't find spec file \"%s\".", + spec_filenames[i]); } delete sf; tileset_stop_read(t, file, fname, sections, layer_order); @@ -2160,7 +2123,7 @@ static struct tileset *tileset_read_toplevel(const QString &tileset_name, secfile_check_unused(file); secfile_destroy(file); - qDebug("finished reading \"%s\".", fname); + qCDebug(tileset_category, "finished reading \"%s\".", fname); delete[] fname; delete[] layer_order; @@ -2257,7 +2220,8 @@ static const char *citizen_rule_name(enum citizen_category citizen) default: break; } - qCritical("Unknown citizen type: %d.", static_cast(citizen)); + qCCritical(tileset_category, "Unknown citizen type: %d.", + static_cast(citizen)); return nullptr; } @@ -2284,13 +2248,10 @@ QString cardinal_index_str(const struct tileset *t, int idx) Do the same thing as cardinal_str, except including all valid directions. The returned string is a pointer to static memory. */ -static QString &valid_index_str(const struct tileset *t, int idx) +QString valid_index_str(const struct tileset *t, int idx) { - static QString c; - int i; - - c = QString(); - for (i = 0; i < t->num_valid_tileset_dirs; i++) { + auto c = QString(); + for (int i = 0; i < t->num_valid_tileset_dirs; i++) { int value = (idx >> i) & 1; c += QStringLiteral("%1%2").arg( @@ -2306,7 +2267,7 @@ static QString &valid_index_str(const struct tileset *t, int idx) counter is increased. Can return nullptr if the sprite couldn't be loaded. */ -QPixmap *load_sprite(struct tileset *t, const QString &tag_name) +static QPixmap *load_sprite(struct tileset *t, const QString &tag_name) { struct small_sprite *ss; @@ -2353,6 +2314,43 @@ QPixmap *load_sprite(struct tileset *t, const QString &tag_name) return ss->sprite; } +/** + * Finds the first sprite matching a list of possible names and returns it. + * Aborts when a required sprite is not found; otherwise, warns and returns + * nullptr. + */ +QPixmap *load_sprite(struct tileset *t, const QStringList &possible_names, + bool required, bool verbose) +{ + // go through the list of possible names until + // you find a sprite that exists. + for (const auto &name : possible_names) { + auto sprite = load_sprite(t, name); + if (sprite) { + return sprite; + } + } + + if (verbose) { + // TODO Qt6 + // We should be able to remove the line below and + // update the tileset_errors in the if statement. + QVector names_vec(possible_names.begin(), possible_names.end()); + // if sprite couldn't be found and it is required, crash, else warn. + if (required) { + tileset_error(t, LOG_FATAL, + _("Could not find required sprite matching %s"), + qUtf8Printable(strvec_to_or_list(names_vec))); + } else { + tileset_error(t, LOG_NORMAL, + _("Could not find optional sprite matching %s"), + qUtf8Printable(strvec_to_or_list(names_vec))); + } + } + + return nullptr; +} + /** Unloads the sprite. Decrease the reference counter. If the last reference is removed the sprite is freed. @@ -2382,28 +2380,7 @@ static void unload_sprite(struct tileset *t, const QString &tag_name) static void assign_sprite(struct tileset *t, QPixmap *&field, const QStringList &possible_names, bool required) { - // go through the list of possible names until - // you find a sprite that exists. - for (const auto &name : possible_names) { - field = load_sprite(t, name); - if (field) { - return; - } - } - // TODO Qt6 - // We should be able to remove the line below and - // update the tileset_errors in the if statement. - QVector names_vec(possible_names.begin(), possible_names.end()); - // if sprite couldn't be found and it is required, crash, else warn. - if (required) { - tileset_error(t, LOG_FATAL, - _("Could not find required sprite matching %s"), - qUtf8Printable(strvec_to_or_list(names_vec))); - } else { - tileset_error(t, LOG_NORMAL, - _("Could not find optional sprite matching %s"), - qUtf8Printable(strvec_to_or_list(names_vec))); - } + field = load_sprite(t, possible_names, required); } /** @@ -2428,11 +2405,11 @@ static void assign_digit_sprites_helper(struct tileset *t, * Assigns the digits for city or go-to orders, for units, tens, * and hundreds (i.e. up to 999) */ -static void assign_digit_sprites(struct tileset *t, - QPixmap *units[NUM_TILES_DIGITS], - QPixmap *tens[NUM_TILES_DIGITS], - QPixmap *hundreds[NUM_TILES_DIGITS], - const QStringList &patterns) +void assign_digit_sprites(struct tileset *t, + QPixmap *units[NUM_TILES_DIGITS], + QPixmap *tens[NUM_TILES_DIGITS], + QPixmap *hundreds[NUM_TILES_DIGITS], + const QStringList &patterns) { assign_digit_sprites_helper(t, units, patterns, QStringLiteral(), true); assign_digit_sprites_helper(t, tens, patterns, QStringLiteral("0"), true); @@ -2531,98 +2508,6 @@ static void tileset_setup_citizen_types(struct tileset *t) } } -/** - Return the sprite in the city_sprite listing that corresponds to this - city - based on city style and size. - - See also load_city_sprite, free_city_sprite. - */ -static const QPixmap *get_city_sprite(const city_sprite &city_sprite, - const struct city *pcity) -{ - // get style and match the best tile based on city size - int style = style_of_city(pcity); - int img_index; - - fc_assert_ret_val(style < city_sprite.size(), nullptr); - - const auto num_thresholds = city_sprite[style].size(); - const auto &thresholds = city_sprite[style]; - - if (num_thresholds == 0) { - return nullptr; - } - - // Get the sprite with the index defined by the effects. - img_index = pcity->client.city_image; - if (img_index == -100) { - /* Server doesn't know right value as this is from old savegame. - * Guess here based on *client* side information as was done in - * versions where information was not saved to savegame - this should - * give us right answer of what city looked like by the time it was - * put under FoW. */ - img_index = get_city_bonus(pcity, EFT_CITY_IMAGE); - } - img_index = CLIP(0, img_index, num_thresholds - 1); - - const auto owner = - pcity->owner; // city_owner asserts when there is no owner - auto color = QColor(); - if (owner && owner->rgb) { - color.setRgb(owner->rgb->r, owner->rgb->g, owner->rgb->b); - } - return thresholds[img_index]->pixmap(color); -} - -/** - Allocates one threshold set for city sprite - */ -static styles load_city_thresholds_sprites(struct tileset *t, QString tag, - char *graphic, char *graphic_alt) -{ - char *gfx_in_use = graphic; - auto thresholds = styles(); - - for (int size = 0; size < MAX_CITY_SIZE; size++) { - const auto buffer = QStringLiteral("%1_%2_%3") - .arg(gfx_in_use, tag, QString::number(size)); - if (const auto sprite = load_sprite(t, buffer)) { - thresholds.push_back( - std::make_unique(*sprite, t->replaced_hue)); - } else if (size == 0) { - if (gfx_in_use == graphic) { - // Try again with graphic_alt. - size--; - gfx_in_use = graphic_alt; - } else { - // Don't load any others if the 0 element isn't there. - break; - } - } - } - - return thresholds; -} - -/** - Allocates and loads a new city sprite from the given sprite tags. - - tag may be nullptr. - - See also get_city_sprite, free_city_sprite. - */ -static city_sprite load_city_sprite(struct tileset *t, const QString &tag) -{ - auto csprite = city_sprite(); - - for (int i = 0; i < game.control.styles_count; ++i) { - csprite.push_back(load_city_thresholds_sprites( - t, tag, city_styles[i].graphic, city_styles[i].graphic_alt)); - } - - return csprite; -} - /** Initialize 'sprites' structure based on hardwired tags which freeciv always requires. @@ -2630,7 +2515,6 @@ static city_sprite load_city_sprite(struct tileset *t, const QString &tag) static void tileset_lookup_sprite_tags(struct tileset *t) { QString buffer, buffer2; - const int W = t->normal_tile_width, H = t->normal_tile_height; int i, j, f; fc_assert_ret(t->sprite_hash != nullptr); @@ -2661,9 +2545,6 @@ static void tileset_lookup_sprite_tags(struct tileset *t) } else { assign_sprite(t, t->sprites.mask.tile, {"mask.tile"}, true); } - assign_sprite(t, t->sprites.mask.worked_tile, {"mask.worked_tile"}, true); - assign_sprite(t, t->sprites.mask.unworked_tile, {"mask.unworked_tile"}, - true); assign_sprite(t, t->sprites.tax_luxury, {"s.tax_luxury"}, true); assign_sprite(t, t->sprites.tax_science, {"s.tax_science"}, true); @@ -2706,7 +2587,6 @@ static void tileset_lookup_sprite_tags(struct tileset *t) assign_sprite(t, t->sprites.explode.nuke, {"explode.nuke"}, true); - sprite_vector_init(&t->sprites.explode.unit); for (i = 0;; i++) { QPixmap *sprite; @@ -2715,64 +2595,8 @@ static void tileset_lookup_sprite_tags(struct tileset *t) if (!sprite) { break; } - sprite_vector_append(&t->sprites.explode.unit, sprite); - } - - assign_sprite(t, t->sprites.unit.auto_attack, {"unit.auto_attack"}, true); - assign_sprite(t, t->sprites.unit.auto_settler, {"unit.auto_settler"}, - true); - assign_sprite(t, t->sprites.unit.auto_explore, {"unit.auto_explore"}, - true); - assign_sprite(t, t->sprites.unit.fortified, {"unit.fortified"}, true); - assign_sprite(t, t->sprites.unit.fortifying, {"unit.fortifying"}, true); - assign_sprite(t, t->sprites.unit.go_to, {"unit.goto"}, true); - assign_sprite(t, t->sprites.unit.irrigate, {"unit.irrigate"}, true); - assign_sprite(t, t->sprites.unit.plant, {"unit.plant"}, true); - assign_sprite(t, t->sprites.unit.pillage, {"unit.pillage"}, true); - assign_sprite(t, t->sprites.unit.sentry, {"unit.sentry"}, true); - assign_sprite(t, t->sprites.unit.convert, {"unit.convert"}, true); - assign_sprite(t, t->sprites.unit.stack, {"unit.stack"}, true); - assign_sprite(t, t->sprites.unit.loaded, {"unit.loaded"}, true); - assign_sprite(t, t->sprites.unit.transform, {"unit.transform"}, true); - assign_sprite(t, t->sprites.unit.connect, {"unit.connect"}, true); - assign_sprite(t, t->sprites.unit.patrol, {"unit.patrol"}, true); - for (i = 0; i < MAX_NUM_BATTLEGROUPS; i++) { - QStringList buffer = { - QStringLiteral("unit.battlegroup_%1").arg(QString::number(i)), - QStringLiteral("city.size_%1").arg(QString::number(i + 1))}; - fc_assert(MAX_NUM_BATTLEGROUPS < NUM_TILES_DIGITS); - assign_sprite(t, t->sprites.unit.battlegroup[i], {buffer}, true); - } - assign_sprite(t, t->sprites.unit.lowfuel, {"unit.lowfuel"}, true); - assign_sprite(t, t->sprites.unit.tired, {"unit.tired"}, true); - - assign_sprite(t, t->sprites.unit.action_decision_want, - {"unit.action_decision_want"}, false); - - for (i = 0; i <= 100; i++) { - buffer = QStringLiteral("unit.hp_%1").arg(QString::number(i)); - auto sprite = load_sprite(t, buffer); - if (sprite) { - t->sprites.unit.hp_bar.push_back(sprite); - } - } - - for (i = 0; i < MAX_VET_LEVELS; i++) { - /* Veteran level sprites are optional. For instance "green" units - * usually have no special graphic. */ - buffer = QStringLiteral("unit.vet_%1").arg(QString::number(i)); - t->sprites.unit.vet_lev[i] = load_sprite(t, buffer); - } - - for (i = 0;; i++) { - buffer = QStringLiteral("unit.select%1").arg(QString::number(i)); - auto sprite = load_sprite(t, buffer); - if (!sprite) { - break; - } - t->sprites.unit.select.push_back(sprite); + t->sprites.explode.unit.push_back(sprite); } - focus_unit_state = 0; assign_sprite(t, t->sprites.citybar.shields, {"citybar.shields"}, true); assign_sprite(t, t->sprites.citybar.food, {"citybar.food"}, true); @@ -2780,7 +2604,6 @@ static void tileset_lookup_sprite_tags(struct tileset *t) assign_sprite(t, t->sprites.citybar.occupied, {"citybar.occupied"}, true); assign_sprite(t, t->sprites.citybar.background, {"citybar.background"}, true); - sprite_vector_init(&t->sprites.citybar.occupancy); for (i = 0;; i++) { QPixmap *sprite; @@ -2789,9 +2612,9 @@ static void tileset_lookup_sprite_tags(struct tileset *t) if (!sprite) { break; } - sprite_vector_append(&t->sprites.citybar.occupancy, sprite); + t->sprites.citybar.occupancy.push_back(sprite); } - if (t->sprites.citybar.occupancy.size < 2) { + if (t->sprites.citybar.occupancy.size() < 2) { tileset_error(t, LOG_FATAL, _("Missing necessary citybar.occupancy_N sprites.")); } @@ -2817,54 +2640,6 @@ static void tileset_lookup_sprite_tags(struct tileset *t) assign_sprite(t, t->sprites.editor.military_base, {"editor.military_base"}, true); - assign_sprite(t, t->sprites.city.disorder, {"city.disorder"}, true); - assign_sprite(t, t->sprites.city.happy, {"city.happy"}, false); - - // digit sprites - QStringList patterns = {QStringLiteral("city.size_%1")}; - assign_digit_sprites(t, t->sprites.city.size, t->sprites.city.size_tens, - t->sprites.city.size_hundreds, patterns); - patterns.prepend(QStringLiteral("path.turns_%1")); - assign_digit_sprites(t, t->sprites.path.s[GTS_MP_LEFT].turns, - t->sprites.path.s[GTS_MP_LEFT].turns_tens, - t->sprites.path.s[GTS_MP_LEFT].turns_hundreds, - patterns); - patterns.prepend(QStringLiteral("path.steps_%1")); - assign_digit_sprites(t, t->sprites.path.s[GTS_TURN_STEP].turns, - t->sprites.path.s[GTS_TURN_STEP].turns_tens, - t->sprites.path.s[GTS_TURN_STEP].turns_hundreds, - patterns); - patterns[0] = QStringLiteral("path.exhausted_mp_%1"); - assign_digit_sprites(t, t->sprites.path.s[GTS_EXHAUSTED_MP].turns, - t->sprites.path.s[GTS_EXHAUSTED_MP].turns_tens, - t->sprites.path.s[GTS_EXHAUSTED_MP].turns_hundreds, - patterns); - - for (int i = 0;; ++i) { - buffer = QStringLiteral("city.t_food_%1").arg(QString::number(i)); - if (auto sprite = load_sprite(t, buffer)) { - t->sprites.city.tile_foodnum.push_back(*sprite); - } else { - break; - } - } - for (int i = 0;; ++i) { - buffer = QStringLiteral("city.t_shields_%1").arg(QString::number(i)); - if (auto sprite = load_sprite(t, buffer)) { - t->sprites.city.tile_shieldnum.push_back(*sprite); - } else { - break; - } - } - for (int i = 0;; ++i) { - buffer = QStringLiteral("city.t_trade_%1").arg(QString::number(i)); - if (auto sprite = load_sprite(t, buffer)) { - t->sprites.city.tile_tradenum.push_back(*sprite); - } else { - break; - } - } - // Must have at least one upkeep sprite per output type (and unhappy) // The rest are optional. buffer = QStringLiteral("upkeep.unhappy"); @@ -2908,204 +2683,50 @@ static void tileset_lookup_sprite_tags(struct tileset *t) assign_sprite(t, t->sprites.user.attention, {"user.attention"}, true); - assign_sprite(t, t->sprites.path.s[GTS_MP_LEFT].specific, {"path.normal"}, - false); - assign_sprite(t, t->sprites.path.s[GTS_EXHAUSTED_MP].specific, - {"path.exhausted_mp"}, false); - assign_sprite(t, t->sprites.path.s[GTS_TURN_STEP].specific, {"path.step"}, - false); - assign_sprite(t, t->sprites.path.waypoint, {"path.waypoint"}, true); - - assign_sprite(t, t->sprites.tx.fog, {"tx.fog"}, true); - - sprite_vector_init(&t->sprites.colors.overlays); - for (i = 0;; i++) { - QPixmap *sprite; + // Initialize all class-based layers + for (auto &layer : t->layers) { + layer->load_sprites(); + } +} - buffer = QStringLiteral("colors.overlay_%1").arg(QString::number(i)); - sprite = load_sprite(t, buffer); - if (!sprite) { - break; +/** + Frees any internal buffers which are created by load_sprite. Should + be called after the last (for a given period of time) load_sprite + call. This saves a fair amount of memory, but it will take extra time + the next time we start loading sprites again. + */ +void finish_loading_sprites(struct tileset *t) +{ + for (auto *sf : qAsConst(*t->specfiles)) { + if (sf->big_sprite) { + delete sf->big_sprite; + sf->big_sprite = nullptr; } - sprite_vector_append(&t->sprites.colors.overlays, sprite); - } - if (i == 0) { - tileset_error(t, LOG_FATAL, - _("Missing overlay-color sprite colors.overlay_0.")); } +} - // Chop up and build the overlay graphics. - sprite_vector_reserve(&t->sprites.city.worked_tile_overlay, - sprite_vector_size(&t->sprites.colors.overlays)); - sprite_vector_reserve(&t->sprites.city.unworked_tile_overlay, - sprite_vector_size(&t->sprites.colors.overlays)); - for (i = 0; i < sprite_vector_size(&t->sprites.colors.overlays); i++) { - QPixmap *color, *color_mask; - QPixmap *worked, *unworked; +/** + Load the tiles; requires tilespec_read_toplevel() called previously. + Leads to tile_sprites being allocated and filled with pointers + to sprites. Also sets up and populates sprite_hash, and calls func + to initialize 'sprites' structure. + */ +void tileset_load_tiles(struct tileset *t) +{ + tileset_lookup_sprite_tags(t); + finish_loading_sprites(t); +} - color = *sprite_vector_get(&t->sprites.colors.overlays, i); - color_mask = crop_sprite(color, 0, 0, W, H, t->sprites.mask.tile, 0, 0); - worked = crop_sprite(color_mask, 0, 0, W, H, t->sprites.mask.worked_tile, - 0, 0); - unworked = crop_sprite(color_mask, 0, 0, W, H, - t->sprites.mask.unworked_tile, 0, 0); - delete color_mask; - t->sprites.city.worked_tile_overlay.p[i] = worked; - t->sprites.city.unworked_tile_overlay.p[i] = unworked; - } - - { - assign_sprite(t, t->sprites.grid.unavailable, {"grid.unavailable"}, - true); - assign_sprite(t, t->sprites.grid.nonnative, {"grid.nonnative"}, false); - - for (i = 0; i < EDGE_COUNT; i++) { - int be; - - if (i == EDGE_UD && t->hex_width == 0) { - continue; - } else if (i == EDGE_LR && t->hex_height == 0) { - continue; - } - - buffer = QStringLiteral("grid.main.%1").arg(edge_name[i]); - assign_sprite(t, t->sprites.grid.main[i], {buffer}, true); - - buffer = QStringLiteral("grid.city.%1").arg(edge_name[i]); - assign_sprite(t, t->sprites.grid.city[i], {buffer}, true); - - buffer = QStringLiteral("grid.worked.%1").arg(edge_name[i]); - assign_sprite(t, t->sprites.grid.worked[i], {buffer}, true); - - buffer = QStringLiteral("grid.selected.%1").arg(edge_name[i]); - assign_sprite(t, t->sprites.grid.selected[i], {buffer}, true); - - buffer = QStringLiteral("grid.coastline.%1").arg(edge_name[i]); - assign_sprite(t, t->sprites.grid.coastline[i], {buffer}, true); - - for (be = 0; be < 2; be++) { - buffer = QStringLiteral("grid.borders.%1").arg(edge_name[i][be]); - assign_sprite(t, t->sprites.grid.borders[i][be], {buffer}, true); - } - } - } - - switch (t->darkness_layer->style()) { - case freeciv::DARKNESS_NONE: - // Nothing. - break; - case freeciv::DARKNESS_ISORECT: { - // Isometric: take a single tx.darkness tile and split it into 4. - QPixmap *darkness = load_sprite(t, QStringLiteral("tx.darkness")); - const int ntw = t->normal_tile_width, nth = t->normal_tile_height; - int offsets[4][2] = { - {ntw / 2, 0}, {0, nth / 2}, {ntw / 2, nth / 2}, {0, 0}}; - - if (!darkness) { - tileset_error(t, LOG_FATAL, _("Sprite tx.darkness missing.")); - } - for (i = 0; i < 4; i++) { - const auto sprite = std::unique_ptr( - crop_sprite(darkness, offsets[i][0], offsets[i][1], ntw / 2, - nth / 2, nullptr, 0, 0)); - t->darkness_layer->set_sprite(i, *sprite); - } - } break; - case freeciv::DARKNESS_CARD_SINGLE: - for (i = 0; i < t->num_cardinal_tileset_dirs; i++) { - enum direction8 dir = t->cardinal_tileset_dirs[i]; - - buffer = - QStringLiteral("tx.darkness_%1").arg(dir_get_tileset_name(dir)); - - const auto sprite = load_sprite(t, buffer); - if (sprite) { - t->darkness_layer->set_sprite(i, *sprite); - } else { - tileset_error(t, LOG_FATAL, _("Sprite for tag '%s' missing."), - qUtf8Printable(buffer)); - } - } - break; - case freeciv::DARKNESS_CARD_FULL: - for (i = 1; i < t->num_index_cardinal; i++) { - buffer = - QStringLiteral("tx.darkness_%1").arg(cardinal_index_str(t, i)); - - const auto sprite = load_sprite(t, buffer); - if (sprite) { - t->darkness_layer->set_sprite(i, *sprite); - } else { - tileset_error(t, LOG_FATAL, _("Sprite for tag '%s' missing."), - qUtf8Printable(buffer)); - } - } - break; - case freeciv::DARKNESS_CORNER: - t->sprites.tx.fullfog = static_cast(fc_realloc( - t->sprites.tx.fullfog, 81 * sizeof(*t->sprites.tx.fullfog))); - for (i = 0; i < 81; i++) { - // Unknown, fog, known. - char ids[] = {'u', 'f', 'k'}; - char buf[512] = "t.fog"; - int values[4], vi, k = i; - - for (vi = 0; vi < 4; vi++) { - values[vi] = k % 3; - k /= 3; - - cat_snprintf(buf, sizeof(buf), "_%c", ids[values[vi]]); - } - fc_assert(k == 0); - - t->sprites.tx.fullfog[i] = load_sprite(t, buf); - } - break; - }; - - // no other place to initialize these variables - sprite_vector_init(&t->sprites.nation_flag); - sprite_vector_init(&t->sprites.nation_shield); -} - -/** - Frees any internal buffers which are created by load_sprite. Should - be called after the last (for a given period of time) load_sprite - call. This saves a fair amount of memory, but it will take extra time - the next time we start loading sprites again. - */ -void finish_loading_sprites(struct tileset *t) -{ - for (auto *sf : qAsConst(*t->specfiles)) { - if (sf->big_sprite) { - delete sf->big_sprite; - sf->big_sprite = nullptr; - } - } -} - -/** - Load the tiles; requires tilespec_read_toplevel() called previously. - Leads to tile_sprites being allocated and filled with pointers - to sprites. Also sets up and populates sprite_hash, and calls func - to initialize 'sprites' structure. - */ -void tileset_load_tiles(struct tileset *t) -{ - tileset_lookup_sprite_tags(t); - finish_loading_sprites(t); -} - -/** - Lookup sprite to match tag, or else to match alt if don't find, - or else return nullptr, and emit log message. - */ -QPixmap *tiles_lookup_sprite_tag_alt(struct tileset *t, QtMsgType level, - const char *tag, const char *alt, - const char *what, const char *name, - bool scale) -{ - QPixmap *sp; +/** + Lookup sprite to match tag, or else to match alt if don't find, + or else return nullptr, and emit log message. + */ +QPixmap *tiles_lookup_sprite_tag_alt(struct tileset *t, QtMsgType level, + const char *tag, const char *alt, + const char *what, const char *name, + bool scale) +{ + QPixmap *sp; // (should get sprite_hash before connection) fc_assert_ret_val_msg(nullptr != t->sprite_hash, nullptr, @@ -3120,9 +2741,10 @@ QPixmap *tiles_lookup_sprite_tag_alt(struct tileset *t, QtMsgType level, sp = load_sprite(t, alt); if (sp) { - qDebug("Using alternate graphic \"%s\" " - "(instead of \"%s\") for %s \"%s\".", - alt, tag, what, name); + qCDebug(tileset_category, + "Using alternate graphic \"%s\" " + "(instead of \"%s\") for %s \"%s\".", + alt, tag, what, name); return sp; } @@ -3289,15 +2911,23 @@ void tileset_setup_tech_type(struct tileset *t, struct advance *padvance) * Make the list of possible tag names for the extras which * may vary depending on the terrain they're on. */ -static QStringList make_tag_terrain_list(const QString &prefix, - const QString &suffix, - const struct terrain *pterrain) +QStringList make_tag_terrain_list(const QString &prefix, + const QString &suffix, + const struct terrain *pterrain) { - return { - QStringLiteral("%1_%2%3").arg(prefix, pterrain->graphic_str, suffix), - QStringLiteral("%1_%2%3").arg(prefix, pterrain->graphic_alt, suffix), - QStringLiteral("%1%2").arg(prefix, suffix), - }; + if (!strlen(pterrain->graphic_alt) + || pterrain->graphic_alt == QStringLiteral("-")) { + return { + QStringLiteral("%1_%2%3").arg(prefix, pterrain->graphic_str, suffix), + QStringLiteral("%1%2").arg(prefix, suffix), + }; + } else { + return { + QStringLiteral("%1_%2%3").arg(prefix, pterrain->graphic_str, suffix), + QStringLiteral("%1_%2%3").arg(prefix, pterrain->graphic_alt, suffix), + QStringLiteral("%1%2").arg(prefix, suffix), + }; + } } /** @@ -3307,11 +2937,9 @@ static QStringList make_tag_terrain_list(const QString &prefix, void tileset_setup_extra(struct tileset *t, struct extra_type *pextra) { const int id = extra_index(pextra); - int extrastyle; if (!fc_strcasecmp(pextra->graphic_str, "none")) { - // Extra without graphics - t->sprites.extras[id].extrastyle = extrastyle_id_invalid(); + // Extra without graphics; nothing to do } else { const char *tag; @@ -3323,64 +2951,19 @@ void tileset_setup_extra(struct tileset *t, struct extra_type *pextra) _("No extra style for \"%s\" or \"%s\"."), pextra->graphic_str, pextra->graphic_alt); } else { - qDebug("Using alternate graphic \"%s\" " - "(instead of \"%s\") for extra \"%s\".", - pextra->graphic_alt, pextra->graphic_str, - extra_rule_name(pextra)); + qCDebug(tileset_category, + "Using alternate graphic \"%s\" " + "(instead of \"%s\") for extra \"%s\".", + pextra->graphic_alt, pextra->graphic_str, + extra_rule_name(pextra)); } } - extrastyle = t->estyle_hash->value(tag); - - t->sprites.extras[id].extrastyle = extrastyle; - - extra_type_list_append(t->style_lists[extrastyle], pextra); - - terrain_type_iterate(pterrain) - { - switch (extrastyle) { - case ESTYLE_3LAYER: - tileset_setup_base(t, pextra, tag); - break; + auto extrastyle = t->estyle_hash->value(tag); - case ESTYLE_ROAD_ALL_SEPARATE: - tileset_setup_crossing_separate(t, pextra, pterrain, tag); - break; - case ESTYLE_ROAD_PARITY_COMBINED: - tileset_setup_crossing_parity(t, pextra, pterrain, tag); - break; - case ESTYLE_ROAD_ALL_COMBINED: - tileset_setup_crossing_combined(t, pextra, pterrain, tag); - break; - case ESTYLE_RIVER: - tileset_setup_river(t, pextra, pterrain, tag); - break; - - case ESTYLE_SINGLE1: - t->special_layers.background->set_sprite(pextra, tag); - break; - case ESTYLE_SINGLE2: - t->special_layers.middleground->set_sprite(pextra, tag); - break; - - case ESTYLE_CARDINALS: { - /* We use direction-specific irrigation and farmland graphics, if - * they are available. If not, we just fall back to the basic - * irrigation graphics. */ - for (int i = 0; i < t->num_index_cardinal; i++) { - QStringList tags = - make_tag_terrain_list(tag, cardinal_index_str(t, i), pterrain); - QStringList alt_tags = make_tag_terrain_list(tag, "", pterrain); - assign_sprite( - t, - t->sprites.extras[id].u[terrain_index(pterrain)].cardinals[i], - tags + alt_tags, true); - } - } break; - case ESTYLE_COUNT: - break; - } + // Also init modern class-based layers + for (auto &layer : t->layers) { + layer->initialize_extra(pextra, tag, extrastyle); } - terrain_type_iterate_end; } if (!fc_strcasecmp(pextra->activity_gfx, "none")) { @@ -3405,187 +2988,6 @@ void tileset_setup_extra(struct tileset *t, struct extra_type *pextra) } } -/** - * Set road/rail/maglev sprite values for ESTYLE_ROAD_ALL_SEPARATE. - * should only happen after tilespec_load_tiles(). - */ -static void tileset_setup_crossing_separate(struct tileset *t, - struct extra_type *pextra, - struct terrain *pterrain, - const char *tag) -{ - QString full_tag_name; - const int id = extra_index(pextra); - - /* place isolated sprites */ - assign_sprite( - t, t->sprites.extras[id].u[terrain_index(pterrain)].road.isolated, - make_tag_terrain_list(tag, "_isolated", pterrain), true); - - /* place the directional sprite options, one per corner. */ - for (int i = 0; i < t->num_valid_tileset_dirs; i++) { - enum direction8 dir = t->valid_tileset_dirs[i]; - QString dir_name = QStringLiteral("_%1").arg(dir_get_tileset_name(dir)); - assign_sprite( - t, t->sprites.extras[id].u[terrain_index(pterrain)].road.ru.dir[i], - make_tag_terrain_list(tag, dir_name, pterrain), true); - } - - /* place special corner road sprites */ - for (int i = 0; i < t->num_valid_tileset_dirs; i++) { - enum direction8 dir = t->valid_tileset_dirs[i]; - - if (!is_cardinal_tileset_dir(t, dir)) { - QString dtn = QStringLiteral("_c_%1").arg(dir_get_tileset_name(dir)); - assign_sprite( - t, - t->sprites.extras[id].u[terrain_index(pterrain)].road.corner[dir], - make_tag_terrain_list(tag, dtn, pterrain), false); - } - } -} - -/* Set road/rail/maglev sprite values for ESTYLE_ROAD_PARITY_COMBINED. - * should only happen after tilespec_load_tiles(). - */ -static void tileset_setup_crossing_parity(struct tileset *t, - struct extra_type *pextra, - struct terrain *pterrain, - const char *tag) -{ - QString full_tag_name; - const int id = extra_index(pextra); - int i; - - /* place isolated sprites */ - assign_sprite( - t, t->sprites.extras[id].u[terrain_index(pterrain)].road.isolated, - make_tag_terrain_list(tag, "_isolated", pterrain), true); - - int num_index = 1 << (t->num_valid_tileset_dirs / 2), j; - - /* place the directional sprite options. - * The comment below exemplifies square tiles: - * additional sprites for each road type: 16 each for cardinal and diagonal - * directions. Each set of 16 provides a NSEW-indexed sprite to provide - * connectors for all rails in the cardinal/diagonal directions. The 0 - * entry is unused (the "isolated" sprite is used instead). */ - for (i = 1; i < num_index; i++) { - QString c = QStringLiteral("_c_"); - QString d = QStringLiteral("_d_"); - - for (j = 0; j < t->num_valid_tileset_dirs / 2; j++) { - int value = (i >> j) & 1; - c += QStringLiteral("%1%2").arg( - dir_get_tileset_name(t->valid_tileset_dirs[2 * j]), - QString::number(value)); - d += QStringLiteral("%1%2").arg( - dir_get_tileset_name(t->valid_tileset_dirs[2 * j + 1]), - QString::number(value)); - } - - assign_sprite(t, - t->sprites.extras[id] - .u[terrain_index(pterrain)] - .road.ru.combo.even[i], - make_tag_terrain_list(tag, c, pterrain), true); - - assign_sprite(t, - t->sprites.extras[id] - .u[terrain_index(pterrain)] - .road.ru.combo.odd[i], - make_tag_terrain_list(tag, d, pterrain), true); - } - - /* place special corner tiles */ - for (i = 0; i < t->num_valid_tileset_dirs; i++) { - enum direction8 dir = t->valid_tileset_dirs[i]; - - if (!is_cardinal_tileset_dir(t, dir)) { - QString dtn = QStringLiteral("_c_%1").arg(dir_get_tileset_name(dir)); - assign_sprite( - t, - t->sprites.extras[id].u[terrain_index(pterrain)].road.corner[dir], - make_tag_terrain_list(tag, dtn, pterrain), false); - } - } -} - -/* Set road/rail/maglev sprite values for ESTYLE_ROAD_ALL_COMBINED. - * should only happen after tilespec_load_tiles(). - */ -static void tileset_setup_crossing_combined(struct tileset *t, - struct extra_type *pextra, - struct terrain *pterrain, - const char *tag) -{ - const int id = extra_index(pextra); - - /* Just go around clockwise, with all combinations. */ - for (int i = 0; i < t->num_index_valid; i++) { - QString idx_str = QStringLiteral("_%1").arg(valid_index_str(t, i)); - assign_sprite( - t, t->sprites.extras[id].u[terrain_index(pterrain)].road.ru.total[i], - make_tag_terrain_list(tag, idx_str, pterrain), true); - } -} - -/** - * Set river sprites (ESTYLE_RIVER). - * should only happen after tilespec_load_tiles(). - */ -static void tileset_setup_river(struct tileset *t, struct extra_type *pextra, - struct terrain *pterrain, const char *tag) -{ - const int id = extra_index(pextra); - - QString suffix; - - for (int i = 0; i < t->num_index_cardinal; i++) { - suffix = QStringLiteral("_s_%1").arg(cardinal_index_str(t, i)); - assign_sprite(t, - t->sprites.extras[id] - .u[terrain_index(pterrain)] - .road.ru.rivers.spec[i], - make_tag_terrain_list(tag, suffix, pterrain), true); - } - - for (int i = 0; i < t->num_cardinal_tileset_dirs; i++) { - suffix = QStringLiteral("_outlet_%1") - .arg(dir_get_tileset_name(t->cardinal_tileset_dirs[i])); - assign_sprite(t, - t->sprites.extras[id] - .u[terrain_index(pterrain)] - .road.ru.rivers.outlet[i], - make_tag_terrain_list(tag, suffix, pterrain), true); - } -} - -/** - Set base sprite values; should only happen after - tilespec_load_tiles(). - */ -static void tileset_setup_base(struct tileset *t, - const struct extra_type *pextra, - const char *tag) -{ - const int id = extra_index(pextra); - - fc_assert_ret(id >= 0 && id < extra_count()); - - QString full_tag_name = QStringLiteral("%1_bg").arg(tag); - t->special_layers.background->set_sprite( - pextra, full_tag_name, FULL_TILE_X_OFFSET, FULL_TILE_Y_OFFSET); - - full_tag_name = QStringLiteral("%1_mg").arg(tag); - t->special_layers.middleground->set_sprite( - pextra, full_tag_name, FULL_TILE_X_OFFSET, FULL_TILE_Y_OFFSET); - - full_tag_name = QStringLiteral("%1_fg").arg(tag); - t->special_layers.foreground->set_sprite( - pextra, full_tag_name, FULL_TILE_X_OFFSET, FULL_TILE_Y_OFFSET); -} - /** Set tile_type sprite values; should only happen after tilespec_load_tiles(). @@ -3637,12 +3039,11 @@ void tileset_setup_nation_flag(struct tileset *t, struct nation_type *nation) nation_rule_name(nation)); } - sprite_vector_reserve(&t->sprites.nation_flag, game.control.nation_count); - t->sprites.nation_flag.p[nation_index(nation)] = flag; + t->sprites.nation_flag.resize(game.control.nation_count); + t->sprites.nation_flag[nation_index(nation)] = flag; - sprite_vector_reserve(&t->sprites.nation_shield, - game.control.nation_count); - t->sprites.nation_shield.p[nation_index(nation)] = shield; + t->sprites.nation_shield.resize(game.control.nation_count); + t->sprites.nation_shield[nation_index(nation)] = shield; } /** @@ -3657,21 +3058,18 @@ const QPixmap *get_city_flag_sprite(const struct tileset *t, /** Return a sprite for the national flag for this unit. */ -static QPixmap *get_unit_nation_flag_sprite(const struct tileset *t, - const struct unit *punit) +QPixmap *get_unit_nation_flag_sprite(const struct tileset *t, + const struct unit *punit) { struct nation_type *pnation = nation_of_unit(punit); if (gui_options->draw_unit_shields) { - return t->sprites.nation_shield.p[nation_index(pnation)]; + return t->sprites.nation_shield[nation_index(pnation)]; } else { - return t->sprites.nation_flag.p[nation_index(pnation)]; + return t->sprites.nation_flag[nation_index(pnation)]; } } -#define ADD_SPRITE_FULL(s) \ - sprs.emplace_back(t, s, true, FULL_TILE_X_OFFSET, FULL_TILE_Y_OFFSET) - /** Assemble some data that is used in building the tile sprite arrays. (map_x, map_y) : the (normalized) map position @@ -3693,903 +3091,46 @@ void build_tile_data(const struct tile *ptile, struct terrain *pterrain, struct terrain *terrain1 = tile_terrain(tile1); if (nullptr != terrain1) { - tterrain_near[dir] = terrain1; - textras_near[dir] = *tile_extras(tile1); - continue; - } - qCritical("build_tile_data() tile (%d,%d) has no terrain!", - TILE_XY(tile1)); - } - /* At the edges of the (known) map, pretend the same terrain continued - * past the edge of the map. */ - tterrain_near[dir] = pterrain; - BV_CLR_ALL(textras_near[dir]); - } -} - -/** - * Returns the sprite used to represent a given activity on the map. - */ -const QPixmap *get_activity_sprite(const struct tileset *t, - enum unit_activity activity, - extra_type *target) -{ - switch (activity) { - case ACTIVITY_MINE: - if (target == nullptr) { - return t->sprites.unit.plant; - } else { - return t->sprites.extras[extra_index(target)].activity; - } - break; - case ACTIVITY_PLANT: - return t->sprites.unit.plant; - break; - case ACTIVITY_IRRIGATE: - if (target == nullptr) { - return t->sprites.unit.irrigate; - } else { - return t->sprites.extras[extra_index(target)].activity; - } - break; - case ACTIVITY_CULTIVATE: - return t->sprites.unit.irrigate; - break; - case ACTIVITY_POLLUTION: - case ACTIVITY_FALLOUT: - return t->sprites.extras[extra_index(target)].rmact; - break; - case ACTIVITY_PILLAGE: - return t->sprites.unit.pillage; - break; - case ACTIVITY_EXPLORE: - // Drawn below as the server side agent. - break; - case ACTIVITY_FORTIFIED: - return t->sprites.unit.fortified; - break; - case ACTIVITY_FORTIFYING: - return t->sprites.unit.fortifying; - break; - case ACTIVITY_SENTRY: - return t->sprites.unit.sentry; - break; - case ACTIVITY_GOTO: - return t->sprites.unit.go_to; - break; - case ACTIVITY_TRANSFORM: - return t->sprites.unit.transform; - break; - case ACTIVITY_BASE: - case ACTIVITY_GEN_ROAD: - return t->sprites.extras[extra_index(target)].activity; - break; - case ACTIVITY_CONVERT: - return t->sprites.unit.convert; - break; - default: - break; - } - return nullptr; -} - -/** - Fill in the sprite array for the unit. - */ -void fill_unit_sprite_array(const struct tileset *t, - std::vector &sprs, - const tile *ptile, const struct unit *punit) -{ - const struct unit_type *ptype = unit_type_get(punit); - - if (ptile && unit_is_in_focus(punit) && !t->sprites.unit.select.empty()) { - // Special case for drawing the selection rectangle. The blinking unit - // is handled separately, inside get_drawable_unit(). - sprs.emplace_back(t, t->sprites.unit.select[focus_unit_state], true, - t->select_offset_x, t->select_offset_y); - } - - // Flag - if (!ptile || !tile_city(ptile)) { - if (!gui_options->solid_color_behind_units) { - sprs.emplace_back(t, get_unit_nation_flag_sprite(t, punit), true, - FULL_TILE_X_OFFSET + t->unit_flag_offset_x, - FULL_TILE_Y_OFFSET + t->unit_flag_offset_y); - } else { - // Taken care of in the LAYER_BACKGROUND. - } - } - - // Add the sprite for the unit type. - const auto rgb = punit->owner ? punit->owner->rgb : nullptr; - const auto color = rgb ? QColor(rgb->r, rgb->g, rgb->b) : QColor(); - const auto uspr = get_unittype_sprite(t, ptype, punit->facing, color); - sprs.emplace_back(t, uspr, true, FULL_TILE_X_OFFSET + t->unit_offset_x, - FULL_TILE_Y_OFFSET + t->unit_offset_y); - - if (t->sprites.unit.loaded && unit_transported(punit)) { - ADD_SPRITE_FULL(t->sprites.unit.loaded); - } - - // Activity sprite - if (auto sprite = - get_activity_sprite(t, punit->activity, punit->activity_target)) { - sprs.emplace_back(t, sprite, true, - FULL_TILE_X_OFFSET + t->activity_offset_x, - FULL_TILE_Y_OFFSET + t->activity_offset_y); - } - - { - QPixmap *s = nullptr; - int offset_x = 0; - int offset_y = 0; - - switch (punit->ssa_controller) { - case SSA_NONE: - break; - case SSA_AUTOSETTLER: - s = t->sprites.unit.auto_settler; - break; - case SSA_AUTOEXPLORE: - s = t->sprites.unit.auto_explore; - // Specified as an activity in the tileset. - offset_x = t->activity_offset_x; - offset_y = t->activity_offset_y; - break; - default: - s = t->sprites.unit.auto_attack; - break; - } - - if (s != nullptr) { - sprs.emplace_back(t, s, true, FULL_TILE_X_OFFSET + offset_x, - FULL_TILE_Y_OFFSET + offset_y); - } - } - - if (unit_has_orders(punit)) { - if (punit->orders.repeat) { - ADD_SPRITE_FULL(t->sprites.unit.patrol); - } else if (punit->activity != ACTIVITY_IDLE) { - sprs.emplace_back(t, t->sprites.unit.connect); - } else { - sprs.emplace_back(t, t->sprites.unit.go_to, true, - FULL_TILE_X_OFFSET + t->activity_offset_x, - FULL_TILE_Y_OFFSET + t->activity_offset_y); - } - } - - if (t->sprites.unit.action_decision_want != nullptr - && should_ask_server_for_actions(punit)) { - sprs.emplace_back(t, t->sprites.unit.action_decision_want, true, - FULL_TILE_X_OFFSET + t->activity_offset_x, - FULL_TILE_Y_OFFSET + t->activity_offset_y); - } - - if (punit->battlegroup != BATTLEGROUP_NONE) { - ADD_SPRITE_FULL(t->sprites.unit.battlegroup[punit->battlegroup]); - } - - if (t->sprites.unit.lowfuel && utype_fuel(ptype) && punit->fuel == 1 - && punit->moves_left <= 2 * SINGLE_MOVE) { - // Show a low-fuel graphic if the plane has 2 or fewer moves left. - ADD_SPRITE_FULL(t->sprites.unit.lowfuel); - } - if (t->sprites.unit.tired && punit->moves_left < SINGLE_MOVE - && ptype->move_rate > 0) { - /* Show a "tired" graphic if the unit has fewer than one move - * remaining, except for units for which it's full movement. */ - ADD_SPRITE_FULL(t->sprites.unit.tired); - } - - if ((ptile && unit_list_size(ptile->units) > 1) - || punit->client.occupied) { - ADD_SPRITE_FULL(t->sprites.unit.stack); - } - - if (t->sprites.unit.vet_lev[punit->veteran]) { - ADD_SPRITE_FULL(t->sprites.unit.vet_lev[punit->veteran]); - } - - auto ihp = ((t->sprites.unit.hp_bar.size() - 1) * punit->hp) / ptype->hp; - ihp = CLIP(0, ihp, t->sprites.unit.hp_bar.size() - 1); // Safety - ADD_SPRITE_FULL(t->sprites.unit.hp_bar[ihp]); -} - -/** - Add any corner road/rail/maglev sprites to the sprite array. - */ -static void fill_crossing_corner_sprites(const struct tileset *t, - const struct extra_type *pextra, - const struct terrain *pterrain, - std::vector &sprs, - bool road, bool *road_near, - bool hider, bool *hider_near) -{ - int i; - int extra_idx = extra_index(pextra); - - if (is_cardinal_only_road(pextra)) { - return; - } - - /* Roads going diagonally adjacent to this tile need to be - * partly drawn on this tile. */ - - /* Draw the corner sprite if: - * - There is a diagonal road (not rail!) between two adjacent tiles. - * - There is no diagonal road (not rail!) that intersects this road. - * The logic is simple: roads are drawn underneath railrods, but are - * not always covered by them (even in the corners!). But if a railroad - * connects two tiles, only the railroad (no road) is drawn between - * those tiles. - */ - for (i = 0; i < t->num_valid_tileset_dirs; i++) { - enum direction8 dir = t->valid_tileset_dirs[i]; - - if (!is_cardinal_tileset_dir(t, dir)) { - // Draw corner sprites for this non-cardinal direction. - int cw = (i + 1) % t->num_valid_tileset_dirs; - int ccw = - (i + t->num_valid_tileset_dirs - 1) % t->num_valid_tileset_dirs; - enum direction8 cwdir = t->valid_tileset_dirs[cw]; - enum direction8 ccwdir = t->valid_tileset_dirs[ccw]; - - if (t->sprites.extras[extra_idx] - .u[terrain_index(pterrain)] - .road.corner[dir] - && (road_near[cwdir] && road_near[ccwdir] - && !(hider_near[cwdir] && hider_near[ccwdir])) - && !(road && road_near[dir] && !(hider && hider_near[dir]))) { - sprs.emplace_back(t, t->sprites.extras[extra_idx] - .u[terrain_index(pterrain)] - .road.corner[dir]); - } - } - } -} - -/** - Fill all road/rail/maglev sprites into the sprite array. - */ -static void fill_crossing_sprite_array( - const struct tileset *t, const struct extra_type *pextra, - std::vector &sprs, bv_extras textras, - bv_extras *textras_near, struct terrain *tterrain_near[8], - struct terrain *pterrain, const struct city *pcity) -{ - bool road, road_near[8], hider, hider_near[8]; - bool land_near[8], hland_near[8]; - bool draw_road[8], draw_single_road; - int dir; - int extra_idx = -1; - bool cl = false; - int extrastyle; - const struct road_type *proad = extra_road_get(pextra); - - extra_idx = extra_index(pextra); - - extrastyle = t->sprites.extras[extra_idx].extrastyle; - - if (extra_has_flag(pextra, EF_CONNECT_LAND)) { - cl = true; - } else { - int i; - - for (i = 0; i < 8; i++) { - land_near[i] = false; - } - } - - /* Fill some data arrays. rail_near and road_near store whether road/rail - * is present in the given direction. draw_rail and draw_road store - * whether road/rail is to be drawn in that direction. draw_single_road - * and draw_single_rail store whether we need an isolated road/rail to be - * drawn. */ - road = BV_ISSET(textras, extra_idx); - - hider = false; - extra_type_list_iterate(pextra->hiders, phider) - { - if (BV_ISSET(textras, extra_index(phider))) { - hider = true; - break; - } - } - extra_type_list_iterate_end; - - draw_single_road = road && (!pcity || !gui_options->draw_cities) && !hider; - - for (dir = 0; dir < 8; dir++) { - bool roads_exist; - - /* Check if there is adjacent road/rail. */ - if (!is_cardinal_only_road(pextra) - || is_cardinal_tileset_dir(t, static_cast(dir))) { - road_near[dir] = false; - extra_type_list_iterate(proad->integrators, iextra) - { - if (BV_ISSET(textras_near[dir], extra_index(iextra))) { - road_near[dir] = true; - break; - } - } - extra_type_list_iterate_end; - if (cl) { - land_near[dir] = - (tterrain_near[dir] != T_UNKNOWN - && terrain_type_terrain_class(tterrain_near[dir]) != TC_OCEAN); - } - } else { - road_near[dir] = false; - land_near[dir] = false; - } - - /* Draw rail/road if there is a connection from this tile to the - * adjacent tile. But don't draw road if there is also a rail - * connection. */ - roads_exist = road && (road_near[dir] || land_near[dir]); - draw_road[dir] = roads_exist; - hider_near[dir] = false; - hland_near[dir] = - tterrain_near[dir] != T_UNKNOWN - && terrain_type_terrain_class(tterrain_near[dir]) != TC_OCEAN; - extra_type_list_iterate(pextra->hiders, phider) - { - bool hider_dir = false; - bool land_dir = false; - - if (!is_cardinal_only_road(phider) - || is_cardinal_tileset_dir(t, static_cast(dir))) { - if (BV_ISSET(textras_near[dir], extra_index(phider))) { - hider_near[dir] = true; - hider_dir = true; - } - if (hland_near[dir] && is_extra_caused_by(phider, EC_ROAD) - && extra_has_flag(phider, EF_CONNECT_LAND)) { - land_dir = true; - } - if (hider_dir || land_dir) { - if (BV_ISSET(textras, extra_index(phider))) { - draw_road[dir] = false; - } - } - } - } - extra_type_list_iterate_end; - - /* Don't draw an isolated road/rail if there's any connection. - * draw_single_road would be true in the first place only if start tile - * has road, so it will have road connection with any adjacent road - * tile. We check from real existence of road (road_near[dir]) and not - * from whether road gets drawn (draw_road[dir]) as latter can be FALSE - * when road is simply hidden by another one, and we don't want to - * draw single road in that case either. */ - if (draw_single_road && road_near[dir]) { - draw_single_road = false; - } - } - - // Draw road corners - fill_crossing_corner_sprites(t, pextra, pterrain, sprs, road, road_near, - hider, hider_near); - - if (extrastyle == ESTYLE_ROAD_ALL_SEPARATE) { - /* With ESTYLE_ROAD_ALL_SEPARATE, we simply draw one road for every - * connection. This means we only need a few sprites, but a lot of - * drawing is necessary and it generally doesn't look very good. */ - int i; - - // First draw roads under rails. - if (road) { - for (i = 0; i < t->num_valid_tileset_dirs; i++) { - if (draw_road[t->valid_tileset_dirs[i]]) { - sprs.emplace_back(t, t->sprites.extras[extra_idx] - .u[terrain_index(pterrain)] - .road.ru.dir[i]); - } - } - } - } else if (extrastyle == ESTYLE_ROAD_PARITY_COMBINED) { - /* With ESTYLE_ROAD_PARITY_COMBINED, we draw one sprite for cardinal - * road connections, one sprite for diagonal road connections. - * This means we need about 4x more sprites than in style 0, but up to - * 4x less drawing is needed. The drawing quality may also be - * improved. */ - - // First draw roads under rails. - if (road) { - int road_even_tileno = 0, road_odd_tileno = 0, i; - - for (i = 0; i < t->num_valid_tileset_dirs / 2; i++) { - enum direction8 even = t->valid_tileset_dirs[2 * i]; - enum direction8 odd = t->valid_tileset_dirs[2 * i + 1]; - - if (draw_road[even]) { - road_even_tileno |= 1 << i; - } - if (draw_road[odd]) { - road_odd_tileno |= 1 << i; - } - } - - /* Draw the cardinal/even roads first. */ - if (road_even_tileno != 0) { - sprs.emplace_back(t, t->sprites.extras[extra_idx] - .u[terrain_index(pterrain)] - .road.ru.combo.even[road_even_tileno]); - } - if (road_odd_tileno != 0) { - sprs.emplace_back(t, t->sprites.extras[extra_idx] - .u[terrain_index(pterrain)] - .road.ru.combo.odd[road_odd_tileno]); - } - } - } else if (extrastyle == ESTYLE_ROAD_ALL_COMBINED) { - /* RSTYLE_ALL_COMBINED is a very simple method that lets us simply - * retrieve entire finished tiles, with a bitwise index of the presence - * of roads in each direction. */ - - // Draw roads first - if (road) { - int road_tileno = 0, i; - - for (i = 0; i < t->num_valid_tileset_dirs; i++) { - enum direction8 vdir = t->valid_tileset_dirs[i]; - - if (draw_road[vdir]) { - road_tileno |= 1 << i; - } - } - - if (road_tileno != 0 || draw_single_road) { - sprs.emplace_back(t, t->sprites.extras[extra_idx] - .u[terrain_index(pterrain)] - .road.ru.total[road_tileno]); - } - } - } else { - fc_assert(false); - } - - /* Draw isolated rail/road separately (ESTYLE_ROAD_ALL_SEPARATE and - ESTYLE_ROAD_PARITY_COMBINED only). */ - if (extrastyle == ESTYLE_ROAD_ALL_SEPARATE - || extrastyle == ESTYLE_ROAD_PARITY_COMBINED) { - if (draw_single_road) { - sprs.emplace_back(t, t->sprites.extras[extra_idx] - .u[terrain_index(pterrain)] - .road.isolated); - } - } -} - -/** - Return the index of the sprite to be used for irrigation or farmland in - this tile. - - We assume that the current tile has farmland or irrigation. We then - choose a sprite (index) based upon which cardinally adjacent tiles have - either farmland or irrigation (the two are considered interchangable for - this). - */ -static int get_irrigation_index(const struct tileset *t, - struct extra_type *pextra, - bv_extras *textras_near) -{ - int tileno = 0, i; - - for (i = 0; i < t->num_cardinal_tileset_dirs; i++) { - enum direction8 dir = t->cardinal_tileset_dirs[i]; - - if (BV_ISSET(textras_near[dir], extra_index(pextra))) { - tileno |= 1 << i; - } - } - - return tileno; -} - -/** - Fill in the farmland/irrigation sprite for the tile. - */ -static void fill_irrigation_sprite_array(const struct tileset *t, - std::vector &sprs, - bv_extras textras, - bv_extras *textras_near, - const struct terrain *pterrain, - const struct city *pcity) -{ - /* We don't draw the irrigation if there's a city (it just gets overdrawn - * anyway, and ends up looking bad). Unless*/ - if (!(pcity && gui_options->draw_cities)) { - extra_type_list_iterate(t->style_lists[ESTYLE_CARDINALS], pextra) - { - if (is_extra_drawing_enabled(pextra)) { - int eidx = extra_index(pextra); - - if (BV_ISSET(textras, eidx)) { - bool hidden = false; - - extra_type_list_iterate(pextra->hiders, phider) - { - if (BV_ISSET(textras, extra_index(phider))) { - hidden = true; - break; - } - } - extra_type_list_iterate_end; - - if (!hidden) { - int idx = get_irrigation_index(t, pextra, textras_near); - - sprs.emplace_back(t, t->sprites.extras[eidx] - .u[terrain_index(pterrain)] - .cardinals[idx]); - } - } - } - } - extra_type_list_iterate_end; - } -} - -/** - Fill in the city overlays for the tile. This includes the citymap - overlays on the mapview as well as the tile output sprites. - */ -static void fill_city_overlays_sprite_array(const struct tileset *t, - std::vector &sprs, - const struct tile *ptile, - const struct city *citymode) -{ - const struct city *pcity; - const struct city *pwork; - struct unit *psettler = nullptr; - int city_x, city_y; - const int NUM_CITY_COLORS = t->sprites.city.worked_tile_overlay.size; - - if (nullptr == ptile || TILE_UNKNOWN == client_tile_get_known(ptile)) { - return; - } - pwork = tile_worked(ptile); - - if (citymode) { - pcity = citymode; - } else { - pcity = find_city_or_settler_near_tile(ptile, &psettler); - } - - /* Below code does not work if pcity is invisible. - * Make sure it is not. */ - fc_assert_ret(pcity == nullptr || pcity->tile != nullptr); - if (pcity && !pcity->tile) { - pcity = nullptr; - } - - if (pcity && city_base_to_city_map(&city_x, &city_y, pcity, ptile)) { - // FIXME: check elsewhere for valid tile (instead of above) - if (!citymode && pcity->client.colored) { - // Add citymap overlay for a city. - int idx = pcity->client.color_index % NUM_CITY_COLORS; - - if (nullptr != pwork && pwork == pcity) { - sprs.emplace_back(t, t->sprites.city.worked_tile_overlay.p[idx]); - } else if (city_can_work_tile(pcity, ptile)) { - sprs.emplace_back(t, t->sprites.city.unworked_tile_overlay.p[idx]); - } - } else if (nullptr != pwork && pwork == pcity - && (citymode || gui_options->draw_city_output)) { - // Add on the tile output sprites. - const int ox = t->type == TS_ISOMETRIC ? t->normal_tile_width / 3 : 0; - const int oy = - t->type == TS_ISOMETRIC ? -t->normal_tile_height / 3 : 0; - - if (!t->sprites.city.tile_foodnum.empty()) { - int food = city_tile_output_now(pcity, ptile, O_FOOD); - food = CLIP(0, food / game.info.granularity, - t->sprites.city.tile_foodnum.size() - 1); - sprs.emplace_back( - t, const_cast(&t->sprites.city.tile_foodnum[food]), - true, ox, oy); - } - if (!t->sprites.city.tile_shieldnum.empty()) { - int shields = city_tile_output_now(pcity, ptile, O_SHIELD); - shields = CLIP(0, shields / game.info.granularity, - t->sprites.city.tile_shieldnum.size() - 1); - sprs.emplace_back( - t, - const_cast(&t->sprites.city.tile_shieldnum[shields]), - true, ox, oy); - } - if (!t->sprites.city.tile_tradenum.empty()) { - int trade = city_tile_output_now(pcity, ptile, O_TRADE); - trade = CLIP(0, trade / game.info.granularity, - t->sprites.city.tile_tradenum.size() - 1); - sprs.emplace_back( - t, const_cast(&t->sprites.city.tile_tradenum[trade]), - true, ox, oy); - } - } - } else if (psettler && psettler->client.colored) { - // Add citymap overlay for a unit. - int idx = psettler->client.color_index % NUM_CITY_COLORS; - - sprs.emplace_back(t, t->sprites.city.unworked_tile_overlay.p[idx]); - } -} - -/** - Add sprites for fog (and some forms of darkness). - */ -static void fill_fog_sprite_array(const struct tileset *t, - std::vector &sprs, - const struct tile *ptile, - const struct tile_edge *pedge, - const struct tile_corner *pcorner) -{ - if (t->fogstyle == FOG_SPRITE && gui_options->draw_fog_of_war - && nullptr != ptile - && TILE_KNOWN_UNSEEN == client_tile_get_known(ptile)) { - // With FOG_AUTO, fog is done this way. - sprs.emplace_back(t, t->sprites.tx.fog); - } - - if (t->darkness_layer->style() == freeciv::DARKNESS_CORNER && pcorner - && gui_options->draw_fog_of_war) { - int i, tileno = 0; - - for (i = 3; i >= 0; i--) { - const int unknown = 0, fogged = 1, known = 2; - int value = -1; - - if (!pcorner->tile[i]) { - value = fogged; - } else { - switch (client_tile_get_known(pcorner->tile[i])) { - case TILE_KNOWN_SEEN: - value = known; - break; - case TILE_KNOWN_UNSEEN: - value = fogged; - break; - case TILE_UNKNOWN: - value = unknown; - break; - } - } - fc_assert(value >= 0 && value < 3); - - tileno = tileno * 3 + value; - } - - if (t->sprites.tx.fullfog[tileno]) { - sprs.emplace_back(t, t->sprites.tx.fullfog[tileno]); - } - } -} - -/** - Indicate whether a unit is to be drawn with a surrounding city outline - under current conditions. - (This includes being in focus, but if the caller has already checked - that, they can bypass this slightly expensive check with check_focus == - FALSE.) - */ -bool unit_drawn_with_city_outline(const struct unit *punit, bool check_focus) -{ - /* Display an outline for city-builder type units if they are selected, - * and on a tile where a city can be built. - * But suppress the outline if the unit has orders (likely it is in - * transit to somewhere else and this will just slow down redraws). */ - return gui_options->draw_city_outlines && unit_is_cityfounder(punit) - && !unit_has_orders(punit) - && (client_tile_get_known(unit_tile(punit)) != TILE_UNKNOWN - && city_can_be_built_here(unit_tile(punit), punit)) - && (!check_focus || unit_is_in_focus(punit)); -} - -/** - Fill in the grid sprites for the given tile, city, and unit. - */ -static void fill_grid_sprite_array(const struct tileset *t, - std::vector &sprs, - const struct tile *ptile, - const struct tile_edge *pedge, - const struct city *citymode) -{ - if (pedge) { - bool known[NUM_EDGE_TILES], city[NUM_EDGE_TILES]; - bool unit[NUM_EDGE_TILES], worked[NUM_EDGE_TILES]; - int i; - - for (i = 0; i < NUM_EDGE_TILES; i++) { - int dummy_x, dummy_y; - const struct tile *tile = pedge->tile[i]; - struct player *powner = tile ? tile_owner(tile) : nullptr; - - known[i] = tile && client_tile_get_known(tile) != TILE_UNKNOWN; - unit[i] = false; - if (tile && !citymode) { - for (const auto pfocus_unit : get_units_in_focus()) { - if (unit_drawn_with_city_outline(pfocus_unit, false)) { - struct tile *utile = unit_tile(pfocus_unit); - int radius = game.info.init_city_radius_sq - + get_target_bonus_effects( - nullptr, unit_owner(pfocus_unit), nullptr, - nullptr, nullptr, utile, nullptr, nullptr, - nullptr, nullptr, nullptr, EFT_CITY_RADIUS_SQ); - - if (city_tile_to_city_map(&dummy_x, &dummy_y, radius, utile, - tile)) { - unit[i] = true; - break; - } - } - } - } - worked[i] = false; - - city[i] = (tile - && (nullptr == powner || nullptr == client.conn.playing - || powner == client.conn.playing) - && player_in_city_map(client.conn.playing, tile)); - if (city[i]) { - if (citymode) { - /* In citymode, we only draw worked tiles for this city - other - * tiles may be marked as unavailable. */ - worked[i] = (tile_worked(tile) == citymode); - } else { - worked[i] = (nullptr != tile_worked(tile)); - } - } - // Draw city grid for main citymap - if (tile && citymode - && city_base_to_city_map(&dummy_x, &dummy_y, citymode, tile)) { - sprs.emplace_back(t, t->sprites.grid.selected[pedge->type]); - } - } - if (mapdeco_is_highlight_set(pedge->tile[0]) - || mapdeco_is_highlight_set(pedge->tile[1])) { - sprs.emplace_back(t, t->sprites.grid.selected[pedge->type]); - } else { - if (gui_options->draw_map_grid) { - if (worked[0] || worked[1]) { - sprs.emplace_back(t, t->sprites.grid.worked[pedge->type]); - } else if (city[0] || city[1]) { - sprs.emplace_back(t, t->sprites.grid.city[pedge->type]); - } else if (known[0] || known[1]) { - sprs.emplace_back(t, t->sprites.grid.main[pedge->type]); - } - } - if (gui_options->draw_city_outlines) { - if (XOR(city[0], city[1])) { - sprs.emplace_back(t, t->sprites.grid.city[pedge->type]); - } - if (XOR(unit[0], unit[1])) { - sprs.emplace_back(t, t->sprites.grid.worked[pedge->type]); - } - } - } - - if (gui_options->draw_borders && BORDERS_DISABLED != game.info.borders - && known[0] && known[1]) { - struct player *owner0 = tile_owner(pedge->tile[0]); - struct player *owner1 = tile_owner(pedge->tile[1]); - - if (owner0 != owner1) { - if (owner0) { - int plrid = player_index(owner0); - sprs.emplace_back( - t, t->sprites.player[plrid].grid_borders[pedge->type][0]); - } - if (owner1) { - int plrid = player_index(owner1); - sprs.emplace_back( - t, t->sprites.player[plrid].grid_borders[pedge->type][1]); - } - } - } - } else if (nullptr != ptile - && TILE_UNKNOWN != client_tile_get_known(ptile)) { - int cx, cy; - - if (citymode - // test to ensure valid coordinates? - && city_base_to_city_map(&cx, &cy, citymode, ptile) - && !client_city_can_work_tile(citymode, ptile)) { - sprs.emplace_back(t, t->sprites.grid.unavailable); - } - - if (gui_options->draw_native && citymode == nullptr) { - bool native = true; - for (const auto pfocus : get_units_in_focus()) { - if (!is_native_tile(unit_type_get(pfocus), ptile)) { - native = false; - break; - } - } - - if (!native) { - if (t->sprites.grid.nonnative != nullptr) { - sprs.emplace_back(t, t->sprites.grid.nonnative); - } else { - sprs.emplace_back(t, t->sprites.grid.unavailable); - } - } - } - } -} - -/** - Fill in the given sprite array with any needed goto sprites. - */ -static void fill_goto_sprite_array(const struct tileset *t, - std::vector &sprs, - const struct tile *ptile, - const struct tile_edge *pedge, - const struct tile_corner *pcorner) -{ - QPixmap *sprite; - bool warn = false; - enum goto_tile_state state; - int length; - bool waypoint; - - if (goto_tile_state(ptile, &state, &length, &waypoint)) { - if (length >= 0) { - fc_assert_ret(state >= 0); - fc_assert_ret(state < ARRAY_SIZE(t->sprites.path.s)); - - sprite = t->sprites.path.s[state].specific; - if (sprite != nullptr) { - sprs.emplace_back(t, sprite, false, 0, 0); - } - - sprite = t->sprites.path.s[state].turns[length % 10]; - sprs.emplace_back(t, sprite); - if (length >= 10) { - sprite = t->sprites.path.s[state].turns_tens[(length / 10) % 10]; - sprs.emplace_back(t, sprite); - if (length >= 100) { - sprite = - t->sprites.path.s[state].turns_hundreds[(length / 100) % 10]; - - if (sprite != nullptr) { - sprs.emplace_back(t, sprite); - if (length >= 1000) { - warn = true; - } - } else { - warn = true; - } - } - } - } - - if (waypoint) { - sprs.emplace_back(t, t->sprites.path.waypoint, false, 0, 0); - } - - if (warn) { - // Warn only once by tileset. - static char last_reported[256] = ""; - - if (0 != strcmp(last_reported, t->name)) { - qInfo(_("Tileset \"%s\" doesn't support long goto paths, " - "such as %d. Path not displayed as expected."), - t->name, length); - sz_strlcpy(last_reported, t->name); + tterrain_near[dir] = terrain1; + textras_near[dir] = *tile_extras(tile1); + continue; } + qCCritical(tileset_category, + "build_tile_data() tile (%d,%d) has no terrain!", + TILE_XY(tile1)); } + /* At the edges of the (known) map, pretend the same terrain continued + * past the edge of the map. */ + tterrain_near[dir] = pterrain; + BV_CLR_ALL(textras_near[dir]); } } +/** + Indicate whether a unit is to be drawn with a surrounding city outline + under current conditions. + (This includes being in focus, but if the caller has already checked + that, they can bypass this slightly expensive check with check_focus == + FALSE.) + */ +bool unit_drawn_with_city_outline(const struct unit *punit, bool check_focus) +{ + /* Display an outline for city-builder type units if they are selected, + * and on a tile where a city can be built. + * But suppress the outline if the unit has orders (likely it is in + * transit to somewhere else and this will just slow down redraws). */ + return gui_options->draw_city_outlines && unit_is_cityfounder(punit) + && !unit_has_orders(punit) + && (client_tile_get_known(unit_tile(punit)) != TILE_UNKNOWN + && city_can_be_built_here(unit_tile(punit), punit)) + && (!check_focus || unit_is_in_focus(punit)); +} + /** Should the given extra be drawn FIXME: Some extras can not be switched */ -bool is_extra_drawing_enabled(struct extra_type *pextra) +bool is_extra_drawing_enabled(const extra_type *pextra) { bool no_disable = true; // Draw if matches no cause @@ -4640,438 +3181,14 @@ bool is_extra_drawing_enabled(struct extra_type *pextra) return no_disable; } -/** - Fill in the sprite array for the given tile, city, and unit. - - ptile, if specified, gives the tile. If specified the terrain and - specials will be drawn for this tile. In this case (map_x,map_y) should - give the location of the tile. - - punit, if specified, gives the unit. For tile drawing this should - generally be get_drawable_unit(); otherwise it can be any unit. - - pcity, if specified, gives the city. For tile drawing this should - generally be tile_city(ptile); otherwise it can be any city. - */ -std::vector -fill_sprite_array(struct tileset *t, enum mapview_layer layer, - const struct tile *ptile, const struct tile_edge *pedge, - const struct tile_corner *pcorner, - const struct unit *punit) -{ - int tileno, dir; - bv_extras textras_near[8]{}; - bv_extras textras; - struct terrain *tterrain_near[8] = {nullptr}; - struct terrain *pterrain = nullptr; - - const auto pcity = tile_city(ptile); - - /* Unit drawing is disabled when the view options are turned off, - * but only where we're drawing on the mapview. */ - bool do_draw_unit = - (punit - && (gui_options->draw_units || !ptile - || (gui_options->draw_focus_unit && unit_is_in_focus(punit)))); - bool solid_bg = (gui_options->solid_color_behind_units - && (do_draw_unit || (pcity && gui_options->draw_cities))); - - const city *citymode = is_any_city_dialog_open(); - - if (ptile && client_tile_get_known(ptile) != TILE_UNKNOWN) { - textras = *tile_extras(ptile); - pterrain = tile_terrain(ptile); - - if (nullptr != pterrain) { - if (layer == LAYER_TERRAIN1 || layer == LAYER_TERRAIN2 - || layer == LAYER_TERRAIN3 || layer == LAYER_WATER - || layer == LAYER_ROADS) { - build_tile_data(ptile, pterrain, tterrain_near, textras_near); - } - } else if (!tile_virtual_check(ptile)) { - qCritical("fill_sprite_array() tile (%d,%d) has no terrain!", - TILE_XY(ptile)); - } - } else { - BV_CLR_ALL(textras); - } - - auto sprs = std::vector(); - sprs.reserve(80); - - switch (layer) { - case LAYER_BACKGROUND: - fc_assert_ret_val(false, {}); - break; - - case LAYER_TERRAIN1: - fc_assert_ret_val(false, {}); - break; - - case LAYER_DARKNESS: - fc_assert_ret_val(false, {}); - break; - - case LAYER_TERRAIN2: - fc_assert_ret_val(false, {}); - break; - - case LAYER_TERRAIN3: - fc_assert_ret_val(false, {}); - break; - - case LAYER_WATER: - if (nullptr != pterrain) { - if (!solid_bg && terrain_type_terrain_class(pterrain) == TC_OCEAN) { - for (dir = 0; dir < t->num_cardinal_tileset_dirs; dir++) { - int didx = t->cardinal_tileset_dirs[dir]; - - extra_type_list_iterate(t->style_lists[ESTYLE_RIVER], priver) - { - int idx = extra_index(priver); - - if (BV_ISSET(textras_near[didx], idx)) { - sprs.emplace_back(t, t->sprites.extras[idx] - .u[terrain_index(pterrain)] - .road.ru.rivers.outlet[dir]); - } - } - extra_type_list_iterate_end; - } - } - } - - fill_irrigation_sprite_array(t, sprs, textras, textras_near, pterrain, - pcity); - - if (!solid_bg) { - extra_type_list_iterate(t->style_lists[ESTYLE_RIVER], priver) - { - int idx = extra_index(priver); - - if (BV_ISSET(textras, idx)) { - int i; - - // Draw rivers on top of irrigation. - tileno = 0; - for (i = 0; i < t->num_cardinal_tileset_dirs; i++) { - enum direction8 cdir = t->cardinal_tileset_dirs[i]; - - if (tterrain_near[cdir] == nullptr - || terrain_type_terrain_class(tterrain_near[cdir]) - == TC_OCEAN - || BV_ISSET(textras_near[cdir], idx)) { - tileno |= 1 << i; - } - } - - sprs.emplace_back(t, t->sprites.extras[idx] - .u[terrain_index(pterrain)] - .road.ru.rivers.spec[tileno]); - } - } - extra_type_list_iterate_end; - } - break; - - case LAYER_ROADS: - /** - * Future code-dweller beware: We do not fully understand how - * fill_crossing_sprite_array works for the ESTYLE_ROAD_PARITY_COMBINED - * sprites. We do know that our recent changes for the terrain-specific - * extras caused an issue when the client tried to assemble an edge tile - * where one edge is null. We have found that wrapping all of this under - * `if (ptile)` solved the error, and we will leave it there. Good luck. - * HF and LM. - */ - if (ptile) { - extra_type_list_iterate(t->style_lists[ESTYLE_ROAD_ALL_SEPARATE], - pextra) - { - if (is_extra_drawing_enabled(pextra)) { - fill_crossing_sprite_array(t, pextra, sprs, textras, textras_near, - tterrain_near, pterrain, pcity); - } - } - extra_type_list_iterate_end; - extra_type_list_iterate(t->style_lists[ESTYLE_ROAD_PARITY_COMBINED], - pextra) - { - if (is_extra_drawing_enabled(pextra)) { - fill_crossing_sprite_array(t, pextra, sprs, textras, textras_near, - tterrain_near, pterrain, pcity); - } - } - extra_type_list_iterate_end; - extra_type_list_iterate(t->style_lists[ESTYLE_ROAD_ALL_COMBINED], - pextra) - { - if (is_extra_drawing_enabled(pextra)) { - fill_crossing_sprite_array(t, pextra, sprs, textras, textras_near, - tterrain_near, pterrain, pcity); - } - } - extra_type_list_iterate_end; - } - break; - - case LAYER_SPECIAL1: - fc_assert_ret_val(false, {}); - break; - - case LAYER_GRID1: - if (t->type == TS_ISOMETRIC) { - fill_grid_sprite_array(t, sprs, ptile, pedge, citymode); - } - break; - - case LAYER_CITY1: - // City. Some city sprites are drawn later. - if (pcity && gui_options->draw_cities) { - if (!citybar_painter::current()->has_flag()) { - sprs.emplace_back(t, get_city_flag_sprite(t, pcity), true, - FULL_TILE_X_OFFSET + t->city_flag_offset_x, - FULL_TILE_Y_OFFSET + t->city_flag_offset_y); - } - bool occupied_graphic = !citybar_painter::current()->has_units(); - fill_basic_city_sprite_array(t, sprs, pcity, occupied_graphic); - } - break; - - case LAYER_SPECIAL2: - fc_assert_ret_val(false, {}); - break; - - case LAYER_UNIT: - case LAYER_FOCUS_UNIT: - fc_assert_ret_val(false, {}); - break; - - case LAYER_SPECIAL3: - fc_assert_ret_val(false, {}); - break; - - case LAYER_BASE_FLAGS: - fc_assert_ret_val(false, {}); - break; - - case LAYER_FOG: - fill_fog_sprite_array(t, sprs, ptile, pedge, pcorner); - break; - - case LAYER_CITY2: - // City size. Drawing this under fog makes it hard to read. - if (pcity && gui_options->draw_cities - && !citybar_painter::current()->has_size()) { - bool warn = false; - unsigned int size = city_size_get(pcity); - - sprs.emplace_back(t, t->sprites.city.size[size % 10], false, - FULL_TILE_X_OFFSET + t->city_size_offset_x, - FULL_TILE_Y_OFFSET + t->city_size_offset_y); - if (10 <= size) { - sprs.emplace_back(t, t->sprites.city.size_tens[(size / 10) % 10], - false, FULL_TILE_X_OFFSET + t->city_size_offset_x, - FULL_TILE_Y_OFFSET + t->city_size_offset_y); - if (100 <= size) { - QPixmap *sprite = t->sprites.city.size_hundreds[(size / 100) % 10]; - - if (nullptr != sprite) { - sprs.emplace_back(t, sprite, false, - FULL_TILE_X_OFFSET + t->city_size_offset_x, - FULL_TILE_Y_OFFSET + t->city_size_offset_y); - } else { - warn = true; - } - if (1000 <= size) { - warn = true; - } - } - } - - if (warn) { - // Warn only once by tileset. - static char last_reported[256] = ""; - - if (0 != strcmp(last_reported, t->name)) { - qInfo(_("Tileset \"%s\" doesn't support big cities size, " - "such as %d. Size not displayed as expected."), - t->name, size); - sz_strlcpy(last_reported, t->name); - } - } - } - break; - - case LAYER_GRID2: - if (t->type == TS_OVERHEAD) { - fill_grid_sprite_array(t, sprs, ptile, pedge, citymode); - } - break; - - case LAYER_OVERLAYS: - fill_city_overlays_sprite_array(t, sprs, ptile, citymode); - if (mapdeco_is_crosshair_set(ptile)) { - sprs.emplace_back(t, t->sprites.user.attention); - } - break; - - case LAYER_CITYBAR: - case LAYER_TILELABEL: - // Nothing. This is just a placeholder. - break; - - case LAYER_GOTO: - if (ptile && goto_is_active()) { - fill_goto_sprite_array(t, sprs, ptile, pedge, pcorner); - } - break; - - case LAYER_WORKERTASK: - if (citymode != nullptr && ptile != nullptr) { - worker_task_list_iterate(citymode->task_reqs, ptask) - { - if (ptask->ptile == ptile) { - switch (ptask->act) { - case ACTIVITY_MINE: - if (ptask->tgt == nullptr) { - sprs.emplace_back(t, t->sprites.unit.plant, true, - FULL_TILE_X_OFFSET + t->activity_offset_x, - FULL_TILE_Y_OFFSET + t->activity_offset_y); - } else { - sprs.emplace_back( - t, t->sprites.extras[extra_index(ptask->tgt)].activity, - true, FULL_TILE_X_OFFSET + t->activity_offset_x, - FULL_TILE_Y_OFFSET + t->activity_offset_y); - } - break; - case ACTIVITY_PLANT: - sprs.emplace_back(t, t->sprites.unit.plant, true, - FULL_TILE_X_OFFSET + t->activity_offset_x, - FULL_TILE_Y_OFFSET + t->activity_offset_y); - break; - case ACTIVITY_IRRIGATE: - if (ptask->tgt == nullptr) { - sprs.emplace_back(t, t->sprites.unit.irrigate, true, - FULL_TILE_X_OFFSET + t->activity_offset_x, - FULL_TILE_Y_OFFSET + t->activity_offset_y); - } else { - sprs.emplace_back( - t, t->sprites.extras[extra_index(ptask->tgt)].activity, - true, FULL_TILE_X_OFFSET + t->activity_offset_x, - FULL_TILE_Y_OFFSET + t->activity_offset_y); - } - break; - case ACTIVITY_CULTIVATE: - sprs.emplace_back(t, t->sprites.unit.irrigate, true, - FULL_TILE_X_OFFSET + t->activity_offset_x, - FULL_TILE_Y_OFFSET + t->activity_offset_y); - break; - case ACTIVITY_GEN_ROAD: - sprs.emplace_back( - t, t->sprites.extras[extra_index(ptask->tgt)].activity, true, - FULL_TILE_X_OFFSET + t->activity_offset_x, - FULL_TILE_Y_OFFSET + t->activity_offset_y); - break; - case ACTIVITY_TRANSFORM: - sprs.emplace_back(t, t->sprites.unit.transform, true, - FULL_TILE_X_OFFSET + t->activity_offset_x, - FULL_TILE_Y_OFFSET + t->activity_offset_y); - break; - case ACTIVITY_POLLUTION: - case ACTIVITY_FALLOUT: - sprs.emplace_back( - t, t->sprites.extras[extra_index(ptask->tgt)].rmact, true, - FULL_TILE_X_OFFSET + t->activity_offset_x, - FULL_TILE_Y_OFFSET + t->activity_offset_y); - break; - default: - break; - } - } - } - worker_task_list_iterate_end; - } - break; - - case LAYER_EDITOR: - if (ptile && editor_is_active()) { - if (editor_tile_is_selected(ptile)) { - int color = 2 % tileset_num_city_colors(tileset); - sprs.emplace_back(t, t->sprites.city.unworked_tile_overlay.p[color]); - } - - if (nullptr != map_startpos_get(ptile)) { - // FIXME: Use a more representative sprite. - sprs.emplace_back(t, t->sprites.user.attention); - } - } - break; - - case LAYER_INFRAWORK: - if (ptile != nullptr && ptile->placing != nullptr) { - const int id = extra_index(ptile->placing); - - if (t->sprites.extras[id].activity != nullptr) { - sprs.emplace_back(t, t->sprites.extras[id].activity, true, - FULL_TILE_X_OFFSET + t->activity_offset_x, - FULL_TILE_Y_OFFSET + t->activity_offset_y); - } - } - break; - - case LAYER_COUNT: - fc_assert(false); - break; - } - - return sprs; -} - /** Set city tiles sprite values; should only happen after tilespec_load_tiles(). */ void tileset_setup_city_tiles(struct tileset *t, int style) { - if (style == game.control.styles_count - 1) { - int i; - - // Free old sprites - t->sprites.city.tile.clear(); - - for (i = 0; i < NUM_WALL_TYPES; i++) { - t->sprites.city.wall[i].clear(); - } - t->sprites.city.single_wall.clear(); - t->sprites.city.occupied.clear(); - - t->sprites.city.tile = load_city_sprite(t, QStringLiteral("city")); - - for (i = 0; i < NUM_WALL_TYPES; i++) { - QString buffer; - - buffer = QStringLiteral("bldg_%1").arg(QString::number(i)); - t->sprites.city.wall[i] = load_city_sprite(t, buffer); - } - t->sprites.city.single_wall = - load_city_sprite(t, QStringLiteral("wall")); - - t->sprites.city.occupied = - load_city_sprite(t, QStringLiteral("occupied")); - - for (style = 0; style < game.control.styles_count; style++) { - if (t->sprites.city.tile[style].empty()) { - tileset_error(t, LOG_FATAL, - _("City style \"%s\": no city graphics."), - city_style_rule_name(style)); - } - if (t->sprites.city.occupied[style].empty()) { - tileset_error(t, LOG_FATAL, - _("City style \"%s\": no occupied graphics."), - city_style_rule_name(style)); - } - } + for (auto &&layer : t->layers) { + layer->initialize_city_style(city_styles[style], style); } } @@ -5082,7 +3199,7 @@ void tileset_setup_city_tiles(struct tileset *t, int style) */ int get_focus_unit_toggle_timeout(const struct tileset *t) { - if (t->sprites.unit.select.empty()) { + if (t->focus_units_layer->focus_unit_state_count() == 0) { return 100; } else { return t->select_step_ms; @@ -5093,14 +3210,17 @@ int get_focus_unit_toggle_timeout(const struct tileset *t) Reset the focus unit state. This should be called when changing focus units. */ -void reset_focus_unit_state(struct tileset *t) { focus_unit_state = 0; } +void reset_focus_unit_state(struct tileset *t) +{ + t->focus_units_layer->focus_unit_state() = 0; +} /** Setup tileset for showing combat where focus unit participates. */ void focus_unit_in_combat(struct tileset *t) { - if (t->sprites.unit.select.empty()) { + if (t->focus_units_layer->focus_unit_state_count() == 0) { reset_focus_unit_state(t); } } @@ -5111,11 +3231,12 @@ void focus_unit_in_combat(struct tileset *t) */ void toggle_focus_unit_state(struct tileset *t) { - focus_unit_state++; - if (t->sprites.unit.select.empty()) { - focus_unit_state %= 2; + t->focus_units_layer->focus_unit_state()++; + if (t->focus_units_layer->focus_unit_state_count() == 0) { + t->focus_units_layer->focus_unit_state() %= 2; } else { - focus_unit_state %= t->sprites.unit.select.size(); + t->focus_units_layer->focus_unit_state() %= + t->focus_units_layer->focus_unit_state_count(); } } @@ -5130,8 +3251,9 @@ struct unit *get_drawable_unit(const struct tileset *t, const ::tile *ptile) return nullptr; } - if (!unit_is_in_focus(punit) || !t->sprites.unit.select.empty() - || focus_unit_state == 0) { + if (!unit_is_in_focus(punit) + || t->focus_units_layer->focus_unit_state_count() > 0 + || t->focus_units_layer->focus_unit_state() == 0) { return punit; } else { return nullptr; @@ -5162,21 +3284,10 @@ static void unload_all_sprites(struct tileset *t) */ void tileset_free_tiles(struct tileset *t) { - int i; - log_debug("tileset_free_tiles()"); unload_all_sprites(t); - t->sprites.city.tile.clear(); - - for (i = 0; i < NUM_WALL_TYPES; i++) { - t->sprites.city.wall[i].clear(); - } - t->sprites.city.single_wall.clear(); - - t->sprites.city.occupied.clear(); - if (t->sprite_hash) { delete t->sprite_hash; t->sprite_hash = nullptr; @@ -5201,30 +3312,10 @@ void tileset_free_tiles(struct tileset *t) } t->specfiles->clear(); - sprite_vector_iterate(&t->sprites.city.worked_tile_overlay, psprite) - { - delete *psprite; - } - sprite_vector_iterate_end; - sprite_vector_free(&t->sprites.city.worked_tile_overlay); - - sprite_vector_iterate(&t->sprites.city.unworked_tile_overlay, psprite) - { - delete *psprite; - } - sprite_vector_iterate_end; - sprite_vector_free(&t->sprites.city.unworked_tile_overlay); - - if (t->sprites.tx.fullfog) { - free(t->sprites.tx.fullfog); - t->sprites.tx.fullfog = nullptr; - } - - sprite_vector_free(&t->sprites.colors.overlays); - sprite_vector_free(&t->sprites.explode.unit); - sprite_vector_free(&t->sprites.nation_flag); - sprite_vector_free(&t->sprites.nation_shield); - sprite_vector_free(&t->sprites.citybar.occupancy); + t->sprites.explode.unit.clear(); + t->sprites.nation_flag.clear(); + t->sprites.nation_shield.clear(); + t->sprites.citybar.occupancy.clear(); } /** @@ -5276,7 +3367,7 @@ const QPixmap *get_citizen_sprite(const struct tileset *t, const QPixmap *get_nation_flag_sprite(const struct tileset *t, const struct nation_type *pnation) { - return t->sprites.nation_flag.p[nation_index(pnation)]; + return t->sprites.nation_flag[nation_index(pnation)]; } /** @@ -5285,7 +3376,7 @@ const QPixmap *get_nation_flag_sprite(const struct tileset *t, const QPixmap *get_nation_shield_sprite(const struct tileset *t, const struct nation_type *pnation) { - return t->sprites.nation_shield.p[nation_index(pnation)]; + return t->sprites.nation_shield[nation_index(pnation)]; } /** @@ -5351,20 +3442,6 @@ const QPixmap *get_unittype_sprite(const struct tileset *t, } } -/** - Return a "sample" sprite for this city style. - */ -const QPixmap *get_sample_city_sprite(const struct tileset *t, int style_idx) -{ - const auto num_thresholds = t->sprites.city.tile[style_idx].size(); - - if (num_thresholds == 0) { - return nullptr; - } else { - return t->sprites.city.tile[style_idx].back()->pixmap(QColor()); - } -} - /** Return a tax sprite for the given output type (usually gold/lux/sci). */ @@ -5424,10 +3501,10 @@ const QPixmap *get_treaty_thumb_sprite(const struct tileset *t, bool on_off) Return a sprite_vector containing the animation sprites for a unit explosion. */ -const struct sprite_vector * +const std::vector & get_unit_explode_animation(const struct tileset *t) { - return &t->sprites.explode.unit; + return t->sprites.explode.unit; } /** @@ -5539,15 +3616,6 @@ const QPixmap *get_unit_upkeep_sprite(const struct tileset *t, } } -/** - Return a rectangular sprite containing a fog "color". This can be used - for drawing fog onto arbitrary areas (like the overview). - */ -const QPixmap *get_basic_fog_sprite(const struct tileset *t) -{ - return t->sprites.tx.fog; -} - /** Return the tileset's color system. */ @@ -5559,75 +3627,7 @@ struct color_system *get_color_system(const struct tileset *t) /** Initialize tileset structure */ -void tileset_init(struct tileset *t) -{ - player_slots_iterate(pslot) - { - int edge, j, id = player_slot_index(pslot); - - for (edge = 0; edge < EDGE_COUNT; edge++) { - for (j = 0; j < 2; j++) { - t->sprites.player[id].grid_borders[edge][j] = nullptr; - } - } - - t->sprites.player[id].color = nullptr; - } - player_slots_iterate_end; - - t->max_upkeep_height = 0; -} - -/** - * Fills @c sprs with sprites to draw a city. The flag and city size are not - * included. The occupied graphic is optional. - */ -void fill_basic_city_sprite_array(const struct tileset *t, - std::vector &sprs, - const city *pcity, bool occupied_graphic) -{ - /* For iso-view the city.wall graphics include the full city, whereas - * for non-iso view they are an overlay on top of the base city - * graphic. */ - if (t->type == TS_OVERHEAD || pcity->client.walls <= 0) { - sprs.emplace_back(t, get_city_sprite(t->sprites.city.tile, pcity), true, - FULL_TILE_X_OFFSET + t->city_offset_x, - FULL_TILE_Y_OFFSET + t->city_offset_y); - } - if (t->type == TS_ISOMETRIC && pcity->client.walls > 0) { - auto spr = get_city_sprite(t->sprites.city.wall[pcity->client.walls - 1], - pcity); - if (spr == nullptr) { - spr = get_city_sprite(t->sprites.city.single_wall, pcity); - } - - if (spr != nullptr) { - sprs.emplace_back(t, spr, true, FULL_TILE_X_OFFSET + t->city_offset_x, - FULL_TILE_Y_OFFSET + t->city_offset_y); - } - } - if (occupied_graphic && pcity->client.occupied) { - sprs.emplace_back(t, get_city_sprite(t->sprites.city.occupied, pcity), - true, FULL_TILE_X_OFFSET + t->occupied_offset_x, - FULL_TILE_Y_OFFSET + t->occupied_offset_y); - } - if (t->type == TS_OVERHEAD && pcity->client.walls > 0) { - auto spr = get_city_sprite(t->sprites.city.wall[pcity->client.walls - 1], - pcity); - if (spr == nullptr) { - spr = get_city_sprite(t->sprites.city.single_wall, pcity); - } - - if (spr != nullptr) { - ADD_SPRITE_FULL(spr); - } - } - if (pcity->client.unhappy) { - ADD_SPRITE_FULL(t->sprites.city.disorder); - } else if (t->sprites.city.happy != nullptr && pcity->client.happy) { - ADD_SPRITE_FULL(t->sprites.city.happy); - } -} +void tileset_init(struct tileset *t) { t->max_upkeep_height = 0; } /** Fill the sprite array with sprites that together make a representative @@ -5652,17 +3652,26 @@ fill_basic_terrain_layer_sprite_array(struct tileset *t, int layer, for (const auto &layer : t->layers) { const auto lsprs = layer->fill_sprite_array(tile, nullptr, nullptr, nullptr); - // Merge by hand because drawn_sprite isn't copyable (but it is - // copy-constructible) - for (const auto &sprite : lsprs) { - sprs.emplace_back(sprite); - } + sprs.insert(sprs.end(), lsprs.begin(), lsprs.end()); } tile_virtual_destroy(tile); return sprs; } +/** + * Returns the layer_city of the tileset. + */ +const freeciv::layer_city *tileset_layer_city(const struct tileset *t) +{ + for (const auto &layer : t->layers) { + if (auto lc = dynamic_cast(layer.get())) { + return lc; + } + } + fc_assert_ret_val(false, nullptr); +} + /** Return a representative sprite for the given extra type. */ @@ -5680,11 +3689,7 @@ fill_basic_extra_sprite_array(const struct tileset *t, for (const auto &layer : t->layers) { const auto lsprs = layer->fill_sprite_array(tile, nullptr, nullptr, nullptr); - // Merge by hand because drawn_sprite isn't copyable (but it is - // copy-constructible) - for (const auto &sprite : lsprs) { - sprs.emplace_back(sprite); - } + sprs.insert(sprs.end(), lsprs.begin(), lsprs.end()); } tile_virtual_destroy(tile); @@ -5702,13 +3707,11 @@ tileset_get_layers(const struct tileset *t) */ void tileset_player_init(struct tileset *t, struct player *pplayer) { - int plrid, i, j; - fc_assert_ret(pplayer != nullptr); - plrid = player_index(pplayer); + auto plrid = player_index(pplayer); fc_assert_ret(plrid >= 0); - fc_assert_ret(plrid < ARRAY_SIZE(t->sprites.player)); + fc_assert_ret(plrid < MAX_NUM_PLAYER_SLOTS); // Free all data before recreating it. tileset_player_free(t, plrid); @@ -5716,28 +3719,6 @@ void tileset_player_init(struct tileset *t, struct player *pplayer) for (auto &&layer : t->layers) { layer->initialize_player(pplayer); } - - QColor c = Qt::black; - if (player_has_color(t, pplayer)) { - c = get_player_color(t, pplayer); - } - QPixmap color(t->normal_tile_width, t->normal_tile_height); - color.fill(c); - - for (i = 0; i < EDGE_COUNT; i++) { - for (j = 0; j < 2; j++) { - QPixmap *s; - - if (t->sprites.grid.borders[i][j]) { - s = crop_sprite(&color, 0, 0, t->normal_tile_width, - t->normal_tile_height, t->sprites.grid.borders[i][j], - 0, 0); - } else { - s = t->sprites.grid.borders[i][j]; - } - t->sprites.player[plrid].grid_borders[i][j] = s; - } - } } /** @@ -5745,28 +3726,12 @@ void tileset_player_init(struct tileset *t, struct player *pplayer) */ static void tileset_player_free(struct tileset *t, int plrid) { - int i, j; - fc_assert_ret(plrid >= 0); - fc_assert_ret(plrid < ARRAY_SIZE(t->sprites.player)); + fc_assert_ret(plrid < MAX_NUM_PLAYER_SLOTS); for (auto &&layer : t->layers) { layer->free_player(plrid); } - - if (t->sprites.player[plrid].color) { - delete t->sprites.player[plrid].color; - t->sprites.player[plrid].color = nullptr; - } - - for (i = 0; i < EDGE_COUNT; i++) { - for (j = 0; j < 2; j++) { - if (t->sprites.player[plrid].grid_borders[i][j]) { - delete t->sprites.player[plrid].grid_borders[i][j]; - t->sprites.player[plrid].grid_borders[i][j] = nullptr; - } - } - } } /** @@ -5774,13 +3739,6 @@ static void tileset_player_free(struct tileset *t, int plrid) */ void tileset_ruleset_reset(struct tileset *t) { - for (int i = 0; i < ESTYLE_COUNT; i++) { - if (t->style_lists[i] != nullptr) { - extra_type_list_destroy(t->style_lists[i]); - t->style_lists[i] = extra_type_list_new(); - } - } - for (auto &layer : t->layers) { layer->reset_ruleset(); } diff --git a/client/tileset/tilespec.h b/client/tileset/tilespec.h index fa16f02d94..e4072823bd 100644 --- a/client/tileset/tilespec.h +++ b/client/tileset/tilespec.h @@ -29,13 +29,9 @@ struct base_type; struct help_item; struct resource_type; -// Create the sprite_vector type. -#define SPECVEC_TAG sprite -#define SPECVEC_TYPE QPixmap * -#include "specvec.h" -#define sprite_vector_iterate(sprite_vec, psprite) \ - TYPED_VECTOR_ITERATE(QPixmap *, sprite_vec, psprite) -#define sprite_vector_iterate_end VECTOR_ITERATE_END +namespace freeciv { +class layer_city; +} #define SPECENUM_NAME ts_type #define SPECENUM_VALUE0 TS_OVERHEAD @@ -44,43 +40,6 @@ struct resource_type; #define SPECENUM_VALUE1NAME N_("Isometric") #include "specenum_gen.h" -#define SPECENUM_NAME fog_style -// Fog is automatically appended by the code. -#define SPECENUM_VALUE0 FOG_AUTO -#define SPECENUM_VALUE0NAME "Auto" -// A single fog sprite is provided by the tileset (tx.fog). -#define SPECENUM_VALUE1 FOG_SPRITE -#define SPECENUM_VALUE1NAME "Sprite" -// No fog, or fog derived from darkness style. -#define SPECENUM_VALUE2 FOG_DARKNESS -#define SPECENUM_VALUE2NAME "Darkness" -#include "specenum_gen.h" - -#define SPECENUM_NAME extrastyle_id -#define SPECENUM_VALUE0 ESTYLE_ROAD_ALL_SEPARATE -#define SPECENUM_VALUE0NAME "RoadAllSeparate" -#define SPECENUM_VALUE1 ESTYLE_ROAD_PARITY_COMBINED -#define SPECENUM_VALUE1NAME "RoadParityCombined" -#define SPECENUM_VALUE2 ESTYLE_ROAD_ALL_COMBINED -#define SPECENUM_VALUE2NAME "RoadAllCombined" -#define SPECENUM_VALUE3 ESTYLE_RIVER -#define SPECENUM_VALUE3NAME "River" -#define SPECENUM_VALUE4 ESTYLE_SINGLE1 -#define SPECENUM_VALUE4NAME "Single1" -#define SPECENUM_VALUE5 ESTYLE_SINGLE2 -#define SPECENUM_VALUE5NAME "Single2" -#define SPECENUM_VALUE6 ESTYLE_3LAYER -#define SPECENUM_VALUE6NAME "3Layer" -#define SPECENUM_VALUE7 ESTYLE_CARDINALS -#define SPECENUM_VALUE7NAME "Cardinals" -#define SPECENUM_COUNT ESTYLE_COUNT -#include "specenum_gen.h" - -// This the way directional indices are now encoded: -#define MAX_INDEX_CARDINAL 64 -#define MAX_INDEX_HALF 16 -#define MAX_INDEX_VALID 256 - #define NUM_TILES_PROGRESS 8 #define NUM_CORNER_DIRS 4 @@ -147,12 +106,22 @@ bool tilespec_reread(const QString &tileset_name, void tilespec_reread_callback(struct option *poption); void tilespec_reread_frozen_refresh(const QString &name); +void assign_digit_sprites(struct tileset *t, + QPixmap *units[NUM_TILES_DIGITS], + QPixmap *tens[NUM_TILES_DIGITS], + QPixmap *hundreds[NUM_TILES_DIGITS], + const QStringList &patterns); + void tileset_setup_specialist_type(struct tileset *t, Specialist_type_id id); void tileset_setup_unit_type(struct tileset *t, struct unit_type *punittype); void tileset_setup_impr_type(struct tileset *t, struct impr_type *pimprove); void tileset_setup_tech_type(struct tileset *t, struct advance *padvance); void tileset_setup_tile_type(struct tileset *t, const struct terrain *pterrain); + +QStringList make_tag_terrain_list(const QString &prefix, + const QString &suffix, + const struct terrain *pterrain); void tileset_setup_extra(struct tileset *t, struct extra_type *pextra); void tileset_setup_government(struct tileset *t, struct government *gov); void tileset_setup_nation_flag(struct tileset *t, @@ -166,20 +135,13 @@ const std::vector> & tileset_get_layers(const struct tileset *t); // Gfx support -QPixmap *load_sprite(struct tileset *t, const QString &tag_name); +QPixmap *load_sprite(struct tileset *t, const QStringList &possible_names, + bool required, bool verbose = true); -std::vector -fill_sprite_array(struct tileset *t, enum mapview_layer layer, - const struct tile *ptile, const struct tile_edge *pedge, - const struct tile_corner *pcorner, - const struct unit *punit); std::vector fill_basic_terrain_layer_sprite_array(struct tileset *t, int layer, struct terrain *pterrain); - -void fill_basic_city_sprite_array(const struct tileset *t, - std::vector &sprs, - const city *pcity, bool occupied_graphic); +const freeciv::layer_city *tileset_layer_city(const struct tileset *t); int get_focus_unit_toggle_timeout(const struct tileset *t); void reset_focus_unit_state(struct tileset *t); @@ -226,7 +188,7 @@ enum spaceship_part { struct citybar_sprites { QPixmap *shields, *food, *trade, *occupied, *background; - struct sprite_vector occupancy; + std::vector occupancy; }; struct editor_sprites { @@ -235,8 +197,6 @@ struct editor_sprites { *properties, *road, *military_base; }; -#define NUM_WALL_TYPES 7 - const QPixmap *get_spaceship_sprite(const struct tileset *t, enum spaceship_part part); const QPixmap *get_citizen_sprite(const struct tileset *t, @@ -248,6 +208,8 @@ const QPixmap *get_city_flag_sprite(const struct tileset *t, void build_tile_data(const struct tile *ptile, struct terrain *pterrain, struct terrain **tterrain_near, bv_extras *textras_near); +QPixmap *get_unit_nation_flag_sprite(const struct tileset *t, + const struct unit *punit); const QPixmap *get_activity_sprite(const struct tileset *t, enum unit_activity activity, extra_type *target); @@ -268,7 +230,7 @@ const QPixmap *get_sample_city_sprite(const struct tileset *t, int style_idx); const QPixmap *get_tax_sprite(const struct tileset *t, Output_type_id otype); const QPixmap *get_treaty_thumb_sprite(const struct tileset *t, bool on_off); -const struct sprite_vector * +const std::vector & get_unit_explode_animation(const struct tileset *t); const QPixmap *get_nuke_explode_sprite(const struct tileset *t); const QPixmap *get_cursor_sprite(const struct tileset *t, @@ -287,15 +249,11 @@ const QPixmap *get_unit_upkeep_sprite(const struct tileset *t, Output_type_id otype, const struct unit *punit, const int *upkeep_cost); -const QPixmap *get_basic_fog_sprite(const struct tileset *t); std::vector fill_basic_extra_sprite_array(const struct tileset *t, const struct extra_type *pextra); -void fill_unit_sprite_array(const struct tileset *t, - std::vector &sprs, - const tile *ptile, const struct unit *punit); -bool is_extra_drawing_enabled(struct extra_type *pextra); +bool is_extra_drawing_enabled(const extra_type *pextra); const QPixmap *get_event_sprite(const struct tileset *t, enum event_type event); const QPixmap *get_dither_sprite(const struct tileset *t); @@ -319,6 +277,7 @@ int tileset_tile_width(const struct tileset *t); int tileset_tile_height(const struct tileset *t); int tileset_full_tile_width(const struct tileset *t); int tileset_full_tile_height(const struct tileset *t); +QPoint tileset_full_tile_offset(const struct tileset *t); int tileset_unit_width(const struct tileset *t); int tileset_unit_height(const struct tileset *t); int tileset_unit_with_upkeep_height(const struct tileset *t); @@ -327,14 +286,19 @@ int tileset_small_sprite_width(const struct tileset *t); int tileset_small_sprite_height(const struct tileset *t); int tileset_citybar_offset_y(const struct tileset *t); int tileset_tilelabel_offset_y(const struct tileset *t); -int tileset_num_city_colors(const struct tileset *t); bool tileset_use_hard_coded_fog(const struct tileset *t); double tileset_preferred_scale(const struct tileset *t); +int tileset_replaced_hue(const struct tileset *t); int tileset_num_cardinal_dirs(const struct tileset *t); int tileset_num_index_cardinals(const struct tileset *t); std::array tileset_cardinal_dirs(const struct tileset *t); +int tileset_num_valid_dirs(const struct tileset *t); +std::array tileset_valid_dirs(const struct tileset *t); QString cardinal_index_str(const struct tileset *t, int idx); +QString valid_index_str(const struct tileset *t, int idx); +QString dir_get_tileset_name(enum direction8 dir); +bool is_cardinal_tileset_dir(const struct tileset *t, enum direction8 dir); /* These are used as array index -> can't be changed freely to values bigger than size of those arrays. */ diff --git a/client/tileset_debugger.cpp b/client/tileset_debugger.cpp index 6ce103d625..126ff149b9 100644 --- a/client/tileset_debugger.cpp +++ b/client/tileset_debugger.cpp @@ -10,9 +10,6 @@ #include "tileset_debugger.h" -// client/include -#include "dialogs_g.h" - // client #include "climap.h" #include "editor.h" @@ -148,8 +145,7 @@ void tileset_debugger::set_tile(const ::tile *t) // Geometry auto rectangle = QRect(); for (const auto &ds : sprites) { - rectangle |= QRect(ds.offset_x, ds.offset_y, ds.sprite->width(), - ds.sprite->height()); + rectangle |= QRect(ds.offset, ds.sprite->size()); } // Draw the composite picture @@ -164,7 +160,7 @@ void tileset_debugger::set_tile(const ::tile *t) // direction. Compensate by offsetting the painter back... p.translate(-rectangle.topLeft() + QPoint(1, 1)); for (const auto &ds : sprites) { - p.drawPixmap(ds.offset_x, ds.offset_y, *ds.sprite); + p.drawPixmap(ds.offset, *ds.sprite); } p.end(); item->setIcon(0, QIcon(this_layer)); @@ -181,11 +177,12 @@ void tileset_debugger::set_tile(const ::tile *t) p.drawRect(0, 0, this_layer.width() - 1, this_layer.height() - 1); // We inherit the translation set above p.translate(-rectangle.topLeft() + QPoint(1, 1)); - p.drawPixmap(ds.offset_x, ds.offset_y, *ds.sprite); + p.drawPixmap(ds.offset, *ds.sprite); p.end(); child->setIcon(0, QIcon(this_sprite)); - child->setText( - 0, QString(_("Offset: %1, %2")).arg(ds.offset_x).arg(ds.offset_y)); + child->setText(0, QString(_("Offset: %1, %2")) + .arg(ds.offset.x()) + .arg(ds.offset.y())); maxSize = maxSize.expandedTo(rectangle.size()); } } diff --git a/client/unitselect.cpp b/client/unitselect.cpp index d4a2199672..d4506cd3f5 100644 --- a/client/unitselect.cpp +++ b/client/unitselect.cpp @@ -114,7 +114,7 @@ void units_select::create_pixmap() unit_pixmap = new QPixmap(tileset_unit_width(tileset), tileset_unit_height(tileset)); unit_pixmap->fill(Qt::transparent); - put_unit(punit, unit_pixmap, 0, 0); + put_unit(punit, unit_pixmap, QPoint()); img = unit_pixmap->toImage(); crop = zealous_crop_rect(img); cropped_img = img.copy(crop); diff --git a/client/views/view_map_common.cpp b/client/views/view_map_common.cpp index 138feaea41..7d4e2b2e7a 100644 --- a/client/views/view_map_common.cpp +++ b/client/views/view_map_common.cpp @@ -773,7 +773,7 @@ bool tile_visible_and_not_on_border_mapcanvas(struct tile *ptile) /** Draw an array of drawn sprites onto the canvas. */ -void put_drawn_sprites(QPixmap *pcanvas, int canvas_x, int canvas_y, +void put_drawn_sprites(QPixmap *pcanvas, const QPoint &canvas_loc, const std::vector &sprites, bool fog, bool city_unit) { @@ -795,14 +795,14 @@ void put_drawn_sprites(QPixmap *pcanvas, int canvas_x, int canvas_y, p2.fillRect(temp.rect(), QColor(0, 0, 0, 110)); p2.end(); - p.drawPixmap(canvas_x + s.offset_x, canvas_y + s.offset_y, temp); + p.drawPixmap(canvas_loc + s.offset, temp); } else { /* We avoid calling canvas_put_sprite_fogged, even though it * should be a valid thing to do, because gui-gtk-2.0 didn't have * a full implementation. */ p.setCompositionMode(QPainter::CompositionMode_SourceOver); p.setOpacity(1); - p.drawPixmap(canvas_x + s.offset_x, canvas_y + s.offset_y, *s.sprite); + p.drawPixmap(canvas_loc + s.offset, *s.sprite); } } p.end(); @@ -816,7 +816,7 @@ void put_one_element(QPixmap *pcanvas, const std::unique_ptr &layer, const struct tile *ptile, const struct tile_edge *pedge, const struct tile_corner *pcorner, - const struct unit *punit, int canvas_x, int canvas_y) + const struct unit *punit, const QPoint &canvas_loc) { bool city_unit = false; int dummy_x, dummy_y; @@ -832,20 +832,20 @@ void put_one_element(QPixmap *pcanvas, } } /*** Draw terrain and specials ***/ - put_drawn_sprites(pcanvas, canvas_x, canvas_y, sprites, fog, city_unit); + put_drawn_sprites(pcanvas, canvas_loc, sprites, fog, city_unit); } /** Draw the given unit onto the canvas store at the given location. The area of drawing is tileset_unit_height(tileset) x tileset_unit_width(tileset). */ -void put_unit(const struct unit *punit, QPixmap *pcanvas, int canvas_x, - int canvas_y) +void put_unit(const struct unit *punit, QPixmap *pcanvas, + const QPoint &canvas_loc) { - canvas_y += (tileset_unit_height(tileset) - tileset_tile_height(tileset)); + auto loc = canvas_loc; + loc.ry() += (tileset_unit_height(tileset) - tileset_tile_height(tileset)); for (const auto &layer : tileset_get_layers(tileset)) { - put_one_element(pcanvas, layer, nullptr, nullptr, nullptr, punit, - canvas_x, canvas_y); + put_one_element(pcanvas, layer, nullptr, nullptr, nullptr, punit, loc); } } @@ -855,15 +855,14 @@ void put_unit(const struct unit *punit, QPixmap *pcanvas, int canvas_x, tileset_full_tile_height(tileset) x tileset_full_tile_width(tileset) (even though most tiles are not this tall). */ -void put_terrain(struct tile *ptile, QPixmap *pcanvas, int canvas_x, - int canvas_y) +void put_terrain(struct tile *ptile, QPixmap *pcanvas, + const QPoint &canvas_loc) { // Use full tile height, even for terrains. - canvas_y += - (tileset_full_tile_height(tileset) - tileset_tile_height(tileset)); + auto loc = canvas_loc; + loc.ry() += (tileset_unit_height(tileset) - tileset_tile_height(tileset)); for (const auto &layer : tileset_get_layers(tileset)) { - put_one_element(pcanvas, layer, ptile, nullptr, nullptr, nullptr, - canvas_x, canvas_y); + put_one_element(pcanvas, layer, ptile, nullptr, nullptr, nullptr, loc); } } @@ -906,9 +905,10 @@ void put_unit_city_overlays(const unit *punit, QPixmap *pcanvas, * tells what color the citymap will be drawn on the mapview. * * This array can be added to without breaking anything elsewhere. + * color_index can grow without limit and even wrap around, the drawing code + * will take care of it. */ static int color_index = 0; -#define NUM_CITY_COLORS tileset_num_city_colors(tileset) /** Toggle the city color. This cycles through the possible colors for the @@ -922,7 +922,7 @@ void toggle_city_color(struct city *pcity) } else { pcity->client.colored = true; pcity->client.color_index = color_index; - color_index = (color_index + 1) % NUM_CITY_COLORS; + color_index++; } refresh_city_mapcanvas(pcity, pcity->tile, true); @@ -940,7 +940,7 @@ void toggle_unit_color(struct unit *punit) } else { punit->client.colored = true; punit->client.color_index = color_index; - color_index = (color_index + 1) % NUM_CITY_COLORS; + color_index++; } refresh_unit_mapcanvas(punit, unit_tile(punit), true); @@ -984,14 +984,14 @@ void put_nuke_mushroom_pixmaps(struct tile *ptile) */ static void put_one_tile(QPixmap *pcanvas, const std::unique_ptr &layer, - const tile *ptile, int canvas_x, int canvas_y) + const tile *ptile, const QPoint &canvas_loc) { if (client_tile_get_known(ptile) != TILE_UNKNOWN || (editor_is_active() && editor_tile_is_selected(ptile))) { struct unit *punit = get_drawable_unit(tileset, ptile); - put_one_element(pcanvas, layer, ptile, nullptr, nullptr, punit, canvas_x, - canvas_y); + put_one_element(pcanvas, layer, ptile, nullptr, nullptr, punit, + canvas_loc); } } @@ -1178,18 +1178,19 @@ void update_map_canvas(int canvas_x, int canvas_y, int width, int height) continue; } for (auto it = freeciv::gui_rect_iterator(tileset, rect); it.next();) { - const int cx = it.x() - mapview.gui_x0, cy = it.y() - mapview.gui_y0; + const auto loc = + QPoint(it.x() - mapview.gui_x0, it.y() - mapview.gui_y0); if (it.has_corner()) { put_one_element(mapview.store, layer, nullptr, nullptr, &it.corner(), - nullptr, cx, cy); + nullptr, loc); } if (it.has_edge()) { put_one_element(mapview.store, layer, nullptr, &it.edge(), nullptr, - nullptr, cx, cy); + nullptr, loc); } if (it.has_tile()) { - put_one_tile(mapview.store, layer, it.tile(), cx, cy); + put_one_tile(mapview.store, layer, it.tile(), loc); } } } @@ -1478,7 +1479,6 @@ void decrease_unit_hp_smooth(struct unit *punit0, int hp0, { struct unit *losing_unit = (hp0 == 0 ? punit0 : punit1); float canvas_x, canvas_y; - int i; set_units_in_combat(punit0, punit1); @@ -1489,8 +1489,7 @@ void decrease_unit_hp_smooth(struct unit *punit0, int hp0, unqueue_mapview_updates(); - const struct sprite_vector *anim = get_unit_explode_animation(tileset); - const int num_tiles_explode_unit = sprite_vector_size(anim); + const auto &anim = get_unit_explode_animation(tileset); while (punit0->hp > hp0 || punit1->hp > hp1) { const int diff0 = punit0->hp - hp0, diff1 = punit1->hp - hp1; @@ -1507,7 +1506,7 @@ void decrease_unit_hp_smooth(struct unit *punit0, int hp0, anim_delay(gui_options->smooth_combat_step_msec); } - if (num_tiles_explode_unit > 0 + if (!anim.empty() && tile_to_canvas_pos(&canvas_x, &canvas_y, unit_tile(losing_unit))) { refresh_unit_mapcanvas(losing_unit, unit_tile(losing_unit), false); unqueue_mapview_updates(); @@ -1516,10 +1515,8 @@ void decrease_unit_hp_smooth(struct unit *punit0, int hp0, tileset_tile_width(tileset), tileset_tile_height(tileset)); p.end(); - for (i = 0; i < num_tiles_explode_unit; i++) { - QPixmap *sprite = *sprite_vector_get(anim, i); - - /* We first draw the explosion onto the unit and draw draw the + for (const auto *sprite : anim) { + /* We first draw the explosion onto the unit and draw the * complete thing onto the map canvas window. This avoids * flickering. */ p.begin(mapview.store); @@ -1612,7 +1609,7 @@ void move_unit_map_canvas(struct unit *punit, struct tile *src_tile, int dx, p.end(); // Draw - put_unit(punit, mapview.store, new_x, new_y); + put_unit(punit, mapview.store, QPoint(new_x, new_y)); dirty_rect(new_x, new_y, tuw, tuh); // Flush. diff --git a/client/views/view_map_common.h b/client/views/view_map_common.h index 0d0e9d1f10..9e6fdf5c68 100644 --- a/client/views/view_map_common.h +++ b/client/views/view_map_common.h @@ -74,10 +74,10 @@ struct tile *get_center_tile_mapcanvas(); bool tile_visible_mapcanvas(struct tile *ptile); bool tile_visible_and_not_on_border_mapcanvas(struct tile *ptile); -void put_unit(const struct unit *punit, QPixmap *pcanvas, int canvas_x, - int canvas_y); -void put_terrain(struct tile *ptile, QPixmap *pcanvas, int canvas_x, - int canvas_y); +void put_unit(const struct unit *punit, QPixmap *pcanvas, + const QPoint &canvas_loc); +void put_terrain(struct tile *ptile, QPixmap *pcanvas, + const QPoint &canvas_loc); void put_unit_city_overlays(const unit *punit, QPixmap *pcanvas, int canvas_x, int canvas_y, @@ -87,7 +87,7 @@ void toggle_unit_color(struct unit *punit); void put_nuke_mushroom_pixmaps(struct tile *ptile); -void put_drawn_sprites(QPixmap *pcanvas, int canvas_x, int canvas_y, +void put_drawn_sprites(QPixmap *pcanvas, const QPoint &canvas_loc, const std::vector &sprites, bool fog, bool city_unit = false); diff --git a/client/widgets/city/city_icon_widget.cpp b/client/widgets/city/city_icon_widget.cpp index 162b8e5a74..6563b3b4e8 100644 --- a/client/widgets/city/city_icon_widget.cpp +++ b/client/widgets/city/city_icon_widget.cpp @@ -8,6 +8,7 @@ // client #include "game.h" +#include "tileset/layer_city.h" #include "tileset/tilespec.h" #include @@ -54,8 +55,8 @@ QSize city_icon_widget::sizeHint() const std::max(tileset_tile_width(tileset), tileset_tile_height(tileset)); if (auto city = game_city_by_number(m_city); city) { - std::vector sprs; - fill_basic_city_sprite_array(tileset, sprs, city, true); + auto sprs = + tileset_layer_city(tileset)->fill_sprite_array_no_flag(city, false); const auto bounds = sprite_array_bounds(sprs); size = std::max(bounds.width(), bounds.height()); } @@ -75,9 +76,9 @@ void city_icon_widget::paintEvent(QPaintEvent *event) return; } - std::vector sprs; - // Keeping the occupied flag disabled because it looks bad with amplio2 - fill_basic_city_sprite_array(tileset, sprs, city, false); + // We don't show the occupied flag as it looks bad with amplio + auto sprs = + tileset_layer_city(tileset)->fill_sprite_array_no_flag(city, false); // Center the sprites auto bounds = sprite_array_bounds(sprs); @@ -88,7 +89,8 @@ void city_icon_widget::paintEvent(QPaintEvent *event) p.translate(bounds.topLeft() - origin); for (const auto sprite : sprs) { - p.drawPixmap(QPointF(sprite.offset_x, sprite.offset_y), *sprite.sprite); + // Floating-point API for DPI scaling. + p.drawPixmap(QPointF(sprite.offset), *sprite.sprite); } } diff --git a/client/widgets/city/upkeep_widget.cpp b/client/widgets/city/upkeep_widget.cpp index 4b84a0f392..d73bce95e3 100644 --- a/client/widgets/city/upkeep_widget.cpp +++ b/client/widgets/city/upkeep_widget.cpp @@ -122,7 +122,7 @@ void upkeep_widget::refresh() auto pixmap = QPixmap(icon_width, tileset_unit_with_upkeep_height(get_tileset())); pixmap.fill(Qt::transparent); - put_unit(unit, &pixmap, 0, 0); + put_unit(unit, &pixmap, QPoint()); auto free_unhappy = get_city_bonus(city, EFT_MAKE_CONTENT_MIL); const auto happy_cost = city_unit_unhappiness(unit, &free_unhappy); diff --git a/translations/core/POTFILES.in b/translations/core/POTFILES.in index 192ff95451..8d3532787d 100644 --- a/translations/core/POTFILES.in +++ b/translations/core/POTFILES.in @@ -92,13 +92,25 @@ client/text.cpp client/themes.cpp client/themes_common.cpp client/tileset/drawn_sprite.cpp -client/tileset/layer.cpp +client/tileset/layer_abstract_activities.cpp client/tileset/layer_background.cpp client/tileset/layer_base_flags.cpp +client/tileset/layer_city.cpp +client/tileset/layer_city_size.cpp client/tileset/layer_darkness.cpp +client/tileset/layer_infrawork.cpp +client/tileset/layer_editor.cpp +client/tileset/layer_fog.cpp +client/tileset/layer_goto.cpp +client/tileset/layer_grid.cpp +client/tileset/layer_overlays.cpp +client/tileset/layer_roads.cpp client/tileset/layer_special.cpp client/tileset/layer_terrain.cpp client/tileset/layer_units.cpp +client/tileset/layer_water.cpp +client/tileset/layer_workertask.cpp +client/tileset/layer.cpp client/tileset/sprite.cpp client/tileset/tilespec.cpp client/tileset_debugger.cpp diff --git a/utility/log.cpp b/utility/log.cpp index 51ae3d4208..78f7ff6cfc 100644 --- a/utility/log.cpp +++ b/utility/log.cpp @@ -49,8 +49,10 @@ static QFile *log_file = nullptr; Parses a log level string as provided by the user on the command line, and installs the corresponding Qt log filters. Prints a warning and returns false if the log level name isn't known. + + Additional logging rules can be passed in Qt format. */ -bool log_init(const QString &level_str) +bool log_init(const QString &level_str, const QStringList &extra_rules) { // Even if it's invalid. log_level = level_str; @@ -84,40 +86,46 @@ bool log_init(const QString &level_str) // Create default filter rules to pass to Qt. We do it this way so the user // can override our simplistic rules with environment variables. + auto rules = QStringList(); if (level_str == QStringLiteral("fatal")) { // Level "fatal" cannot be disabled, so we omit it below. - QLoggingCategory::setFilterRules(QStringLiteral("*.critical = false\n" - "*.warning = false\n" - "*.info = false\n" - "*.debug = false\n")); - return true; + rules += { + QStringLiteral("*.critical = false"), + QStringLiteral("*.warning = false"), + QStringLiteral("*.info = false"), + QStringLiteral("*.debug = false"), + }; } else if (level_str == QStringLiteral("critical")) { - QLoggingCategory::setFilterRules(QStringLiteral("*.critical = true\n" - "*.warning = false\n" - "*.info = false\n" - "*.debug = false\n")); - return true; + rules += { + QStringLiteral("*.critical = true"), + QStringLiteral("*.warning = false"), + QStringLiteral("*.info = false"), + QStringLiteral("*.debug = false"), + }; } else if (level_str == QStringLiteral("warning")) { - QLoggingCategory::setFilterRules(QStringLiteral("*.critical = true\n" - "*.warning = true\n" - "*.info = false\n" - "*.debug = false\n")); - return true; + rules += { + QStringLiteral("*.critical = true"), + QStringLiteral("*.warning = true"), + QStringLiteral("*.info = false"), + QStringLiteral("*.debug = false"), + }; } else if (level_str == QStringLiteral("info")) { - QLoggingCategory::setFilterRules(QStringLiteral("*.critical = true\n" - "*.warning = true\n" - "*.info = true\n" - "*.debug = false\n" - "qt.*.info = false\n")); - return true; + rules += { + QStringLiteral("*.critical = true"), + QStringLiteral("*.warning = true"), + QStringLiteral("*.info = true"), + QStringLiteral("*.debug = false"), + QStringLiteral("qt.*.info = false"), + }; } else if (level_str == QStringLiteral("debug")) { - QLoggingCategory::setFilterRules(QStringLiteral("*.critical = true\n" - "*.warning = true\n" - "*.info = true\n" - "*.debug = true\n" - "qt.*.info = false\n" - "qt.*.debug = false\n")); - return true; + rules += { + QStringLiteral("*.critical = true"), + QStringLiteral("*.warning = true"), + QStringLiteral("*.info = true"), + QStringLiteral("*.debug = true"), + QStringLiteral("qt.*.info = false"), + QStringLiteral("qt.*.debug = false"), + }; } else { // Not a known name // TRANS: Do not translate "fatal", "critical", "warning", "info" or @@ -127,6 +135,13 @@ bool log_init(const QString &level_str) qUtf8Printable(level_str)); return false; } + + rules += extra_rules; + QLoggingCategory::setFilterRules(rules.join('\n')); + + qDebug() << "Applied logging rules" << rules; + + return true; } /** diff --git a/utility/log.h b/utility/log.h index 28d1aa3981..586d844bb9 100644 --- a/utility/log.h +++ b/utility/log.h @@ -32,7 +32,8 @@ constexpr auto LOG_DEBUG = QtDebugMsg; #define __FC_LINE__ __LINE__ void log_close(); -bool log_init(const QString &level_str = QStringLiteral("info")); +bool log_init(const QString &level_str = QStringLiteral("info"), + const QStringList &extra_rules = {}); void log_set_file(const QString &path); const QString &log_get_level();