SirVer has proposed merging lp:~widelands-dev/widelands/kill_overlay_manager into lp:widelands.
Commit message: Delete the overlay_manager. - Draw build help in the draw routines directly. - Give Editor tools the ability to modify which field caps should be used for overlays. This is used to restrict resources, player starting positions and port positions. Requested reviews: Widelands Developers (widelands-dev) For more details, see: https://code.launchpad.net/~widelands-dev/widelands/kill_overlay_manager/+merge/330651 -- Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/kill_overlay_manager into lp:widelands.
=== modified file 'src/editor/CMakeLists.txt' --- src/editor/CMakeLists.txt 2017-08-17 13:37:32 +0000 +++ src/editor/CMakeLists.txt 2017-09-13 07:28:46 +0000 @@ -108,6 +108,5 @@ ui_basic wui wui_common - wui_field_overlay_manager wui_mapview_pixelfunctions ) === modified file 'src/editor/editorinteractive.cc' --- src/editor/editorinteractive.cc 2017-09-10 18:17:55 +0000 +++ src/editor/editorinteractive.cc 2017-09-13 07:28:46 +0000 @@ -51,7 +51,6 @@ #include "scripting/lua_table.h" #include "ui_basic/messagebox.h" #include "ui_basic/progresswindow.h" -#include "wui/field_overlay_manager.h" #include "wui/game_tips.h" #include "wui/interactive_base.h" @@ -190,9 +189,6 @@ // TODO(unknown): get rid of cleanup_for_load, it tends to be very messy // Instead, delete and re-create the egbase. egbase().cleanup_for_load(); - - // Select a tool that doesn't care about map changes - mutable_field_overlay_manager()->register_overlay_callback_function(nullptr); } /// Called just before the editor starts, after postload, init and gfxload. @@ -331,9 +327,14 @@ } } - // TODO(sirver): Do not use the field_overlay_manager, instead draw the - // overlays we are interested in here directly. - field_overlay_manager().foreach_overlay(field.fcoords, blit_overlay); + // Draw build help. + if (buildhelp()) { + const auto* overlay = + get_buildhelp_overlay(tools_->current().nodecaps_for_buildhelp(field.fcoords, ebase)); + if (overlay != nullptr) { + blit_overlay(overlay->pic, overlay->hotspot); + } + } // Draw the player starting position overlays. const auto it = starting_positions.find(field.fcoords); @@ -589,9 +590,6 @@ toolsize_menu.update(toolsize_menu.value()); } } - // A new tool has been selected. Remove all registered overlay callback - // functions. - mutable_field_overlay_manager()->register_overlay_callback_function(nullptr); egbase().mutable_map()->recalc_whole_map(egbase().world()); } tools_->current_pointer = &primary; @@ -680,8 +678,6 @@ case MapWas::kGloballyMutated: break; } - - mutable_field_overlay_manager()->remove_all_overlays(); } EditorInteractive::Tools* EditorInteractive::tools() { === modified file 'src/editor/tools/decrease_resources_tool.h' --- src/editor/tools/decrease_resources_tool.h 2017-01-25 18:55:59 +0000 +++ src/editor/tools/decrease_resources_tool.h 2017-09-13 07:28:46 +0000 @@ -20,6 +20,7 @@ #ifndef WL_EDITOR_TOOLS_DECREASE_RESOURCES_TOOL_H #define WL_EDITOR_TOOLS_DECREASE_RESOURCES_TOOL_H +#include "editor/tools/set_resources_tool.h" #include "editor/tools/tool.h" /// Decreases the resources of a node by a value. @@ -45,6 +46,11 @@ return g_gr->images().get("images/wui/editor/fsel_editor_decrease_resources.png"); } + Widelands::NodeCaps nodecaps_for_buildhelp(const Widelands::FCoords& fcoords, + const Widelands::EditorGameBase& egbase) override { + return resource_tools_nodecaps(fcoords, egbase, cur_res_); + } + int32_t get_change_by() const { return change_by_; } === modified file 'src/editor/tools/increase_resources_tool.h' --- src/editor/tools/increase_resources_tool.h 2017-01-25 18:55:59 +0000 +++ src/editor/tools/increase_resources_tool.h 2017-09-13 07:28:46 +0000 @@ -57,6 +57,11 @@ return g_gr->images().get("images/wui/editor/fsel_editor_increase_resources.png"); } + Widelands::NodeCaps nodecaps_for_buildhelp(const Widelands::FCoords& fcoords, + const Widelands::EditorGameBase& egbase) override { + return resource_tools_nodecaps(fcoords, egbase, cur_res_); + } + int32_t get_change_by() const { return change_by_; } === modified file 'src/editor/tools/set_port_space_tool.cc' --- src/editor/tools/set_port_space_tool.cc 2017-08-13 18:02:53 +0000 +++ src/editor/tools/set_port_space_tool.cc 2017-09-13 07:28:46 +0000 @@ -28,16 +28,19 @@ using namespace Widelands; -/// static callback function for overlay calculation -int32_t editor_tool_set_port_space_callback(const Widelands::FCoords& c, const Map& map) { +namespace { + +Widelands::NodeCaps port_tool_nodecaps(const Widelands::FCoords& c, const Map& map) { NodeCaps const caps = c.field->nodecaps(); if ((caps & BUILDCAPS_SIZEMASK) == BUILDCAPS_BIG) { if (!map.find_portdock(c).empty()) return caps; } - return 0; + return NodeCaps::CAPS_NONE; } +} // namespace + EditorSetPortSpaceTool::EditorSetPortSpaceTool(EditorUnsetPortSpaceTool& the_unset_tool) : EditorTool(the_unset_tool, the_unset_tool) { } @@ -61,7 +64,7 @@ *map, Widelands::Area<Widelands::FCoords>(map->get_fcoords(center.node), args->sel_radius)); do { // check if field is valid - if (editor_tool_set_port_space_callback(mr.location(), *map)) { + if (port_tool_nodecaps(mr.location(), *map) != NodeCaps::CAPS_NONE) { map->set_port_space(mr.location(), true); Area<FCoords> a(mr.location(), 0); map->recalc_for_field_area(world, a); @@ -72,6 +75,12 @@ return nr; } +Widelands::NodeCaps +EditorSetPortSpaceTool::nodecaps_for_buildhelp(const Widelands::FCoords& fcoords, + const Widelands::EditorGameBase& egbase) { + return port_tool_nodecaps(fcoords, egbase.map()); +} + int32_t EditorSetPortSpaceTool::handle_undo_impl(const Widelands::World& world, const NodeAndTriangle<Coords>& center, EditorInteractive& parent, @@ -96,7 +105,7 @@ *map, Widelands::Area<Widelands::FCoords>(map->get_fcoords(center.node), args->sel_radius)); do { // check if field is valid - if (editor_tool_set_port_space_callback(mr.location(), *map)) { + if (port_tool_nodecaps(mr.location(), *map)) { map->set_port_space(mr.location(), false); Area<FCoords> a(mr.location(), 0); map->recalc_for_field_area(world, a); @@ -114,3 +123,10 @@ Map* map) { return parent.tools()->set_port_space.handle_click_impl(world, center, parent, args, map); } + +Widelands::NodeCaps +EditorUnsetPortSpaceTool::nodecaps_for_buildhelp(const Widelands::FCoords& fcoords, + const Widelands::EditorGameBase& egbase) { + return port_tool_nodecaps(fcoords, egbase.map()); +} + === modified file 'src/editor/tools/set_port_space_tool.h' --- src/editor/tools/set_port_space_tool.h 2017-08-13 18:02:53 +0000 +++ src/editor/tools/set_port_space_tool.h 2017-09-13 07:28:46 +0000 @@ -46,6 +46,8 @@ const Image* get_sel_impl() const override { return g_gr->images().get(FSEL_EUPS_FILENAME); } + Widelands::NodeCaps nodecaps_for_buildhelp(const Widelands::FCoords& fcoords, + const Widelands::EditorGameBase& egbase) override; }; /// Sets a buildspace for ports. @@ -68,8 +70,8 @@ const Image* get_sel_impl() const override { return g_gr->images().get(FSEL_ESPS_FILENAME); } + Widelands::NodeCaps nodecaps_for_buildhelp(const Widelands::FCoords& fcoords, + const Widelands::EditorGameBase& egbase) override; }; -int32_t editor_tool_set_port_space_callback(const Widelands::FCoords& c, const Widelands::Map& map); - #endif // end of include guard: WL_EDITOR_TOOLS_SET_PORT_SPACE_TOOL_H === modified file 'src/editor/tools/set_resources_tool.cc' --- src/editor/tools/set_resources_tool.cc 2017-01-25 18:55:59 +0000 +++ src/editor/tools/set_resources_tool.cc 2017-09-13 07:28:46 +0000 @@ -85,3 +85,13 @@ a.set_to = set_to_; return a; } + +Widelands::NodeCaps resource_tools_nodecaps(const Widelands::FCoords& fcoords, + const Widelands::EditorGameBase& egbase, + Widelands::DescriptionIndex resource) { + if (egbase.map().is_resource_valid(egbase.world(), fcoords, resource)) { + return fcoords.field->nodecaps(); + } + return Widelands::NodeCaps::CAPS_NONE; +} + === modified file 'src/editor/tools/set_resources_tool.h' --- src/editor/tools/set_resources_tool.h 2017-01-25 18:55:59 +0000 +++ src/editor/tools/set_resources_tool.h 2017-09-13 07:28:46 +0000 @@ -24,6 +24,10 @@ #include "logic/mapregion.h" #include "logic/widelands.h" +Widelands::NodeCaps resource_tools_nodecaps(const Widelands::FCoords& fcoords, + const Widelands::EditorGameBase& egbase, + Widelands::DescriptionIndex resource); + /// Decreases the resources of a node by a value. struct EditorSetResourcesTool : public EditorTool { EditorSetResourcesTool() : EditorTool(*this, *this), cur_res_(0), set_to_(0) { @@ -50,6 +54,11 @@ return g_gr->images().get("images/wui/editor/fsel_editor_set_resources.png"); } + Widelands::NodeCaps nodecaps_for_buildhelp(const Widelands::FCoords& fcoords, + const Widelands::EditorGameBase& egbase) override { + return resource_tools_nodecaps(fcoords, egbase, cur_res_); + } + Widelands::ResourceAmount get_set_to() const { return set_to_; } === modified file 'src/editor/tools/set_starting_pos_tool.cc' --- src/editor/tools/set_starting_pos_tool.cc 2017-08-28 12:58:24 +0000 +++ src/editor/tools/set_starting_pos_tool.cc 2017-09-13 07:28:46 +0000 @@ -28,17 +28,17 @@ // global variable to pass data from callback to class static int32_t current_player_; -/* - * static callback function for overlay calculation - */ -int32_t editor_tool_set_starting_pos_callback(const Widelands::FCoords& c, Widelands::Map& map) { +namespace { + +Widelands::NodeCaps set_starting_pos_tool_nodecaps(const Widelands::FCoords& c, + const Widelands::Map& map) { // Area around already placed players Widelands::PlayerNumber const nr_players = map.get_nrplayers(); for (Widelands::PlayerNumber p = 1, last = current_player_ - 1;; ++p) { for (; p <= last; ++p) { if (Widelands::Coords const sp = map.get_starting_pos(p)) { if (map.calc_distance(sp, c) < MIN_PLACE_AROUND_PLAYERS) { - return 0; + return Widelands::NodeCaps::CAPS_NONE; } } } @@ -52,9 +52,11 @@ if ((caps & Widelands::BUILDCAPS_SIZEMASK) == Widelands::BUILDCAPS_BIG) return caps; - return 0; + return Widelands::NodeCaps::CAPS_NONE; } +} // namespace + EditorSetStartingPosTool::EditorSetStartingPosTool() : EditorTool(*this, *this, false) { current_player_ = 1; } @@ -78,13 +80,20 @@ } // check if field is valid - if (editor_tool_set_starting_pos_callback(map->get_fcoords(center.node), *map)) { + if (set_starting_pos_tool_nodecaps(map->get_fcoords(center.node), *map) != + Widelands::NodeCaps::CAPS_NONE) { map->set_starting_pos(current_player_, center.node); } } return 1; } +Widelands::NodeCaps +EditorSetStartingPosTool::nodecaps_for_buildhelp(const Widelands::FCoords& fcoords, + const Widelands::EditorGameBase& egbase) { + return set_starting_pos_tool_nodecaps(fcoords, egbase.map()); +} + Widelands::PlayerNumber EditorSetStartingPosTool::get_current_player() const { return current_player_; } === modified file 'src/editor/tools/set_starting_pos_tool.h' --- src/editor/tools/set_starting_pos_tool.h 2017-08-28 12:41:15 +0000 +++ src/editor/tools/set_starting_pos_tool.h 2017-09-13 07:28:46 +0000 @@ -25,7 +25,6 @@ #include "editor/tools/tool.h" #include "graphic/playercolor.h" #include "logic/widelands.h" -#include "wui/field_overlay_manager.h" // How much place should be left around a player position // where no other player can start @@ -49,8 +48,8 @@ bool has_size_one() const override { return true; } + Widelands::NodeCaps nodecaps_for_buildhelp(const Widelands::FCoords& fcoords, + const Widelands::EditorGameBase&) override; }; -int32_t editor_tool_set_starting_pos_callback(const Widelands::FCoords& c, Widelands::Map& map); - #endif // end of include guard: WL_EDITOR_TOOLS_SET_STARTING_POS_TOOL_H === modified file 'src/editor/tools/tool.h' --- src/editor/tools/tool.h 2017-01-25 18:55:59 +0000 +++ src/editor/tools/tool.h 2017-09-13 07:28:46 +0000 @@ -26,6 +26,7 @@ #include "editor/tools/action_args.h" #include "graphic/graphic.h" #include "graphic/image.h" +#include "logic/editor_game_base.h" #include "logic/widelands_geometry.h" class EditorInteractive; @@ -99,6 +100,14 @@ return 0; } // non unduable tools don't need to implement this. virtual const Image* get_sel_impl() const = 0; + + // Gives the tool the chance to modify the nodecaps to change what will be + // displayed as build help. + virtual Widelands::NodeCaps nodecaps_for_buildhelp(const Widelands::FCoords& fcoords, + const Widelands::EditorGameBase&) { + return fcoords.field->nodecaps(); + } + virtual bool operates_on_triangles() const { return false; } === modified file 'src/editor/ui_menus/player_menu.cc' --- src/editor/ui_menus/player_menu.cc 2017-09-01 00:16:04 +0000 +++ src/editor/ui_menus/player_menu.cc 2017-09-13 07:28:46 +0000 @@ -33,7 +33,6 @@ #include "ui_basic/editbox.h" #include "ui_basic/messagebox.h" #include "ui_basic/textarea.h" -#include "wui/field_overlay_manager.h" #define UNDEFINED_TRIBE_NAME "<undefined>" @@ -220,13 +219,6 @@ Widelands::PlayerNumber const nr_players = old_nr_players - 1; assert(1 <= nr_players); - if (const Widelands::Coords sp = map->get_starting_pos(old_nr_players)) { - // Remove starting position marker. - const Image* player_image = - playercolor_image(old_nr_players - 1, "images/players/player_position.png"); - assert(player_image); - menu.mutable_field_overlay_manager()->remove_overlay(sp, player_image); - } // if removed player was selected switch to the next highest player if (old_nr_players == menu.tools()->set_starting_pos.get_current_player()) { set_starting_pos_clicked(nr_players); @@ -275,12 +267,6 @@ // reselect tool, so everything is in a defined state menu.select_tool(menu.tools()->current(), EditorTool::First); - - // Register callback function to make sure that only valid locations are - // selected. - menu.mutable_field_overlay_manager()->register_overlay_callback_function( - boost::bind(&editor_tool_set_starting_pos_callback, _1, boost::ref(*map))); - map->recalc_whole_map(menu.egbase().world()); update(); } === modified file 'src/editor/ui_menus/tool_change_resources_options_menu.cc' --- src/editor/ui_menus/tool_change_resources_options_menu.cc 2017-08-19 22:22:20 +0000 +++ src/editor/ui_menus/tool_change_resources_options_menu.cc 2017-09-13 07:28:46 +0000 @@ -35,7 +35,6 @@ #include "logic/map_objects/world/world.h" #include "logic/widelands.h" #include "logic/widelands_geometry.h" -#include "wui/field_overlay_manager.h" constexpr int kMaxValue = 63; @@ -153,17 +152,6 @@ increase_tool_.set_cur_res(resource_index); increase_tool_.decrease_tool().set_cur_res(resource_index); - Widelands::EditorGameBase& egbase = eia().egbase(); - Widelands::Map* map = egbase.mutable_map(); - eia().mutable_field_overlay_manager()->register_overlay_callback_function( - [resource_index, map, &egbase](const Widelands::FCoords& fc) -> uint32_t { - if (map->is_resource_valid(egbase.world(), fc, resource_index)) { - return fc.field->nodecaps(); - } - return 0; - }); - - map->recalc_whole_map(egbase.world()); select_correct_tool(); update(); } === modified file 'src/editor/ui_menus/tool_menu.cc' --- src/editor/ui_menus/tool_menu.cc 2017-08-19 22:22:20 +0000 +++ src/editor/ui_menus/tool_menu.cc 2017-09-13 07:28:46 +0000 @@ -140,13 +140,6 @@ } parent.select_tool(*current_tool_pointer, EditorTool::First); - if (current_tool_pointer == &parent.tools()->set_port_space) { - // Set correct overlay - Widelands::Map* map = parent.egbase().mutable_map(); - parent.mutable_field_overlay_manager()->register_overlay_callback_function( - boost::bind(&editor_tool_set_port_space_callback, _1, boost::ref(*map))); - map->recalc_whole_map(parent.egbase().world()); - } if (current_registry_pointer) { if (UI::Window* const window = current_registry_pointer->window) { === modified file 'src/graphic/CMakeLists.txt' --- src/graphic/CMakeLists.txt 2017-08-28 08:33:02 +0000 +++ src/graphic/CMakeLists.txt 2017-09-13 07:28:46 +0000 @@ -184,7 +184,6 @@ graphic_surface logic wui - wui_field_overlay_manager wui_mapview_pixelfunctions ) === modified file 'src/graphic/game_renderer.cc' --- src/graphic/game_renderer.cc 2017-08-29 10:48:24 +0000 +++ src/graphic/game_renderer.cc 2017-09-13 07:28:46 +0000 @@ -29,7 +29,6 @@ #include "logic/editor_game_base.h" #include "logic/map_objects/world/world.h" #include "logic/player.h" -#include "wui/field_overlay_manager.h" #include "wui/interactive_base.h" #include "wui/mapviewpixelconstants.h" #include "wui/mapviewpixelfunctions.h" === modified file 'src/logic/map.cc' --- src/logic/map.cc 2017-09-03 09:36:11 +0000 +++ src/logic/map.cc 2017-09-13 07:28:46 +0000 @@ -1770,7 +1770,7 @@ bool Map::is_resource_valid(const Widelands::World& world, const Widelands::FCoords& c, - DescriptionIndex curres) { + DescriptionIndex curres) const { if (curres == Widelands::kNoResource) return true; === modified file 'src/logic/map.h' --- src/logic/map.h 2017-08-31 10:55:45 +0000 +++ src/logic/map.h 2017-09-13 07:28:46 +0000 @@ -428,7 +428,7 @@ */ bool is_resource_valid(const Widelands::World& world, const Widelands::FCoords& c, - DescriptionIndex curres); + DescriptionIndex curres) const; // The objectives that are defined in this map if it is a scenario. const Objectives& objectives() const { === modified file 'src/wui/CMakeLists.txt' --- src/wui/CMakeLists.txt 2017-09-04 05:08:56 +0000 +++ src/wui/CMakeLists.txt 2017-09-13 07:28:46 +0000 @@ -24,17 +24,6 @@ wui ) -wl_library(wui_field_overlay_manager - SRCS - field_overlay_manager.cc - field_overlay_manager.h - DEPENDS - base_geometry - graphic - logic - logic_widelands_geometry -) - wl_library(wui_economy_options SRCS economy_options_window.cc @@ -265,7 +254,6 @@ ui_basic wui_chat_ui wui_economy_options - wui_field_overlay_manager wui_mapview wui_mapview_pixelfunctions wui_quicknavigation === modified file 'src/wui/buildingwindow.h' --- src/wui/buildingwindow.h 2017-09-01 09:18:10 +0000 +++ src/wui/buildingwindow.h 2017-09-13 07:28:46 +0000 @@ -28,7 +28,6 @@ #include "notifications/notifications.h" #include "ui_basic/button.h" #include "ui_basic/unique_window.h" -#include "wui/field_overlay_manager.h" #include "wui/interactive_gamebase.h" #include "wui/waresdisplay.h" === removed file 'src/wui/field_overlay_manager.cc' --- src/wui/field_overlay_manager.cc 2017-08-16 05:10:15 +0000 +++ src/wui/field_overlay_manager.cc 1970-01-01 00:00:00 +0000 @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2002-2017 by the Widelands Development Team - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include "wui/field_overlay_manager.h" - -#include <algorithm> - -#include <stdint.h> - -#include "graphic/graphic.h" -#include "logic/field.h" - -FieldOverlayManager::FieldOverlayManager() : buildhelp_(false), current_overlay_id_(0) { - OverlayInfo* buildhelp_info = buildhelp_infos_; - const char* filenames[] = {"images/wui/overlays/set_flag.png", "images/wui/overlays/small.png", - "images/wui/overlays/medium.png", "images/wui/overlays/big.png", - "images/wui/overlays/mine.png", "images/wui/overlays/port.png"}; - const char* const* filename = filenames; - - // Special case for flag, which has a different formula for hotspot_y. - buildhelp_info->pic = g_gr->images().get(*filename); - buildhelp_info->hotspot = - Vector2i(buildhelp_info->pic->width() / 2, buildhelp_info->pic->height() - 1); - - const OverlayInfo* const buildhelp_infos_end = buildhelp_info + Widelands::Field::Buildhelp_None; - for (;;) { // The other buildhelp overlays. - ++buildhelp_info; - ++filename; - if (buildhelp_info == buildhelp_infos_end) - break; - buildhelp_info->pic = g_gr->images().get(*filename); - buildhelp_info->hotspot = - Vector2i(buildhelp_info->pic->width() / 2, buildhelp_info->pic->height() / 2); - } -} - -bool FieldOverlayManager::buildhelp() const { - return buildhelp_; -} - -void FieldOverlayManager::show_buildhelp(const bool value) { - buildhelp_ = value; -} - -int FieldOverlayManager::get_buildhelp_overlay(const Widelands::FCoords& fc) const { - Widelands::NodeCaps const caps = - callback_ ? static_cast<Widelands::NodeCaps>(callback_(fc)) : fc.field->nodecaps(); - - if (caps & Widelands::BUILDCAPS_MINE) { - return Widelands::Field::Buildhelp_Mine; - } - if ((caps & Widelands::BUILDCAPS_SIZEMASK) == Widelands::BUILDCAPS_BIG) { - if (caps & Widelands::BUILDCAPS_PORT) { - return Widelands::Field::Buildhelp_Port; - } - return Widelands::Field::Buildhelp_Big; - } - if ((caps & Widelands::BUILDCAPS_SIZEMASK) == Widelands::BUILDCAPS_MEDIUM) { - return Widelands::Field::Buildhelp_Medium; - } - if ((caps & Widelands::BUILDCAPS_SIZEMASK) == Widelands::BUILDCAPS_SMALL) { - return Widelands::Field::Buildhelp_Small; - } - if (caps & Widelands::BUILDCAPS_FLAG) { - return Widelands::Field::Buildhelp_Flag; - } - return Widelands::Field::Buildhelp_None; -} - -void FieldOverlayManager::register_overlay(const Widelands::Coords& c, - const Image* pic, - const OverlayLevel& level, - Vector2i hotspot, - OverlayId const overlay_id) { - if (hotspot == Vector2i::invalid()) { - hotspot = Vector2i(pic->width() / 2, pic->height() / 2); - } - - for (auto it = overlays_.find(c); it != overlays_.end() && it->first == c; ++it) - if (it->second.pic == pic && it->second.hotspot == hotspot && it->second.level == level) { - it->second.overlay_ids.insert(overlay_id); - return; - } - - overlays_.insert(std::pair<Widelands::Coords const, RegisteredOverlays>( - c, RegisteredOverlays(overlay_id, pic, hotspot, level))); - - // Now manually sort, so that they are ordered - // * first by c (done by std::multimap) - // * second by levels (done manually here) - - // there is at least one registered - auto it = overlays_.lower_bound(c); - do { - auto jt = it; - ++jt; - if (jt == overlays_.end()) - break; - if (jt->first == it->first) { - // There are several overlays registered for this location. - if (jt->second.level < it->second.level) { - std::swap(it->second, jt->second); - it = overlays_.lower_bound(c); - } else - ++it; - } else - break; // it is the last element, break this loop. - } while (it->first == c); -} - -/** - * remove one (or many) overlays from a node or triangle - * - * @param pic The overlay to remove. If null, all overlays are removed. - */ -void FieldOverlayManager::remove_overlay(const Widelands::Coords& c, const Image* pic) { - if (overlays_.count(c) == 0) { - return; - } - auto it = overlays_.lower_bound(c); - do { - if (!pic || it->second.pic == pic) { - overlays_.erase(it); - it = overlays_.lower_bound(c); - } else { - ++it; - } - } while (it != overlays_.end() && it->first == c); -} - -void FieldOverlayManager::remove_overlay(const OverlayId overlay_id) { - for (auto it = overlays_.begin(); it != overlays_.end();) { - it->second.overlay_ids.erase(overlay_id); - if (it->second.overlay_ids.empty()) { - overlays_.erase(it++); // This is necessary! - } else { - ++it; - } - } -} - -void FieldOverlayManager::remove_all_overlays() { - overlays_.clear(); -} - -void FieldOverlayManager::register_overlay_callback_function(CallbackFn function) { - callback_ = function; -} - -FieldOverlayManager::OverlayId FieldOverlayManager::next_overlay_id() { - ++current_overlay_id_; - return current_overlay_id_; -} - -bool FieldOverlayManager::is_enabled(const OverlayLevel& level) const { - return disabled_layers_.count(level) == 0; -} - -void FieldOverlayManager::set_enabled(const OverlayLevel& level, const bool enabled) { - if (enabled) { - disabled_layers_.erase(level); - } else { - disabled_layers_.insert(level); - } -} === removed file 'src/wui/field_overlay_manager.h' --- src/wui/field_overlay_manager.h 2017-09-01 13:26:55 +0000 +++ src/wui/field_overlay_manager.h 1970-01-01 00:00:00 +0000 @@ -1,172 +0,0 @@ -/* - * Copyright (C) 2002-2017 by the Widelands Development Team - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#ifndef WL_WUI_FIELD_OVERLAY_MANAGER_H -#define WL_WUI_FIELD_OVERLAY_MANAGER_H - -#include <functional> -#include <map> -#include <set> -#include <vector> - -#include "base/vector.h" -#include "logic/field.h" -#include "logic/widelands_geometry.h" - -class Image; - -/* - * The Overlay Manager is responsible for the map overlays. He - * manages overlays in the following way: - * - When someone registered one (or more) special overlays - * for a field he draws them accordingly - * - * about the level variable: - * the level describe when the overlay should be drawn, lower means drawn - * earlier. - * - * about overlay_id: - * the overlay_id can be given to the register function, whenever - * the job is finished or canceled, a simple remove_overlay - * with the overlay_id can be called and all overlays created in the - * job are removed. - */ - -// Levels for the overlay registers. This defines in which order they will be -// drawn. Buildhelp is special and has the value 5, i.e. every smaller will be -// drawn below the buildhelp, everything higher above. -// TODO(sirver): no longer used. remove. -enum class OverlayLevel {}; - -struct FieldOverlayManager { - /// A unique id identifying a registered overlay. - using OverlayId = uint32_t; - - /// A function returning Field::nodecaps() for the build overlay. This can be - /// registered to hide or change some of the nodecaps during rendering. - using CallbackFn = std::function<int32_t(const Widelands::FCoords& coordinates)>; - - FieldOverlayManager(); - - /// Returns true if the buildhelp is currently shown. - bool buildhelp() const; - - /// Defines if the buildhelp should be shown. - void show_buildhelp(bool t); - - /// Register callback function. - void register_overlay_callback_function(CallbackFn function); - - /// Like 'buildhelp', but for an individual layer. - bool is_enabled(const OverlayLevel& level) const; - void set_enabled(const OverlayLevel& level, bool value); - - /// Get a unique, unused id that can be passed to register_overlay. - OverlayId next_overlay_id(); - - /// Register an overlay at a location (node or triangle). hotspot is the point - /// of the picture that will be exactly over the location. If hotspot is - /// Vector2i::invalid(), the center of the picture will be used as hotspot. - void register_overlay(const Widelands::Coords& coords, - const Image* pic, - const OverlayLevel& overlay_level, - Vector2i hotspot = Vector2i::invalid(), - OverlayId overlay_id = 0); - - /// removes all overlays when pic is nullptr. - void remove_overlay(const Widelands::Coords& coords, const Image* pic); - - /// remove all overlays with this overlay_id - void remove_overlay(OverlayId overlay_id); - - /// Removes all overlays. - // TODO(sirver): It would be preferable to just delete and recreate the object. - void remove_all_overlays(); - - /// Calls 'func' for each of the the currently registered and enabled - /// overlays and the buildhelp. - template <typename T> void foreach_overlay(const Widelands::FCoords& c, T func) const { - auto it = overlays_.lower_bound(c); - while (it != overlays_.end() && it->first == c && - static_cast<int>(it->second.level) <= kLevelForBuildHelp) { - if (is_enabled(it->second.level)) { - func(it->second.pic, it->second.hotspot); - } - ++it; - } - - if (buildhelp_) { - int buildhelp_overlay_index = get_buildhelp_overlay(c); - if (buildhelp_overlay_index < Widelands::Field::Buildhelp_None) { - auto& overlay_info = buildhelp_infos_[buildhelp_overlay_index]; - func(overlay_info.pic, overlay_info.hotspot); - } - } - - while (it != overlays_.end() && it->first == c) { - if (is_enabled(it->second.level)) { - func(it->second.pic, it->second.hotspot); - } - ++it; - } - } - -private: - static constexpr int kLevelForBuildHelp = 5; - - /// A overlay as drawn onto the screen. - struct OverlayInfo { - const Image* pic = nullptr; - Vector2i hotspot = Vector2i::zero(); - }; - - struct RegisteredOverlays { - RegisteredOverlays(const OverlayId init_overlay_id, - const Image* init_pic, - const Vector2i init_hotspot, - const OverlayLevel& init_level) - : pic(init_pic), hotspot(init_hotspot), level(init_level) { - overlay_ids.insert(init_overlay_id); - } - std::set<OverlayId> overlay_ids; - const Image* pic; - Vector2i hotspot = Vector2i::zero(); - OverlayLevel level; - }; - - // Returns the index into buildhelp_infos_ for the correct fieldcaps for - // 'fc' according to the current 'callback_'. - int get_buildhelp_overlay(const Widelands::FCoords& fc) const; - - std::multimap<const Widelands::Coords, RegisteredOverlays> overlays_; - - OverlayInfo buildhelp_infos_[Widelands::Field::Buildhelp_None]; - bool buildhelp_; - // We are inverting the logic here, since new layers are by default enabled - // and we only support to toggle some of them off. Otherwise whenever a new - // layer is added in 'OverlayLevel' we would also need to add it to the - // 'enabled_layers_' set on construction. - std::set<OverlayLevel> disabled_layers_; - - // this callback is used to define where overlays are drawn. - CallbackFn callback_; - OverlayId current_overlay_id_; -}; - -#endif // end of include guard: WL_WUI_FIELD_OVERLAY_MANAGER_H === modified file 'src/wui/fieldaction.cc' --- src/wui/fieldaction.cc 2017-09-03 13:03:56 +0000 +++ src/wui/fieldaction.cc 2017-09-13 07:28:46 +0000 @@ -41,7 +41,6 @@ #include "wui/actionconfirm.h" #include "wui/attack_box.h" #include "wui/economy_options_window.h" -#include "wui/field_overlay_manager.h" #include "wui/game_debug_ui.h" #include "wui/interactive_player.h" #include "wui/waresdisplay.h" === modified file 'src/wui/interactive_base.cc' --- src/wui/interactive_base.cc 2017-09-11 19:18:35 +0000 +++ src/wui/interactive_base.cc 2017-09-13 07:28:46 +0000 @@ -46,7 +46,6 @@ #include "logic/widelands_geometry.h" #include "profile/profile.h" #include "scripting/lua_interface.h" -#include "wui/field_overlay_manager.h" #include "wui/game_chat_menu.h" #include "wui/game_debug_ui.h" #include "wui/interactive_player.h" @@ -56,6 +55,8 @@ #include "wui/minimap.h" #include "wui/unique_window_handler.h" +namespace { + using Widelands::Area; using Widelands::CoordPath; using Widelands::Coords; @@ -65,15 +66,39 @@ using Widelands::MapObject; using Widelands::TCoords; +int caps_to_buildhelp(const Widelands::NodeCaps caps) { + if (caps & Widelands::BUILDCAPS_MINE) { + return Widelands::Field::Buildhelp_Mine; + } + if ((caps & Widelands::BUILDCAPS_SIZEMASK) == Widelands::BUILDCAPS_BIG) { + if (caps & Widelands::BUILDCAPS_PORT) { + return Widelands::Field::Buildhelp_Port; + } + return Widelands::Field::Buildhelp_Big; + } + if ((caps & Widelands::BUILDCAPS_SIZEMASK) == Widelands::BUILDCAPS_MEDIUM) { + return Widelands::Field::Buildhelp_Medium; + } + if ((caps & Widelands::BUILDCAPS_SIZEMASK) == Widelands::BUILDCAPS_SMALL) { + return Widelands::Field::Buildhelp_Small; + } + if (caps & Widelands::BUILDCAPS_FLAG) { + return Widelands::Field::Buildhelp_Flag; + } + return Widelands::Field::Buildhelp_None; +} + +} // namespace + InteractiveBase::InteractiveBase(EditorGameBase& the_egbase, Section& global_s) : UI::Panel(nullptr, 0, 0, g_gr->get_xres(), g_gr->get_yres()), show_workarea_preview_(global_s.get_bool("workareapreview", true)), + buildhelp_(false), map_view_(this, the_egbase.map(), 0, 0, g_gr->get_xres(), g_gr->get_yres()), // Initialize chatoveraly before the toolbar so it is below chat_overlay_(new ChatOverlay(this, 10, 25, get_w() / 2, get_h() - 25)), toolbar_(this, 0, 0, UI::Box::Horizontal), quick_navigation_(&map_view_), - field_overlay_manager_(new FieldOverlayManager()), egbase_(the_egbase), #ifndef NDEBUG // not in releases display_flags_(dfDebug), @@ -94,6 +119,33 @@ g_gr->images().get("images/wui/overlays/workarea2.png"), g_gr->images().get("images/wui/overlays/workarea1.png")} { + // Load the buildhelp icons. + { + BuildhelpOverlay* buildhelp_overlay = buildhelp_overlays_; + const char* filenames[] = { + "images/wui/overlays/set_flag.png", "images/wui/overlays/small.png", + "images/wui/overlays/medium.png", "images/wui/overlays/big.png", + "images/wui/overlays/mine.png", "images/wui/overlays/port.png"}; + const char* const* filename = filenames; + + // Special case for flag, which has a different formula for hotspot_y. + buildhelp_overlay->pic = g_gr->images().get(*filename); + buildhelp_overlay->hotspot = + Vector2i(buildhelp_overlay->pic->width() / 2, buildhelp_overlay->pic->height() - 1); + + const BuildhelpOverlay* const buildhelp_overlays_end = + buildhelp_overlay + Widelands::Field::Buildhelp_None; + for (;;) { // The other buildhelp overlays. + ++buildhelp_overlay; + ++filename; + if (buildhelp_overlay == buildhelp_overlays_end) + break; + buildhelp_overlay->pic = g_gr->images().get(*filename); + buildhelp_overlay->hotspot = + Vector2i(buildhelp_overlay->pic->width() / 2, buildhelp_overlay->pic->height() / 2); + } + } + resize_chat_overlay(); graphic_resolution_changed_subscriber_ = Notifications::subscribe<GraphicResolutionChanged>( @@ -144,6 +196,15 @@ } } +const InteractiveBase::BuildhelpOverlay* +InteractiveBase::get_buildhelp_overlay(const Widelands::NodeCaps caps) const { + const int buildhelp_overlay_index = caps_to_buildhelp(caps); + if (buildhelp_overlay_index < Widelands::Field::Buildhelp_None) { + return &buildhelp_overlays_[buildhelp_overlay_index]; + } + return nullptr; +} + UniqueWindowHandler& InteractiveBase::unique_windows() { return *unique_window_handler_; } @@ -202,16 +263,16 @@ } bool InteractiveBase::buildhelp() const { - return field_overlay_manager_->buildhelp(); + return buildhelp_; } void InteractiveBase::show_buildhelp(bool t) { - field_overlay_manager_->show_buildhelp(t); + buildhelp_ = t; on_buildhelp_changed(t); } void InteractiveBase::toggle_buildhelp() { - show_buildhelp(!field_overlay_manager_->buildhelp()); + show_buildhelp(!buildhelp()); } UI::Button* InteractiveBase::add_toolbar_button(const std::string& image_basename, === modified file 'src/wui/interactive_base.h' --- src/wui/interactive_base.h 2017-09-11 14:11:56 +0000 +++ src/wui/interactive_base.h 2017-09-13 07:28:46 +0000 @@ -37,7 +37,6 @@ #include "ui_basic/unique_window.h" #include "wui/chatoverlay.h" #include "wui/debugconsole.h" -#include "wui/field_overlay_manager.h" #include "wui/mapview.h" #include "wui/minimap.h" #include "wui/quicknavigation.h" @@ -72,6 +71,12 @@ std::map<Widelands::Coords, const Image*> steepness_indicators; }; + /// A build help overlay, i.e. small, big, mine, port ... + struct BuildhelpOverlay { + const Image* pic = nullptr; + Vector2i hotspot = Vector2i::zero(); + }; + // Manages all UniqueWindows. UniqueWindowHandler& unique_windows(); @@ -155,13 +160,6 @@ log_message(std::string(message)); } - const FieldOverlayManager& field_overlay_manager() const { - return *field_overlay_manager_; - } - FieldOverlayManager* mutable_field_overlay_manager() { - return field_overlay_manager_.get(); - } - void toggle_minimap(); void toggle_buildhelp(); @@ -171,10 +169,6 @@ // Sets the landmark for the keyboard 'key' to 'point' void set_landmark(size_t key, const MapView::View& view); - const RoadBuildingOverlays& road_building_overlays() const { - return road_building_overlays_; - } - MapView* map_view() { return &map_view_; } @@ -228,6 +222,14 @@ std::map<Widelands::Coords, const Image*> get_work_area_overlays(const Widelands::Map& map) const; + // Returns the 'BuildhelpOverlay' for 'caps' or nullptr if there is no help + // to be displayed on this field. + const BuildhelpOverlay* get_buildhelp_overlay(Widelands::NodeCaps caps) const; + + const RoadBuildingOverlays& road_building_overlays() const { + return road_building_overlays_; + } + private: int32_t stereo_position(Widelands::Coords position_map); void resize_chat_overlay(); @@ -244,18 +246,17 @@ Widelands::Coords(0, 0), Widelands::TCoords<>(Widelands::Coords(0, 0), Widelands::TriangleIndex::D)}, const uint32_t Radius = 0, - const Image* Pic = nullptr, - const FieldOverlayManager::OverlayId Jobid = 0) - : freeze(Freeze), triangles(Triangles), pos(Pos), radius(Radius), pic(Pic), jobid(Jobid) { + const Image* Pic = nullptr) + : freeze(Freeze), triangles(Triangles), pos(Pos), radius(Radius), pic(Pic) { } bool freeze; // don't change sel, even if mouse moves bool triangles; // otherwise nodes Widelands::NodeAndTriangle<> pos; uint32_t radius; const Image* pic; - FieldOverlayManager::OverlayId jobid; } sel_; + bool buildhelp_; MapView map_view_; ChatOverlay* chat_overlay_; @@ -270,8 +271,6 @@ MiniMap::Registry minimap_registry_; QuickNavigation quick_navigation_; - std::unique_ptr<FieldOverlayManager> field_overlay_manager_; - // The currently enabled work area previews. They are keyed by the // coordinate that the building that shows the work area is positioned. std::map<Widelands::Coords, const WorkareaInfo*> work_area_previews_; @@ -293,6 +292,7 @@ UI::UniqueWindow::Registry debugconsole_; std::unique_ptr<UniqueWindowHandler> unique_window_handler_; std::vector<const Image*> workarea_pics_; + BuildhelpOverlay buildhelp_overlays_[Widelands::Field::Buildhelp_None]; }; #endif // end of include guard: WL_WUI_INTERACTIVE_BASE_H === modified file 'src/wui/interactive_gamebase.cc' --- src/wui/interactive_gamebase.cc 2017-08-20 17:45:42 +0000 +++ src/wui/interactive_gamebase.cc 2017-09-13 07:28:46 +0000 @@ -137,13 +137,9 @@ * during single/multiplayer/scenario). */ void InteractiveGameBase::postload() { - auto* overlay_manager = mutable_field_overlay_manager(); show_buildhelp(false); on_buildhelp_changed(buildhelp()); - overlay_manager->register_overlay_callback_function( - boost::bind(&InteractiveGameBase::calculate_buildcaps, this, _1)); - // Recalc whole map for changed owner stuff egbase().mutable_map()->recalc_whole_map(egbase().world()); === modified file 'src/wui/interactive_gamebase.h' --- src/wui/interactive_gamebase.h 2017-08-28 07:39:59 +0000 +++ src/wui/interactive_gamebase.h 2017-09-13 07:28:46 +0000 @@ -97,7 +97,6 @@ protected: void draw_overlay(RenderTarget&) override; - virtual int32_t calculate_buildcaps(const Widelands::FCoords& c) = 0; GameMainMenuWindows main_windows_; ChatProvider* chat_provider_; === modified file 'src/wui/interactive_player.cc' --- src/wui/interactive_player.cc 2017-09-11 08:22:25 +0000 +++ src/wui/interactive_player.cc 2017-09-13 07:28:46 +0000 @@ -389,9 +389,13 @@ } } - // TODO(sirver): Do not use the field_overlay_manager, instead draw the - // overlays we are interested in here directly. - field_overlay_manager().foreach_overlay(f->fcoords, blit_overlay); + // Draw build help. + if (buildhelp()) { + const auto* overlay = get_buildhelp_overlay(plr.get_buildcaps(f->fcoords)); + if (overlay != nullptr) { + blit_overlay(overlay->pic, overlay->hotspot); + } + } // Blit the selection marker. if (f->fcoords == get_sel_pos().node) { @@ -425,11 +429,6 @@ return player_number_; } -int32_t InteractivePlayer::calculate_buildcaps(const Widelands::FCoords& c) { - assert(get_player()); - return get_player()->get_buildcaps(c); -} - /// Player has clicked on the given node; bring up the context menu. void InteractivePlayer::node_action(const Widelands::NodeAndTriangle<>& node_and_triangle) { const Map& map = egbase().map(); === modified file 'src/wui/interactive_player.h' --- src/wui/interactive_player.h 2017-08-28 07:39:59 +0000 +++ src/wui/interactive_player.h 2017-09-13 07:28:46 +0000 @@ -78,7 +78,6 @@ } void popup_message(Widelands::MessageId, const Widelands::Message&); - int32_t calculate_buildcaps(const Widelands::FCoords& c) override; private: void cmdSwitchPlayer(const std::vector<std::string>& args); === modified file 'src/wui/interactive_spectator.cc' --- src/wui/interactive_spectator.cc 2017-09-01 13:26:55 +0000 +++ src/wui/interactive_spectator.cc 2017-09-13 07:28:46 +0000 @@ -117,27 +117,27 @@ assert(get_sel_radius() == 0); assert(!get_sel_triangles()); - const auto& gbase = egbase(); - auto* fields_to_draw = given_map_view->draw_terrain(gbase, dst); + const Widelands::Game& the_game = game(); + const Widelands::Map& map = the_game.map(); + auto* fields_to_draw = given_map_view->draw_terrain(the_game, dst); const float scale = 1.f / given_map_view->view().zoom; - const uint32_t gametime = gbase.get_gametime(); + const uint32_t gametime = the_game.get_gametime(); const auto text_to_draw = get_text_to_draw(); - const std::map<Widelands::Coords, const Image*> work_area_overlays = - get_work_area_overlays(gbase.map()); + const std::map<Widelands::Coords, const Image*> work_area_overlays = get_work_area_overlays(map); for (size_t idx = 0; idx < fields_to_draw->size(); ++idx) { const FieldsToDraw::Field& field = fields_to_draw->at(idx); draw_border_markers(field, scale, *fields_to_draw, dst); Widelands::BaseImmovable* const imm = field.fcoords.field->get_immovable(); - if (imm != nullptr && imm->get_positions(gbase).front() == field.fcoords) { + if (imm != nullptr && imm->get_positions(the_game).front() == field.fcoords) { imm->draw(gametime, text_to_draw, field.rendertarget_pixel, scale, dst); } for (Widelands::Bob* bob = field.fcoords.field->get_first_bob(); bob; bob = bob->get_next_bob()) { - bob->draw(gbase, text_to_draw, field.rendertarget_pixel, scale, dst); + bob->draw(the_game, text_to_draw, field.rendertarget_pixel, scale, dst); } const auto blit_overlay = [dst, &field, scale](const Image* pic, const Vector2i& hotspot) { @@ -147,15 +147,29 @@ BlendMode::UseAlpha); }; + // Draw work area previews. const auto it = work_area_overlays.find(field.fcoords); if (it != work_area_overlays.end()) { const Image* pic = it->second; blit_overlay(pic, Vector2i(pic->width() / 2, pic->height() / 2)); } - // TODO(sirver): Do not use the field_overlay_manager, instead draw the - // overlays we are interested in here directly. - field_overlay_manager().foreach_overlay(field.fcoords, blit_overlay); + // Draw build help. + if (buildhelp()) { + auto caps = Widelands::NodeCaps::CAPS_NONE; + const Widelands::PlayerNumber nr_players = map.get_nrplayers(); + iterate_players_existing(p, nr_players, the_game, player) { + const Widelands::NodeCaps nc = player->get_buildcaps(field.fcoords); + if (nc > Widelands::NodeCaps::CAPS_NONE) { + caps = nc; + break; + } + } + const auto* overlay = get_buildhelp_overlay(caps); + if (overlay != nullptr) { + blit_overlay(overlay->pic, overlay->hotspot); + } + } // Blit the selection marker. if (field.fcoords == get_sel_pos().node) { @@ -175,17 +189,6 @@ return nullptr; } -int32_t InteractiveSpectator::calculate_buildcaps(const Widelands::FCoords& c) { - const Widelands::PlayerNumber nr_players = game().map().get_nrplayers(); - iterate_players_existing(p, nr_players, game(), player) { - const Widelands::NodeCaps nc = player->get_buildcaps(c); - if (nc > Widelands::NodeCaps::CAPS_NONE) { - return nc; - } - } - return Widelands::NodeCaps::CAPS_NONE; -} - // Toolbar button callback functions. void InteractiveSpectator::exit_btn() { if (is_multiplayer()) { === modified file 'src/wui/interactive_spectator.h' --- src/wui/interactive_spectator.h 2017-08-28 07:39:59 +0000 +++ src/wui/interactive_spectator.h 2017-09-13 07:28:46 +0000 @@ -48,7 +48,6 @@ private: void exit_btn(); - int32_t calculate_buildcaps(const Widelands::FCoords& c) override; bool can_see(Widelands::PlayerNumber) const override; bool can_act(Widelands::PlayerNumber) const override; Widelands::PlayerNumber player_number() const override;
_______________________________________________ Mailing list: https://launchpad.net/~widelands-dev Post to : widelands-dev@lists.launchpad.net Unsubscribe : https://launchpad.net/~widelands-dev More help : https://help.launchpad.net/ListHelp