diff --git a/gen_commands.py b/gen_commands.py index 03dafe6d53..7f38420cf8 100644 --- a/gen_commands.py +++ b/gen_commands.py @@ -24,6 +24,17 @@ FILES = [ 'script/script_cmd.h', ] +BASE_CLASS = { + 'BuildDock': 'StationBuildCommand', + 'BuildRailStation': 'StationBuildCommand', + 'BuildAirport': 'StationBuildCommand', + 'BuildRoadStop': 'StationBuildCommand', +} + +BASE_FIELDS = { + 'StationBuildCommand': ['station_to_join', 'adjacent'], +} + BASE_DIR = Path(__file__).parent OUTPUT = BASE_DIR / 'src/citymania/generated/cm_gen_commands' GLOBAL_TYPES = set(('GoalType', 'GoalTypeID', 'GoalID')) @@ -245,13 +256,20 @@ def run(): ) for cmd in commands: name = cmd['name'] - args_list = ', '.join(f'{at} {an}' for at, an in cmd['args']) - args_init = ', '.join(f'{an}{{{an}}}' for _, an in cmd['args']) + base_class = BASE_CLASS.get(name, 'Command') + base_fields = BASE_FIELDS.get(base_class, []) f.write( - f'class {name}: public Command {{\n' + f'class {name}: public {base_class} {{\n' f'public:\n' ) + args_list = ', '.join(f'{at} {an}' for at, an in cmd['args']) + args_init = ', '.join(f'{an}{{{an}}}' for _, an in cmd['args'] if an not in base_fields) + if base_fields: + base_joined = ', '.join(base_fields) + args_init = f'{base_class}{{{base_joined}}}, ' + args_init for at, an in cmd['args']: + if an in base_fields: + continue f.write(f' {at} {an};\n') f.write(f'\n') if args_init: diff --git a/src/airport_gui.cpp b/src/airport_gui.cpp index 26255e552c..e385e323f6 100644 --- a/src/airport_gui.cpp +++ b/src/airport_gui.cpp @@ -37,6 +37,7 @@ #include "widgets/airport_widget.h" #include "citymania/cm_hotkeys.hpp" +#include "citymania/cm_highlight.hpp" #include "citymania/cm_station_gui.hpp" #include "safeguards.h" @@ -64,11 +65,7 @@ void CcBuildAirport(Commands, const CommandCost &result, TileIndex tile) */ static void PlaceAirport(TileIndex tile) { - if (citymania::UseImprovedStationJoin()) { - citymania::PlaceAirport(tile); - return; - } - + NOT_REACHED(); // CityMania uses tools if (_selected_airport_index == -1) return; byte airport_type = AirportClass::Get(_selected_airport_class)->GetSpec(_selected_airport_index)->GetIndex(); @@ -130,7 +127,8 @@ struct BuildAirToolbarWindow : Window { { switch (widget) { case WID_AT_AIRPORT: - if (HandlePlacePushButton(this, WID_AT_AIRPORT, SPR_CURSOR_AIRPORT, HT_RECT, CM_DDSP_BUILD_AIRPORT)) { + // if (HandlePlacePushButton(this, WID_AT_AIRPORT, SPR_CURSOR_AIRPORT, HT_RECT, CM_DDSP_BUILD_AIRPORT)) { + if (citymania::HandlePlacePushButton(this, WID_AT_AIRPORT, std::make_unique())) { ShowBuildAirportPicker(this); this->last_user_action = widget; } diff --git a/src/citymania/cm_command_type.hpp b/src/citymania/cm_command_type.hpp index a1e275a403..52b6a0a393 100644 --- a/src/citymania/cm_command_type.hpp +++ b/src/citymania/cm_command_type.hpp @@ -94,6 +94,15 @@ public: } }; +class StationBuildCommand : public Command { +public: + StationID station_to_join; + bool adjacent; + + StationBuildCommand(StationID station_to_join, bool adjacent) + :station_to_join{station_to_join}, adjacent{adjacent} {} +}; + } // namaespace citymania #endif diff --git a/src/citymania/cm_highlight.cpp b/src/citymania/cm_highlight.cpp index 130306b4ef..5cae50f24e 100644 --- a/src/citymania/cm_highlight.cpp +++ b/src/citymania/cm_highlight.cpp @@ -188,7 +188,6 @@ struct TileZoning { static TileZoning *_mz = nullptr; static IndustryType _industry_forbidden_tiles = INVALID_INDUSTRYTYPE; -extern StationBuildingStatus _station_building_status; extern const Station *_station_to_join; extern const Station *_highlight_station_to_join; extern TileArea _highlight_join_area; @@ -509,6 +508,13 @@ ObjectHighlight ObjectHighlight::make_industry(TileIndex tile, IndustryType ind_ return oh; } +ObjectHighlight ObjectHighlight::make_dock(TileIndex tile, DiagDirection orientation) { + auto oh = ObjectHighlight{ObjectHighlight::Type::DOCK}; + oh.tile = tile; + oh.ddir = orientation; + return oh; +} + /** * Try to add an additional rail-track at the entrance of a depot * @param tile Tile to use for adding the rail-track @@ -561,12 +567,12 @@ void ObjectHighlight::AddStationOverlayData(int w, int h, int rad, StationCovera if (cs == nullptr) continue; if (!has_header) { - this->overlay_data.emplace_back(PAL_NONE, GetString(CM_STR_BUILD_INFO_OVERLAY_STATION_SUPPLIES)); + this->overlay_data.emplace_back(0, PAL_NONE, GetString(CM_STR_BUILD_INFO_OVERLAY_STATION_SUPPLIES)); has_header = true; } SetDParam(0, i); SetDParam(1, production[i] >> 8); - this->overlay_data.emplace_back(cs->GetCargoIcon(), GetString(CM_STR_BUILD_INFO_OVERLAY_STATION_CARGO)); + this->overlay_data.emplace_back(1, cs->GetCargoIcon(), GetString(CM_STR_BUILD_INFO_OVERLAY_STATION_CARGO)); } } @@ -802,6 +808,20 @@ void ObjectHighlight::UpdateTiles() { } break; } + case Type::DOCK: { + this->cost = cmd::BuildDock( + this->tile, + NEW_STATION, + true + ).test(); + auto palette = (cost.Succeeded() ? CM_PALETTE_TINT_WHITE : CM_PALETTE_TINT_RED_DEEP); + this->AddTile(this->tile, ObjectTileHighlight::make_dock_slope(palette, this->ddir)); + if (this->ddir != INVALID_DIAGDIR) { + TileIndex tile_to = TileAddByDiagDir(this->tile, this->ddir); + this->AddTile(tile_to, ObjectTileHighlight::make_dock_flat(palette, DiagDirToAxis(this->ddir))); + } + break; + } default: NOT_REACHED(); } @@ -818,11 +838,12 @@ void ObjectHighlight::UpdateOverlay() { HideBuildInfoOverlay(); return; } + auto err = this->cost.GetErrorMessage(); // auto extra_err = this->cost.GetExtraErrorMessage(); bool no_money = (err == STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY); SetDParam(0, this->cost.GetCost()); - this->overlay_data.emplace_back(PAL_NONE, GetString(no_money ? CM_STR_BUILD_INFO_OVERLAY_COST_NO_MONEY : CM_STR_BUILD_INFO_OVERLAY_COST_OK)); + this->overlay_data.emplace_back(0, PAL_NONE, GetString(no_money ? CM_STR_BUILD_INFO_OVERLAY_COST_NO_MONEY : CM_STR_BUILD_INFO_OVERLAY_COST_OK)); // if (this->cost.Failed() && err != STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY) { // if (err == INVALID_STRING_ID) { // this->overlay_data.emplace_back(PAL_NONE, GetString(CM_STR_BUILD_INFO_OVERLAY_ERROR_UNKNOWN)); @@ -1619,6 +1640,44 @@ TileHighlight ObjectHighlight::GetTileHighlight(const TileInfo *ti) { return th; } +HighlightMap ObjectHighlight::GetHighlightMap(SpriteID palette) { + // TODO remove the need to convert (maybe replace HighlightMap with multimap?) + HighlightMap res; + for (auto &[tile, oth] : this->tiles) { + auto othp = oth; + othp.palette = palette; + res.Add(tile, othp); + } + return res; +} + +std::optional ObjectHighlight::GetArea() { + switch(this->type) { + case Type::NONE: + case Type::BLUEPRINT: + case Type::POLYRAIL: + case Type::INDUSTRY: + return std::nullopt; + case Type::RAIL_DEPOT: + case Type::ROAD_DEPOT: + return TileArea{this->tile, 1, 1}; + case Type::RAIL_STATION: + case Type::ROAD_STOP: + return TileArea{this->tile, this->end_tile}; + case Type::AIRPORT: { + const AirportSpec *as = AirportSpec::Get(this->airport_type); + if (!as->IsAvailable() || this->airport_layout >= as->num_table) return std::nullopt; + return TileArea{this->tile, as->size_x, as->size_y}; + } + case Type::DOCK: { + if (this->ddir == INVALID_DIAGDIR) return std::nullopt; + return TileArea{this->tile, TileAddByDiagDir(this->tile, this->ddir)}; + } + default: + NOT_REACHED(); + } +} + static void DrawObjectTileHighlight(const TileInfo *ti, const ObjectTileHighlight &oth) { switch (oth.type) { case ObjectTileHighlight::Type::RAIL_DEPOT: @@ -1917,7 +1976,7 @@ void CalcCBTownLimitBorder(TileHighlight &th, TileIndex tile, SpriteID border_pa TileHighlight GetTileHighlight(const TileInfo *ti, TileType tile_type) { TileHighlight th; - auto hl = _ap.tiles.GetForTile(ti->tile); + auto hl = _at.tiles.GetForTile(ti->tile); if (hl.has_value()) { for (auto &oth : hl.value().get()) { oth.SetTileHighlight(th, ti); @@ -2046,7 +2105,7 @@ void DrawTileZoning(const TileInfo *ti, const TileHighlight &th, TileType tile_t bool DrawTileSelection(const TileInfo *ti, [[maybe_unused]] const TileHighlightType &tht) { if (ti->tile == INVALID_TILE || IsTileType(ti->tile, MP_VOID)) return false; - auto hl = _ap.tiles.GetForTile(ti->tile); + auto hl = _at.tiles.GetForTile(ti->tile); if (hl.has_value()) { for (auto &oth : hl.value().get()) { DrawObjectTileHighlight(ti, oth); @@ -2376,7 +2435,8 @@ PaletteID GetTreeShadePal(TileIndex tile) { } } -ActivePreview _ap; +ActiveTool _at; + static void ResetVanillaHighlight() { if (_thd.window_class != WC_INVALID) { @@ -2402,34 +2462,81 @@ static void ResetVanillaHighlight() { _thd.make_square_red = false; } -void SetActivePreview(up &&preview) { +void SetActiveTool(up &&tool) { ResetVanillaHighlight(); - ResetActivePreview(); - _ap.preview = std::move(preview); + ResetActiveTool(); + _at.tool = std::move(tool); } -void ResetActivePreview() { - for (auto t : _ap.tiles.GetAllTiles()) { +void ResetActiveTool() { + for (auto t : _at.tiles.GetAllTiles()) { MarkTileDirtyByTile(t); } - _ap.preview = nullptr; - _ap.tiles = {}; + _at.tool = nullptr; + _at.tiles = {}; } -const up &GetActivePreview() { - return _ap.preview; +const up &GetActiveTool() { + return _at.tool; } -void UpdateActivePreview() { - if (_ap.preview == nullptr) return; +void UpdateActiveTool() { Point pt = GetTileBelowCursor(); auto tile = pt.x == -1 ? INVALID_TILE : TileVirtXY(pt.x, pt.y); - _ap.preview->Update(pt, tile); - auto tiles_changed = _ap.tiles.UpdateWithMap(_ap.preview->GetHighlightMap()); + ToolGUIInfo info; + if (citymania::StationBuildTool::active_highlight.has_value()) { + info = GetSelectedStationGUIInfo(); + } else if (_at.tool != nullptr) { + _at.tool->Update(pt, tile); + info = _at.tool->GetGUIInfo(); + } + auto [hlmap, overlay_data, cost] = info; + auto tiles_changed = _at.tiles.UpdateWithMap(hlmap); for (auto t : tiles_changed) MarkTileDirtyByTile(t); + + if (cost.GetExpensesType() != INVALID_EXPENSES || cost.GetErrorMessage() != INVALID_STRING_ID) { + // Add CommandCost info + auto err = cost.GetErrorMessage(); + if (cost.Succeeded()) { + auto money = cost.GetCost(); + if (money != 0) { + SetDParam(0, money); + overlay_data.emplace_back(0, PAL_NONE, GetString(CM_STR_BUILD_INFO_OVERLAY_COST_OK)); + } + } else if (err == STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY) { + SetDParam(0, cost.GetCost()); + overlay_data.emplace_back(0, PAL_NONE, GetString(CM_STR_BUILD_INFO_OVERLAY_COST_NO_MONEY)); + } else { + if (err == INVALID_STRING_ID) { + overlay_data.emplace_back(0, PAL_NONE, GetString(CM_STR_BUILD_INFO_OVERLAY_ERROR_UNKNOWN)); + } else { + SetDParam(0, err); + overlay_data.emplace_back(0, PAL_NONE, GetString(CM_STR_BUILD_INFO_OVERLAY_ERROR)); + } + auto extra_err = cost.GetExtraErrorMessage(); + if (extra_err != INVALID_STRING_ID) { + SetDParam(0, extra_err); + overlay_data.emplace_back(0, PAL_NONE, GetString(CM_STR_BUILD_INFO_OVERLAY_ERROR)); + } + } + } + + /* Update overlay */ + if (overlay_data.size() > 0) { + auto w = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y); + if (w == nullptr) { HideBuildInfoOverlay(); return; } + auto vp = IsPtInWindowViewport(w, _cursor.pos.x, _cursor.pos.y); + if (vp == nullptr) { HideBuildInfoOverlay(); return; } + Point pto = RemapCoords2(TileX(tile) * TILE_SIZE, TileY(tile) * TILE_SIZE); + pto.x = UnScaleByZoom(pto.x - vp->virtual_left, vp->zoom) + vp->left; + pto.y = UnScaleByZoom(pto.y - vp->virtual_top, vp->zoom) + vp->top; + ShowBuildInfoOverlay(pto.x, pto.y, overlay_data); + } else { + HideBuildInfoOverlay(); + } } bool _prev_left_button_down = false; @@ -2444,35 +2551,35 @@ bool HandleMouseMove() { bool released = !_left_button_down && changed && _keep_mouse_click; if (!_left_button_down) _keep_mouse_click = false; - if (_ap.preview == nullptr) return false; + if (_at.tool == nullptr) return false; // Viewport *vp = IsPtInWindowViewport(w, ); auto pt = GetTileBelowCursor(); if (pt.x == -1) return false; auto tile = pt.x == -1 ? INVALID_TILE : TileVirtXY(pt.x, pt.y); - _ap.preview->Update(pt, tile); - _ap.preview->HandleMouseMove(); + _at.tool->Update(pt, tile); + _at.tool->HandleMouseMove(); if (_left_button_down) { - if (changed && _ap.preview->HandleMousePress()) { + if (changed && _at.tool->HandleMousePress()) { _keep_mouse_click = true; } if (_keep_mouse_click) return true; } if (released) { - _ap.preview->HandleMouseRelease(); + _at.tool->HandleMouseRelease(); } return false; } bool HandleMouseClick(Viewport *vp, bool double_click) { - if (_ap.preview == nullptr) return false; + if (_at.tool == nullptr) return false; auto pt = GetTileBelowCursor(); auto tile = pt.x == -1 ? INVALID_TILE : TileVirtXY(pt.x, pt.y); - _ap.preview->Update(pt, tile); - return _ap.preview->HandleMouseClick(vp, pt, tile, double_click); + _at.tool->Update(pt, tile); + return _at.tool->HandleMouseClick(vp, pt, tile, double_click); } -bool HandlePlacePushButton(Window *w, WidgetID widget, up preview) { +bool HandlePlacePushButton(Window *w, WidgetID widget, up tool) { if (w->IsWidgetDisabled(widget)) return false; if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP); @@ -2485,13 +2592,13 @@ bool HandlePlacePushButton(Window *w, WidgetID widget, up preview) { w->LowerWidget(widget); - auto icon = preview->GetCursor(); + auto icon = tool->GetCursor(); if ((icon & ANIMCURSOR_FLAG) != 0) { SetAnimatedMouseCursor(_animcursors[icon & ~ANIMCURSOR_FLAG]); } else { SetMouseCursor(icon, PAL_NONE); } - citymania::SetActivePreview(std::move(preview)); + citymania::SetActiveTool(std::move(tool)); _thd.window_class = w->window_class; _thd.window_number = w->window_number; @@ -2499,5 +2606,4 @@ bool HandlePlacePushButton(Window *w, WidgetID widget, up preview) { } - } // namespace citymania diff --git a/src/citymania/cm_highlight.hpp b/src/citymania/cm_highlight.hpp index cbe31b3447..45e48754f2 100644 --- a/src/citymania/cm_highlight.hpp +++ b/src/citymania/cm_highlight.hpp @@ -57,13 +57,13 @@ PaletteID GetTreeShadePal(TileIndex tile); void RotateAutodetection(); void ResetRotateAutodetection(); -void ResetActivePreview(); -void SetActivePreview(up &&preview); -void UpdateActivePreview(); -const up &GetActivePreview(); +void ResetActiveTool(); +void SetActiveTool(up &&tool); +void UpdateActiveTool(); +const up &GetActiveTool(); -bool HandlePlacePushButton(Window *w, WidgetID widget, up preview); +bool HandlePlacePushButton(Window *w, WidgetID widget, up tool); bool HandleMouseMove(); bool HandleMouseClick(Viewport *vp, bool double_click); diff --git a/src/citymania/cm_highlight_type.hpp b/src/citymania/cm_highlight_type.hpp index 2fc5bb915b..51acf9e55b 100644 --- a/src/citymania/cm_highlight_type.hpp +++ b/src/citymania/cm_highlight_type.hpp @@ -23,6 +23,7 @@ #include #include "cm_command_type.hpp" +#include "cm_overlays.hpp" namespace citymania { @@ -313,6 +314,26 @@ public: std::multimap GetTiles(TileIndex tile); }; + +class HighlightMap { +public: + typedef std::map> MapType; + typedef decltype(std::views::keys(std::declval())) MapTypeKeys; +protected: + MapType map; +public: + const MapType &GetMap() const; + void Add(TileIndex tile, ObjectTileHighlight oth); + bool Contains(TileIndex tile) const; + std::optional>> GetForTile(TileIndex tile) const; + MapTypeKeys GetAllTiles() const; + std::vector UpdateWithMap(const HighlightMap &update); + void AddTileArea(const TileArea &area, SpriteID palette); + void AddTileAreaWithBorder(const TileArea &area, SpriteID palette); + void AddTilesBorder(const std::set &tiles, SpriteID palette); +}; + + class ObjectHighlight { public: enum class Type : byte { @@ -325,6 +346,7 @@ public: BLUEPRINT = 6, POLYRAIL = 7, INDUSTRY = 8, + DOCK = 9, }; Type type = Type::NONE; @@ -352,7 +374,7 @@ protected: bool tiles_updated = false; std::multimap tiles; std::vector sprites = {}; - std::vector> overlay_data = {}; + BuildInfoOverlayData overlay_data = {}; // Point overlay_pos = {0, 0}; void AddTile(TileIndex tile, ObjectTileHighlight &&oh); // void AddSprite(TileIndex tile, ObjectTileHighlight &&oh); @@ -373,8 +395,11 @@ public: TileIndex start_tile2, TileIndex end_tile2, Trackdir trackdir2); static ObjectHighlight make_industry(TileIndex tile, IndustryType ind_type, uint32 ind_layout); + static ObjectHighlight make_dock(TileIndex tile, DiagDirection orientation); TileHighlight GetTileHighlight(const TileInfo *ti); + HighlightMap GetHighlightMap(SpriteID palette); + std::optional GetArea(); void Draw(const TileInfo *ti); void DrawSelectionOverlay(DrawPixelInfo *dpi); void DrawOverlay(DrawPixelInfo *dpi); @@ -384,24 +409,7 @@ public: void MarkDirty(); }; - -class HighlightMap { -public: - typedef std::map> MapType; - typedef decltype(std::views::keys(std::declval())) MapTypeKeys; -protected: - MapType map; -public: - const MapType &GetMap() const; - void Add(TileIndex tile, ObjectTileHighlight oth); - bool Contains(TileIndex tile) const; - std::optional>> GetForTile(TileIndex tile) const; - MapTypeKeys GetAllTiles() const; - std::vector UpdateWithMap(const HighlightMap &update); - void AddTileArea(const TileArea &area, SpriteID palette); - void AddTileAreaWithBorder(const TileArea &area, SpriteID palette); - void AddTilesBorder(const std::set &tiles, SpriteID palette); -}; +typedef std::tuple ToolGUIInfo; class Preview { public: @@ -411,7 +419,38 @@ public: virtual bool HandleMousePress() { return false; }; virtual void HandleMouseRelease() {}; virtual bool HandleMouseClick(Viewport* /* vp */, Point /* pt */, TileIndex /* tile */, bool /* double_click */) { return false; }; - virtual HighlightMap GetHighlightMap() = 0; + virtual std::pair GetGUIInfo() = 0; + virtual CursorID GetCursor() = 0; + virtual void OnStationRemoved(const Station* /* station */) {}; +}; + +class Action { +public: + virtual ~Action() = default; + virtual void Update(Point pt, TileIndex tile) = 0; + virtual std::optional GetArea() const { return std::nullopt; }; + virtual void HandleMouseMove() {}; + virtual bool HandleMousePress() { return false; }; + virtual void HandleMouseRelease() {}; + virtual bool HandleMouseClick(Viewport* vp, Point pt, TileIndex tile, bool double_click) { + (void)vp; (void)pt; (void)tile; (void)double_click; + return false; + }; + virtual ToolGUIInfo GetGUIInfo() = 0; + virtual void OnStationRemoved(const Station *); +}; + +class Tool { +protected: + up action = nullptr; +public: + virtual ~Tool() = default; + virtual void Update(Point pt, TileIndex tile) = 0; + virtual void HandleMouseMove() { if(this->action) this->action->HandleMouseMove(); }; + virtual bool HandleMousePress() { return this->action ? this->action->HandleMousePress() : false; } + virtual void HandleMouseRelease() { if(this->action) this->action->HandleMouseRelease(); }; + virtual bool HandleMouseClick(Viewport* vp, Point pt, TileIndex tile, bool double_click) { return this->action ? this->action->HandleMouseClick(vp, pt, tile, double_click) : false; }; + virtual ToolGUIInfo GetGUIInfo() = 0; virtual CursorID GetCursor() = 0; virtual void OnStationRemoved(const Station* /* station */) {}; }; @@ -423,12 +462,13 @@ public: // DragStop, // }; -struct ActivePreview { - up preview; +struct ActiveTool { + up tool; HighlightMap tiles; }; -extern ActivePreview _ap; +extern ActiveTool _at; + } // namespace citymania diff --git a/src/citymania/cm_overlays.cpp b/src/citymania/cm_overlays.cpp index 2a8e5dbe69..ed8b135e2a 100644 --- a/src/citymania/cm_overlays.cpp +++ b/src/citymania/cm_overlays.cpp @@ -62,7 +62,7 @@ public: } void UpdateSize() { - this->padding = ScaleGUITrad(3); + this->padding = ScaleGUITrad(5); auto dim = this->GetContentDimension(); this->box.width = dim.width + 2 * this->padding; this->box.height = dim.height + 2 * this->padding; @@ -181,8 +181,9 @@ public: Dimension text_dim{0, 0}; Dimension icon_dim{0, 0}; - for (const auto &[icon, s] : data) { + for (const auto &[indent, icon, s] : data) { text_dim = maxdim(text_dim, GetStringBoundingBox(s)); + text_dim.width += WidgetDimensions::scaled.hsep_indent * indent; if (icon != PAL_NONE) icon_dim = maxdim(icon_dim, GetSpriteSize(icon)); } @@ -192,25 +193,26 @@ public: if (icon_dim.width > 0) this->text_ofs_x = icon_dim.width + this->padding; return { - text_dim.width + this->text_ofs_x, + std::min(text_dim.width + this->text_ofs_x, ScaleGUITrad(500)), (uint)data.size() * this->line_height - padding }; } - std::pair GetPositions(Rect rect, int row, SpriteID icon) { + std::pair GetPositions(Rect rect, int row, uint indent, SpriteID icon) { auto icon_height = this->max_height; auto ofs_x = 0; + int ind = WidgetDimensions::scaled.hsep_indent * indent; if (icon != PAL_NONE) { icon_height = GetSpriteSize(icon).height; ofs_x = this->text_ofs_x; } return { { - rect.left, + rect.left + ind, rect.top + row * this->line_height + (this->max_height - icon_height) / 2, }, { - rect.left + ofs_x, + rect.left + ofs_x + ind, rect.top + row * this->line_height + this->text_ofs_y, rect.right, rect.bottom @@ -227,6 +229,11 @@ public: ~BuildInfoOverlay() override {} void Show(int x, int y, BuildInfoOverlayData data) { + if (data.size() == 0) { + this->visible = false; + return; + } + if (this->visible && this->x == x && this->y == y && this->data == data) return; this->x = x; this->y = y; this->data = data; @@ -248,9 +255,8 @@ public: void DrawContent(Rect rect) override { int row = 0; - for (const auto &[icon, s] : this->data) { - auto [ipos, srect] = this->aligner.GetPositions(rect, row, icon); - + for (const auto &[indent, icon, s] : this->data) { + auto [ipos, srect] = this->aligner.GetPositions(rect, row, indent, icon); DrawString(srect.left, srect.right, srect.top, s); if (icon != PAL_NONE) DrawSprite(icon, PAL_NONE, ipos.x, ipos.y); diff --git a/src/citymania/cm_overlays.hpp b/src/citymania/cm_overlays.hpp index 1822d7fc64..5952a60c54 100644 --- a/src/citymania/cm_overlays.hpp +++ b/src/citymania/cm_overlays.hpp @@ -6,7 +6,7 @@ namespace citymania { -typedef std::vector> BuildInfoOverlayData; +typedef std::vector> BuildInfoOverlayData; void UndrawOverlays(int left, int top, int right, int bottom); void DrawOverlays(); diff --git a/src/citymania/cm_station_gui.cpp b/src/citymania/cm_station_gui.cpp index ee870662b4..82f81b7034 100644 --- a/src/citymania/cm_station_gui.cpp +++ b/src/citymania/cm_station_gui.cpp @@ -2,6 +2,7 @@ #include "cm_station_gui.hpp" +#include "cm_command_type.hpp" #include "cm_highlight.hpp" #include "cm_highlight_type.hpp" #include "cm_hotkeys.hpp" @@ -14,6 +15,7 @@ #include "../industry_map.h" #include "../industry.h" #include "../landscape.h" +#include "../newgrf_airport.h" // AirportClassID, AirportClass #include "../newgrf_station.h" // StationClassID #include "../newgrf_house.h" // GetHouseCallback #include "../newgrf_cargo.h" // GetCargoTranslation @@ -33,6 +35,7 @@ #include "cm_type.hpp" #include "generated/cm_gen_commands.hpp" +#include #include #include #include @@ -73,9 +76,12 @@ struct RoadStopGUISettings { }; extern RoadStopGUISettings _roadstop_gui_settings; +extern AirportClassID _selected_airport_class; ///< the currently visible airport class +extern int _selected_airport_index; ///< the index of the selected airport in the current class or -1 +extern byte _selected_airport_layout; ///< selected airport layout number. + namespace citymania { -StationBuildingStatus _station_building_status = StationBuildingStatus::NEW; const Station *_highlight_station_to_join = nullptr; TileArea _highlight_join_area; @@ -83,10 +89,6 @@ bool UseImprovedStationJoin() { return _settings_client.gui.cm_use_improved_station_join && _settings_game.station.distant_join_stations && _settings_game.station.adjacent_stations; } -void SetStationBiildingStatus(StationBuildingStatus status) { - _station_building_status = status; -}; - static const int MAX_TILE_EXTENT_LEFT = ZOOM_LVL_BASE * TILE_PIXELS; ///< Maximum left extent of tile relative to north corner. static const int MAX_TILE_EXTENT_RIGHT = ZOOM_LVL_BASE * TILE_PIXELS; ///< Maximum right extent of tile relative to north corner. static const int MAX_TILE_EXTENT_TOP = ZOOM_LVL_BASE * MAX_BUILDING_PIXELS; ///< Maximum top extent of tile relative to north corner (not considering bridges). @@ -124,7 +126,6 @@ static void UpdateHiglightJoinArea(const Station *station) { // if (_highlight_join_area.tile == ta.tile && // _highlight_join_area.w == ta.w && // _highlight_join_area.h == ta.h) return; - MarkTileAreaDirty(_highlight_join_area); // _highlight_join_area = ta; MarkTileAreaDirty(_highlight_join_area); } @@ -137,42 +138,31 @@ void MarkCoverageHighlightDirty() { MarkCatchmentTilesDirty(); } -void SetHighlightStationToJoin(const Station *station, bool with_area) { - UpdateHiglightJoinArea(with_area ? station : nullptr); - - if (_highlight_station_to_join == station) return; - - if (_highlight_station_to_join && _settings_client.gui.station_show_coverage) - MarkCoverageAreaDirty(_highlight_station_to_join); - _highlight_station_to_join = station; - if (_highlight_station_to_join && _settings_client.gui.station_show_coverage) - MarkCoverageAreaDirty(_highlight_station_to_join); -} - void OnStationTileSetChange(const Station *station, bool /* adding */, StationType /* type */) { - if (station == _highlight_station_to_join) { - // if (_highlight_join_area.tile != INVALID_TILE) - // UpdateHiglightJoinArea(_station_to_join); - if (_settings_client.gui.station_show_coverage) - MarkCoverageAreaDirty(_highlight_station_to_join); - } - if (station == _viewport_highlight_station) MarkCoverageAreaDirty(_viewport_highlight_station); + // TODO + // if (station == _highlight_station_to_join) { + // // if (_highlight_join_area.tile != INVALID_TILE) + // // UpdateHiglightJoinArea(_station_to_join); + // if (_settings_client.gui.station_show_coverage) + // MarkCoverageAreaDirty(_highlight_station_to_join); + // } + // if (station == _viewport_highlight_station) MarkCoverageAreaDirty(_viewport_highlight_station); } void OnStationDeleted(const Station *station) { - if (_highlight_station_to_join == station) { - MarkCoverageAreaDirty(station); - _highlight_station_to_join = nullptr; - } + // TODO + // if (_highlight_station_to_join == station) { + // MarkCoverageAreaDirty(station); + // _highlight_station_to_join = nullptr; + // } } -const Station *_last_built_station; +// const Station *_last_built_station; void OnStationPartBuilt(const Station *station) { - _last_built_station = station; - CheckRedrawStationCoverage(); + // _last_built_station = station; + // CheckRedrawStationCoverage(); } - const Station *CheckClickOnDeadStationSign() { int x = _cursor.pos.x; int y = _cursor.pos.y; @@ -200,36 +190,36 @@ const Station *CheckClickOnDeadStationSign() { return last_st; } -bool CheckStationJoin(TileIndex start_tile, TileIndex /* end_tile */) { - if (citymania::_fn_mod) { - if (IsTileType (start_tile, MP_STATION)) { - citymania::SelectStationToJoin(Station::GetByTile(start_tile)); - return true; - } - auto st = CheckClickOnDeadStationSign(); - if (st) { - citymania::SelectStationToJoin(st); - return true; - } - } - return false; -} +// bool CheckStationJoin(TileIndex start_tile, TileIndex /* end_tile */) { +// if (citymania::_fn_mod) { +// if (IsTileType (start_tile, MP_STATION)) { +// citymania::SelectStationToJoin(Station::GetByTile(start_tile)); +// return true; +// } +// auto st = CheckClickOnDeadStationSign(); +// if (st) { +// citymania::SelectStationToJoin(st); +// return true; +// } +// } +// return false; +// } -template -void JoinAndBuild(Tcommand command, Tcallback *callback) { - auto join_to = _highlight_station_to_join; - command.adjacent = (citymania::_fn_mod || join_to); - command.station_to_join = INVALID_STATION; +// template +// void JoinAndBuild(Tcommand command, Tcallback *callback) { +// auto join_to = _highlight_station_to_join; +// command.adjacent = (citymania::_fn_mod || join_to); +// command.station_to_join = INVALID_STATION; - if (citymania::_fn_mod) command.station_to_join = NEW_STATION; - else if (join_to) command.station_to_join = join_to->index; +// if (citymania::_fn_mod) command.station_to_join = NEW_STATION; +// else if (join_to) command.station_to_join = join_to->index; - command.with_callback([] (bool res)->bool { - if (!res) return false; - // _station_to_join = _last_built_station; - return true; - }).post(callback); -} +// command.with_callback([] (bool res)->bool { +// if (!res) return false; +// // _station_to_join = _last_built_station; +// return true; +// }).post(callback); +// } static DiagDirection TileFractCoordsToDiagDir(Point pt) { auto x = pt.x & TILE_UNIT_MASK; @@ -354,158 +344,6 @@ DiagDirection AutodetectDriveThroughRoadStopDirection(TileArea area, Point pt, R return DiagDirToAxis(AutodetectRoadObjectDirection(area.tile, pt, roadtype)) == AXIS_X ? STATIONDIR_X : STATIONDIR_Y; } -void PlaceRoadStop(TileIndex start_tile, TileIndex end_tile, RoadStopType stop_type, bool adjacent, RoadType rt, StringID err_msg) { - assert(_thd.cm.type == citymania::ObjectHighlight::Type::ROAD_STOP); - DiagDirection ddir = _thd.cm.ddir; - - TileArea ta(start_tile, end_tile); - - if (CheckStationJoin(start_tile, end_tile)) return; - - bool drive_through = (ddir >= DIAGDIR_END); - if (drive_through) ddir = static_cast(ddir - DIAGDIR_END); // Adjust picker result to actual direction. - RoadStopClassID spec_class = _roadstop_gui_settings.roadstop_class; - uint16_t spec_index = _roadstop_gui_settings.roadstop_type; - - auto c = cmd::BuildRoadStop( - ta.tile, - ta.w, - ta.h, - stop_type, - drive_through, - static_cast(ddir), - rt, - spec_class, - spec_index, - INVALID_STATION, - adjacent - ); - c.with_error(err_msg); - JoinAndBuild(c, CcRoadStop); -} - -void HandleStationPlacement(TileIndex start, TileIndex end) -{ - if (CheckStationJoin(start, end)) return; - - TileArea ta(start, end); - uint numtracks = ta.w; - uint platlength = ta.h; - - if (_railstation.orientation == AXIS_X) Swap(numtracks, platlength); - - auto c = cmd::BuildRailStation( - ta.tile, - _cur_railtype, - _railstation.orientation, - numtracks, - platlength, - _railstation.station_class, - _railstation.station_type, - INVALID_STATION, - false - ); - c.with_error(STR_ERROR_CAN_T_BUILD_RAILROAD_STATION); - JoinAndBuild(c, CcStation); -} - -void PlaceRail_Station(TileIndex tile) { - if (CheckStationJoin(tile, tile)) return; - auto c = cmd::BuildRailStation( - tile, - _cur_railtype, - _railstation.orientation, - _settings_client.gui.station_numtracks, - _settings_client.gui.station_platlength, - _railstation.station_class, - _railstation.station_type, - INVALID_STATION, - false - ); - c.with_error(STR_ERROR_CAN_T_BUILD_RAILROAD_STATION); - JoinAndBuild(c, CcStation); -} - -void PlaceDock(TileIndex tile, TileIndex tile_to) { - if (CheckStationJoin(tile, tile_to)) return; - - auto c = cmd::BuildDock( - tile, - INVALID_STATION, - false - ); - c.with_error(STR_ERROR_CAN_T_BUILD_DOCK_HERE); - JoinAndBuild(c, CcBuildDocks); -} - -void PlaceAirport(TileIndex tile) { - if (CheckStationJoin(tile, tile)) return; - - if (_selected_airport_index == -1) return; - - byte airport_type = AirportClass::Get(_selected_airport_class)->GetSpec(_selected_airport_index)->GetIndex(); - byte layout = _selected_airport_layout; - - auto c = cmd::BuildAirport( - tile, - airport_type, - layout, - INVALID_STATION, - false - ); - c.with_error(STR_ERROR_CAN_T_BUILD_AIRPORT_HERE); - JoinAndBuild(c, CcBuildAirport); -} - -bool CheckRedrawStationCoverage() { - // static bool last_ctrl_pressed = false; - static TileArea last_location; - static bool last_station_mode = false; - static bool last_fn_mod = false; - TileArea location(TileVirtXY(_thd.pos.x, _thd.pos.y), _thd.size.x / TILE_SIZE - 1, _thd.size.y / TILE_SIZE - 1); - bool station_mode = ((_thd.drawstyle & HT_DRAG_MASK) == HT_RECT && _thd.outersize.x > 0); - bool location_changed = (location.tile != last_location.tile || location.w != last_location.w || location.h != last_location.h); - bool mode_changed = (last_station_mode != station_mode); - if (!location_changed && citymania::_fn_mod == last_fn_mod && !mode_changed) - return false; - - last_fn_mod = citymania::_fn_mod; - last_location = location; - last_station_mode = station_mode; - - if (citymania::_fn_mod) { - Station *st = nullptr; - if (IsTileType(location.tile, MP_STATION) && GetTileOwner(location.tile) == _local_company) - st = Station::GetByTile(location.tile); - - // SetHighlightStationToJoin(st, _station_to_join && st == _station_to_join); - _station_building_status = (st == nullptr ? StationBuildingStatus::NEW : StationBuildingStatus::JOIN); - } else { - // if (_station_to_join) { - // SetHighlightStationToJoin(_station_to_join, true); - // _station_building_status = StationBuildingStatus::JOIN; - // } else { - // FindStationsAroundSelection(location); - // } - } - return true; -} - - -void SelectStationToJoin(const Station *) { - // if (_station_to_join == station) - // _station_to_join = nullptr; - // else - // _station_to_join = station; - CheckRedrawStationCoverage(); -} - -void AbortStationPlacement() { - // _station_to_join = nullptr; - SetHighlightStationToJoin(nullptr, false); -} - - uint GetMonthlyFrom256Tick(uint amount) { return ((amount * Ticks::DAY_TICKS * EconomyTime::DAYS_IN_ECONOMY_MONTH) >> 8); } @@ -621,66 +459,9 @@ CargoArray GetProductionAroundTiles(TileIndex tile, int w, int h, int rad) return produced; } -std::optional GetStationCoverageAreaText(TileIndex tile, int w, int h, int rad, StationCoverageType sct, bool supplies) { - auto sp = dynamic_cast(GetActivePreview().get()); - if (sp != nullptr) - return sp->GetStationCoverageAreaText(rad, sct, supplies); - if (!supplies) return std::nullopt; - auto production = citymania::GetProductionAroundTiles(tile, w, h, rad); - - std::ostringstream s; - s << GetString(CM_STR_STATION_BUILD_SUPPLIES); - bool first = true; - for (CargoID i = 0; i < NUM_CARGO; i++) { - if (production[i] == 0) continue; - switch (sct) { - case SCT_PASSENGERS_ONLY: if (!IsCargoInClass(i, CC_PASSENGERS)) continue; break; - case SCT_NON_PASSENGERS_ONLY: if (IsCargoInClass(i, CC_PASSENGERS)) continue; break; - case SCT_ALL: break; - default: NOT_REACHED(); - } - if (!first) s << ", "; - first = false; - SetDParam(0, i); - SetDParam(1, production[i] >> 8); - s << GetString(STR_JUST_CARGO); - } - return s.str(); -} - - // ---- NEw code -StationID _station_to_join = INVALID_STATION; -std::chrono::time_point _station_to_join_selected; - -void OnStationRemoved(const Station *station) { - if (_last_built_station == station) _last_built_station = nullptr; - if (_station_to_join == station->index) { - _station_to_join = INVALID_STATION; - } - if (_ap.preview != nullptr) _ap.preview->OnStationRemoved(station); -} - -// copied from cm_blueprint.cpp -template -void IterateStation(TileIndex start_tile, Axis axis, byte numtracks, byte plat_len, Func visitor) { - auto plat_delta = (axis == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1)); - auto track_delta = (axis == AXIS_Y ? TileDiffXY(1, 0) : TileDiffXY(0, 1)); - TileIndex tile_track = start_tile; - do { - TileIndex tile = tile_track; - int w = plat_len; - do { - visitor(tile); - tile += plat_delta; - } while (--w); - tile_track += track_delta; - } while (--numtracks); -} - - -TileArea GetStationJoinArea(StationID station_id) { +static TileArea GetStationJoinArea(StationID station_id) { auto station = Station::GetIfValid(station_id); if (station == nullptr) return {}; @@ -696,586 +477,967 @@ TileArea GetStationJoinArea(StationID station_id) { return ta; } -bool RailStationPreview::IsDragDrop() const { - return _settings_client.gui.station_dragdrop; +extern DiagDirection AddAutodetectionRotation(DiagDirection ddir); // cm_highlight.cpp + +// void VanillaStationPreview::Update(Point pt, TileIndex tile) { +// StationPreviewBase::Update(pt, tile); +// this->palette = CM_PALETTE_TINT_WHITE; + +// if (this->remove_mode) return; +// if (this->selected_station_to_join != INVALID_STATION) { +// this->station_to_join = this->selected_station_to_join; +// return; +// } + +// if (!IsValidTile(this->type->cur_tile)) return; +// this->station_to_join = INVALID_STATION; +// auto area = this->type->GetArea(false); +// area.Expand(1); +// area.ClampToMap(); +// for (auto tile : area) { +// if (IsTileType(tile, MP_STATION) && GetTileOwner(tile) == _local_company) { +// Station *st = Station::GetByTile(tile); +// if (st == nullptr || st->index == this->station_to_join) continue; +// if (this->station_to_join != INVALID_STATION) { +// this->station_to_join = INVALID_STATION; +// this->palette = CM_PALETTE_TINT_YELLOW; +// break; +// } +// this->station_to_join = st->index; +// // TODO check for command to return multiple? but also check each to +// // see if they can be built +// // if (this->GetCommand(true, st->index)->test().Succeeded()) { +// // if (this->station_to_join != INVALID_STATION) { +// // this->station_to_join = INVALID_STATION; +// // this->palette = CM_PALETTE_TINT_YELLOW; +// // break; +// // } else this->station_to_join = st->index; +// // } +// } +// } +// if (this->station_to_join == INVALID_STATION && !this->GetCommand(true, NEW_STATION)->test().Succeeded()) +// this->palette = CM_PALETTE_TINT_RED_DEEP; +// } + +// void VanillaStationPreview::Execute() { +// if (this->remove_mode) { +// this->type->Execute(this->type->GetRemoveCommand(), true); +// return; +// } +// auto proc = [type=this->type](bool test, StationID to_join) -> bool { +// auto cmd = type->GetCommand(_fn_mod, to_join); +// if (test) return cmd->test().Succeeded(); +// return type->Execute(std::move(cmd), false); +// }; +// ShowSelectStationIfNeeded(this->type->GetArea(false), proc); +// } + +// void VanillaStationPreview::OnStationRemoved(const Station *station) { +// if (this->station_to_join == station->index) this->station_to_join = INVALID_STATION; +// if (this->selected_station_to_join == station->index) this->station_to_join = INVALID_STATION; +// } + +// StationPreview::StationPreview(sp type) +// :StationPreviewBase{type} +// { +// auto seconds_since_selected = std::chrono::duration_cast(std::chrono::system_clock::now() - _station_to_join_selected).count(); +// if (seconds_since_selected < 30) this->station_to_join = _station_to_join; +// else this->station_to_join = INVALID_STATION; +// } + +// StationPreview::~StationPreview() { +// _station_to_join_selected = std::chrono::system_clock::now(); +// } + +// up StationPreview::GetCommand() { +// if (this->select_mode) return nullptr; + +// auto res = StationPreviewBase::GetCommand(true, this->station_to_join); +// if (this->remove_mode) return res; + +// res->with_callback([] (bool res) -> bool { +// if (!res) return false; +// if (_last_built_station == nullptr) return false; +// _station_to_join = _last_built_station->index; +// _station_to_join_selected = std::chrono::system_clock::now(); +// // auto p = dynamic_cast(_ap.preview.get()); +// // if (p == nullptr) return false; +// // p->station_to_join = _last_built_station->index; +// return true; +// }); +// return res; +// } + +// void StationPreview::OnStationRemoved(const Station *station) { +// if (this->station_to_join == station->index) this->station_to_join = INVALID_STATION; +// } + +void SetSelectedStationToJoin(StationID station_id) { + StationBuildTool::current_selected_station = station_id; + UpdateActiveTool(); } -CursorID RailStationPreview::GetCursor() const { - return SPR_CURSOR_RAIL_STATION; +void ResetJoinStationHighlight() { + StationBuildTool::active_highlight = std::nullopt; + SetSelectedStationToJoin(INVALID_STATION); } -TileArea RailStationPreview::GetArea(bool remove_mode) const { - if (this->IsDragDrop() || remove_mode) return {this->GetStartTile(), this->cur_tile}; - if (_railstation.orientation == AXIS_X) return {this->cur_tile, _settings_client.gui.station_platlength, _settings_client.gui.station_numtracks}; - return {this->cur_tile, _settings_client.gui.station_numtracks, _settings_client.gui.station_platlength}; +void OnStationRemoved(const Station *station) { + // if (_last_built_station == station) _last_built_station = nullptr; + if (StationBuildTool::station_to_join == station->index) { + StationBuildTool::station_to_join = INVALID_STATION; + UpdateActiveTool(); + } + if (StationBuildTool::current_selected_station == station->index) { + StationBuildTool::current_selected_station = INVALID_STATION; + UpdateActiveTool(); + } + // TODO? + // if (GetActiveTool() != nullptr) GetActiveTool()->OnStationRemoved(station); } -up RailStationPreview::GetCommand(bool adjacent, StationID join_to) const { - auto ta = this->GetArea(false); - auto start_tile = ta.tile; - auto numtracks = ta.w; - auto platlength = ta.h; - if (_railstation.orientation == AXIS_X) Swap(numtracks, platlength); - - auto res = make_up( - start_tile, - _cur_railtype, - _railstation.orientation, - numtracks, - platlength, - _railstation.station_class, - _railstation.station_type, - join_to, - adjacent - ); - res->with_error(STR_ERROR_CAN_T_BUILD_RAILROAD_STATION); - return res; +void AbortStationPlacement() { + // TODO is it necessary? + // SetHighlightStationToJoin(station=nullptr, with_area=false); } -up RailStationPreview::GetRemoveCommand() const { - auto res = make_up( - this->GetStartTile(), - this->cur_tile, - !citymania::_fn_mod - ); - res->with_error(STR_ERROR_CAN_T_REMOVE_PART_OF_STATION); - return res; + +// --- Action base class --- +void Action::OnStationRemoved(const Station *) {} + +// --- RemoveAction --- +template +void RemoveAction::Update(Point, TileIndex tile) { + this->cur_tile = tile; } -bool RailStationPreview::Execute(up cmd, bool remove_mode) const { - if (remove_mode) return cmd->post(&CcPlaySound_CONSTRUCTION_RAIL); - else return cmd->post(&CcStation); -} - -void RailStationPreview::AddPreviewTiles(HighlightMap &hlmap, SpriteID palette) const { - auto cmd = this->GetCommand(true, NEW_STATION); - auto cmdt = dynamic_cast(cmd.get()); - if (cmdt == nullptr) return; - - if (palette == PAL_NONE) palette = cmd->test().Succeeded() ? CM_PALETTE_TINT_WHITE : CM_PALETTE_TINT_RED_DEEP; - - std::vector layouts(cmdt->numtracks * cmdt->plat_len); - byte *layout_ptr = layouts.data(); - GetStationLayout(layout_ptr, cmdt->numtracks, cmdt->plat_len, nullptr); - IterateStation(cmdt->tile_org, cmdt->axis, cmdt->numtracks, cmdt->plat_len, - [&](TileIndex t) { - byte layout = *layout_ptr++; - hlmap.Add(t, ObjectTileHighlight::make_rail_station(palette, cmdt->axis, layout & ~1)); - } - ); -} - -OverlayParams RailStationPreview::GetOverlayParams() const { - return {this->GetArea(false), CA_TRAIN, SCT_ALL}; -} - -bool RoadStationPreview::IsDragDrop() const { +template +bool RemoveAction::HandleMousePress() { + if (!IsValidTile(this->cur_tile)) return false; + this->start_tile = this->cur_tile; return true; } -CursorID RoadStationPreview::GetCursor() const { - return SPR_CURSOR_BUS_STATION; - // return SPR_CURSOR_TRUCK_STATION; +template +void RemoveAction::HandleMouseRelease() { + auto area = this->GetArea(); + if (!area.has_value()) return; + this->handler.Execute(area.value()); + this->start_tile = INVALID_TILE; } -TileArea RoadStationPreview::GetArea(bool /* remove_mode */) const { - return {this->GetStartTile(), this->cur_tile}; +template +std::optional RemoveAction::GetArea() const { + if (!IsValidTile(this->cur_tile)) return std::nullopt; + if (!IsValidTile(this->start_tile)) return TileArea{this->cur_tile, this->cur_tile}; + return TileArea{this->start_tile, this->cur_tile}; } -extern DiagDirection AddAutodetectionRotation(DiagDirection ddir); // cm_highlight.cpp - -void RoadStationPreview::Update(Point pt, TileIndex tile) { - if (pt.x == -1) return; - - auto ddir = _roadstop_gui_settings.orientation; - auto area = this->GetArea(false); - if (ddir >= DIAGDIR_END && ddir < STATIONDIR_AUTO) { - // When placed on road autorotate anyway - if (ddir == STATIONDIR_X) { - if (!CheckDriveThroughRoadStopDirection(area, ROAD_X)) - ddir = STATIONDIR_Y; - } else { - if (!CheckDriveThroughRoadStopDirection(area, ROAD_Y)) - ddir = STATIONDIR_X; - } - } else if (ddir == STATIONDIR_AUTO) { - ddir = AddAutodetectionRotation(AutodetectRoadObjectDirection(tile, pt, _cur_roadtype)); - } else if (ddir == STATIONDIR_AUTO_XY) { - ddir = AddAutodetectionRotation(AutodetectDriveThroughRoadStopDirection(area, pt, _cur_roadtype)); +template +ToolGUIInfo RemoveAction::GetGUIInfo() { + HighlightMap hlmap; + BuildInfoOverlayData data; + auto area = this->GetArea(); + CommandCost cost; + if (area.has_value()) { + hlmap.AddTileAreaWithBorder(area.value(), CM_PALETTE_TINT_RED_DEEP); + auto cmd = this->handler.GetCommand(area.value()); + if (cmd) cost = cmd->test(); } - this->ddir = ddir; + return {hlmap, data, cost}; } -up RoadStationPreview::GetCommand(bool adjacent, StationID join_to) const { - auto area = this->GetArea(false); - DiagDirection ddir = this->ddir; - bool drive_through = this->ddir >= DIAGDIR_END; - if (drive_through) ddir = static_cast(this->ddir - DIAGDIR_END); // Adjust picker result to actual direction. - RoadStopClassID spec_class = _roadstop_gui_settings.roadstop_class; - uint16_t spec_index = _roadstop_gui_settings.roadstop_type; +template +void RemoveAction::OnStationRemoved(const Station *) {} - auto res = make_up( - area.tile, - area.w, - area.h, - this->stop_type, - drive_through, - ddir, - _cur_roadtype, - spec_class, - spec_index, - join_to, - adjacent - ); +static HighlightMap PrepareHighilighMap(Station *st_join, ObjectHighlight &ohl, SpriteID pal, bool show_join_area, StationCoverageType sct, uint rad) { + bool add_current = true; // FIXME + bool show_coverage = (rad > 0); - return res; -} - -up RoadStationPreview::GetRemoveCommand() const { - auto area = this->GetArea(false); - auto res = make_up( - area.tile, - area.w, - area.h, - this->stop_type, - citymania::_fn_mod - ); - auto rti = GetRoadTypeInfo(_cur_roadtype); - res->with_error(rti->strings.err_remove_station[this->stop_type]); - return res; -} - -bool RoadStationPreview::Execute(up cmd, bool remove_mode) const { - if (remove_mode) return cmd->post(&CcPlaySound_CONSTRUCTION_OTHER); - else return cmd->post(&CcRoadStop); -} - -void RoadStationPreview::AddPreviewTiles(HighlightMap &hlmap, SpriteID palette) const { - auto cmd = this->GetCommand(true, NEW_STATION); - auto cmdt = dynamic_cast(cmd.get()); - if (cmdt == nullptr) return; - - if (palette == PAL_NONE) palette = cmd->test().Succeeded() ? CM_PALETTE_TINT_WHITE : CM_PALETTE_TINT_RED_DEEP; - - for (TileIndex t : this->GetArea(false)) { - auto ddir = cmdt->ddir; - if (cmdt->is_drive_through) ddir = ddir + DIAGDIR_END; - hlmap.Add(t, ObjectTileHighlight::make_road_stop( - palette, - cmdt->rt, - ddir, - cmdt->stop_type == ROADSTOP_TRUCK, - cmdt->spec_class, - cmdt->spec_index - )); - } -} - -OverlayParams RoadStationPreview::GetOverlayParams() const { - return { - this->GetArea(false), - this->stop_type == ROADSTOP_TRUCK ? CA_TRUCK : CA_BUS, - this->stop_type == ROADSTOP_TRUCK ? SCT_NON_PASSENGERS_ONLY : SCT_PASSENGERS_ONLY - }; -} - -bool DockPreview::IsDragDrop() const { - return false; -} - -CursorID DockPreview::GetCursor() const { - return SPR_CURSOR_DOCK; -} - -TileArea DockPreview::GetArea(bool /* remove_mode */) const { - auto tile = this->GetStartTile(); - TileIndex tile_to = (this->ddir != INVALID_DIAGDIR ? TileAddByDiagDir(tile, this->ddir) : tile); - return {tile, tile_to}; -} - -void DockPreview::Update(Point pt, TileIndex tile) { - if (pt.x == -1) return; - this->ddir = GetInclinedSlopeDirection(GetTileSlope(tile)); - if (this->ddir == INVALID_DIAGDIR) this->ddir = DIAGDIR_NE; - else this->ddir = ReverseDiagDir(this->ddir); -} - -up DockPreview::GetCommand(bool adjacent, StationID join_to) const { - // STR_ERROR_CAN_T_BUILD_DOCK_HERE - return make_up( - this->GetStartTile(), - join_to, - adjacent - ); -} - -up DockPreview::GetRemoveCommand() const { - return nullptr; -} - -bool DockPreview::Execute(up cmd, bool remove_mode) const { - return cmd->post(&CcBuildDocks); -} - -void DockPreview::AddPreviewTiles(HighlightMap &hlmap, SpriteID palette) const { - auto t = this->GetStartTile(); - hlmap.Add(t, ObjectTileHighlight::make_dock_slope(CM_PALETTE_TINT_WHITE, this->ddir)); - t += TileOffsByDiagDir(this->ddir); - hlmap.Add(t, ObjectTileHighlight::make_dock_flat(CM_PALETTE_TINT_WHITE, DiagDirToAxis(this->ddir))); - // TODO - // auto cmd = this->GetCommand(true, NEW_STATION); - // auto cmdt = dynamic_cast(cmd.get()); - // if (cmdt == nullptr) return; - - // if (palette == PAL_NONE) palette = cmd->test().Succeeded() ? CM_PALETTE_TINT_WHITE : CM_PALETTE_TINT_RED_DEEP; - - // for (TileIndex t : this->GetArea(false)) { - // auto ddir = cmdt->ddir; - // if (cmdt->is_drive_through) ddir = ddir + DIAGDIR_END; - // hlmap.Add(t, ObjectTileHighlight::make_road_stop( - // palette, - // cmdt->rt, - // ddir, - // cmdt->stop_type == ROADSTOP_TRUCK, - // cmdt->spec_class, - // cmdt->spec_index - // )); - // } -} - -OverlayParams DockPreview::GetOverlayParams() const { - return { - this->GetArea(false), - CA_DOCK, - SCT_ALL - }; -} - -void StationPreviewBase::AddAreaTiles(HighlightMap &hlmap, bool add_current, bool show_join_area) { - Station *st_join = Station::GetIfValid(this->station_to_join); - std::set join_area; + auto hlmap = ohl.GetHighlightMap(pal); + TileArea join_area; std::set coverage_area; if (show_join_area && st_join != nullptr) { - hlmap.AddTileArea(GetStationJoinArea(st_join->index), CM_PALETTE_TINT_CYAN); - // FIXME hlmap can already have stuff - for (auto t : hlmap.GetAllTiles()) join_area.insert(t); + join_area = GetStationJoinArea(st_join->index); + hlmap.AddTileArea(join_area, CM_PALETTE_TINT_CYAN); } - if (this->show_coverage && st_join != nullptr) { + if (show_coverage && st_join != nullptr) { // Add joining station coverage for (auto t : st_join->catchment_tiles) { - auto pal = join_area.find(t) != join_area.end() ? CM_PALETTE_TINT_CYAN_WHITE : CM_PALETTE_TINT_WHITE; + auto pal = join_area.Contains(t) ? CM_PALETTE_TINT_CYAN_WHITE : CM_PALETTE_TINT_WHITE; hlmap.Add(t, ObjectTileHighlight::make_tint(pal)); coverage_area.insert(t); } } - if (this->show_coverage && add_current) { + auto area = ohl.GetArea(); + if (!_settings_game.station.modified_catchment) rad = CA_UNMODIFIED; + std::optional rad_area = std::nullopt; + if (area.has_value()) { + auto xarea = area.value(); + xarea.Expand(rad); + xarea.ClampToMap(); + rad_area = xarea; + } + if (show_coverage && add_current && rad_area.has_value()) { // Add current station coverage - auto rad = CA_UNMODIFIED; - if (_settings_game.station.modified_catchment) rad = CA_TRAIN; - auto area = this->type->GetArea(false); - area.Expand(rad); - area.ClampToMap(); - for (auto t : area) { - auto pal = join_area.find(t) != join_area.end() ? CM_PALETTE_TINT_CYAN_WHITE : CM_PALETTE_TINT_WHITE; + for (auto t : rad_area.value()) { + auto pal = join_area.Contains(t) ? CM_PALETTE_TINT_CYAN_WHITE : CM_PALETTE_TINT_WHITE; hlmap.Add(t, ObjectTileHighlight::make_tint(pal)); coverage_area.insert(t); } } - if (this->show_coverage) { + if (show_coverage) { hlmap.AddTilesBorder(coverage_area, CM_PALETTE_TINT_WHITE); } if (st_join != nullptr) { + // Highlight joining station blue TileArea ta(TileXY(st_join->rect.left, st_join->rect.top), TileXY(st_join->rect.right, st_join->rect.bottom)); for (TileIndex t : ta) { if (!IsTileType(t, MP_STATION) || GetStationIndex(t) != st_join->index) continue; hlmap.Add(t, ObjectTileHighlight::make_struct_tint(CM_PALETTE_TINT_BLUE)); } } + + return hlmap; } -void StationPreviewBase::Update(Point pt, TileIndex tile) { - if (tile != INVALID_TILE) this->type->cur_tile = tile; - this->show_coverage = _settings_client.gui.station_show_coverage; - this->adjacent_stations = _settings_game.station.adjacent_stations; - this->remove_mode = false; - if (_remove_button_clicked) { - this->remove_mode = true; - this->keep_rail = !_fn_mod; - } else if (!this->type->IsDragDrop()) { - this->type->start_tile = INVALID_TILE; +ToolGUIInfo PlacementAction::PrepareGUIInfo(std::optional ohl, up cmd, StationCoverageType sct, uint rad) { + if (!cmd || !ohl.has_value()) return {}; + ohl.value().UpdateTiles(); + + auto cost = cmd->test(); + + bool show_coverage = _settings_client.gui.station_show_coverage; + + auto hlmap = PrepareHighilighMap( + Station::GetIfValid(StationBuildTool::station_to_join), + ohl.value(), + cost.Succeeded() ? CM_PALETTE_TINT_WHITE : CM_PALETTE_TINT_RED_DEEP, + true, + sct, + show_coverage ? rad : 0 + ); + + // Prepare build overlay + + BuildInfoOverlayData data; + + if (StationBuildTool::station_to_join != INVALID_STATION) { + SetDParam(0, StationBuildTool::station_to_join); + data.emplace_back(0, PAL_NONE, GetString(CM_STR_BULID_INFO_OVERLAY_JOIN_STATION)); + } else { + data.emplace_back(0, PAL_NONE, GetString(CM_STR_BULID_INFO_OVERLAY_NEW_STATION)); } - this->type->Update(pt, tile); + + auto area = ohl.value().GetArea(); + if (area.has_value()) { + // Add supplied cargo information + // TODO can we use rad_area since we already have it? + auto production = citymania::GetProductionAroundTiles(area->tile, area->w, area->h, rad); + bool has_header = false; + for (CargoID i = 0; i < NUM_CARGO; i++) { + if (production[i] == 0) continue; + + switch (sct) { + case SCT_PASSENGERS_ONLY: if (!IsCargoInClass(i, CC_PASSENGERS)) continue; break; + case SCT_NON_PASSENGERS_ONLY: if (IsCargoInClass(i, CC_PASSENGERS)) continue; break; + case SCT_ALL: break; + default: NOT_REACHED(); + } + + const CargoSpec *cs = CargoSpec::Get(i); + if (cs == nullptr) continue; + + if (!has_header) { + data.emplace_back(0, PAL_NONE, GetString(CM_STR_BUILD_INFO_OVERLAY_STATION_SUPPLIES)); + has_header = true; + } + SetDParam(0, i); + SetDParam(1, production[i] >> 8); + data.emplace_back(1, cs->GetCargoIcon(), GetString(CM_STR_BUILD_INFO_OVERLAY_STATION_CARGO)); + } + + // Add accepted cargo information + CargoTypes always_accepted; + auto cargoes = ::GetAcceptanceAroundTiles(area->tile, area->w, area->h, rad, &always_accepted); + /* Convert cargo counts to a set of cargo bits, and draw the result. */ + std::vector> cargostr; + for (CargoID i = 0; i < NUM_CARGO; i++) { + switch (sct) { + case SCT_PASSENGERS_ONLY: if (!IsCargoInClass(i, CC_PASSENGERS)) continue; break; + case SCT_NON_PASSENGERS_ONLY: if (IsCargoInClass(i, CC_PASSENGERS)) continue; break; + case SCT_ALL: break; + default: NOT_REACHED(); + } + if (cargoes[i] > 0) { + SetDParam(0, 1 << i); + if(cargoes[i] < 8) { + SetDParam(1, cargoes[i]); + cargostr.emplace_back(2, GetString(CM_STR_BULID_INFO_OVERLAY_ACCEPTS_CARGO_PARTIAL)); + } else if (HasBit(always_accepted, i)) { + SetDParam(1, cargoes[i]); + cargostr.emplace_back(1, GetString(CM_STR_BULID_INFO_OVERLAY_ACCEPTS_CARGO_FULL)); + } else { + cargostr.emplace_back(0, GetString(CM_STR_BULID_INFO_OVERLAY_ACCEPTS_CARGO)); + } + } + } + + std::string cargoliststr; + if (cargostr.size() > 0) { + // Sort by first element (priority) + std::stable_sort(cargostr.begin(), cargostr.end(), [](const auto& a, const auto& b) { + return a.first < b.first; + }); + std::vector cargolistsorted; + cargolistsorted.reserve((cargostr.size())); + for (auto [_, s] : cargostr) cargolistsorted.push_back(s); + cargoliststr = string::join(cargolistsorted, ", "); + } else { + cargoliststr = GetString(STR_JUST_NOTHING); + } + SetDParamStr(0, cargoliststr); + data.emplace_back(0, PAL_NONE, GetString(CM_STR_BULID_INFO_OVERLAY_ACCEPTS)); + + Town *t = ClosestTownFromTile(area->tile, _settings_game.economy.dist_local_authority); + if (t) { + auto town_allowed = CheckIfAuthorityAllowsNewStation(area->tile, DC_NONE).Succeeded(); + auto rating = t->ratings[_current_company]; + auto dist = DistanceManhattan(t->xy, area->tile); + SetDParam(0, t->index); + SetDParam(1, rating); + if (dist <= 10) { + SetDParam(2, CM_STR_BULID_INFO_OVERLAY_TOWN_S_ADS); + } else if (dist <= 15) { + SetDParam(2, CM_STR_BULID_INFO_OVERLAY_TOWN_M_ADS); + } else if (dist <= 20) { + SetDParam(2, CM_STR_BULID_INFO_OVERLAY_TOWN_L_ADS); + } else { + SetDParam(2, CM_STR_BULID_INFO_OVERLAY_TOWN_NO_ADS); + } + data.emplace_back(0, PAL_NONE, GetString(town_allowed ? CM_STR_BULID_INFO_OVERLAY_TOWN_ALLOWS : CM_STR_BULID_INFO_OVERLAY_TOWN_DENIES)); + } else { + data.emplace_back(0, PAL_NONE, GetString(CM_STR_BULID_INFO_OVERLAY_TOWN_NONE)); + } + + SetDParam(0, area->w); + SetDParam(1, area->h); + data.emplace_back(0, PAL_NONE, GetString(CM_STR_BULID_INFO_OVERLAY_STATION_SIZE)); + } + + return {hlmap, data, cost}; } -bool StationPreviewBase::HandleMousePress() { - if (!IsValidTile(this->type->cur_tile)) return false; +ToolGUIInfo GetSelectedStationGUIInfo() { + if (!StationBuildTool::active_highlight.has_value()) return {}; + auto &ohl = StationBuildTool::active_highlight.value(); + // TODO maybe update or none at all? + ohl.UpdateTiles(); + auto hlmap = PrepareHighilighMap( + Station::GetIfValid(StationBuildTool::current_selected_station), + ohl, + CM_PALETTE_TINT_WHITE, + false, + SCT_ALL, + 0 + ); + return {hlmap, {}, {}}; +} - if (this->remove_mode || this->type->IsDragDrop()) { - this->type->start_tile = this->type->cur_tile; - return true; - } +// --- SizedPlacementAction --- +template +void SizedPlacementAction::Update(Point, TileIndex tile) { + this->cur_tile = tile; +} - this->Execute(); +template +bool SizedPlacementAction::HandleMousePress() { + return IsValidTile(this->cur_tile); +} + +template +void SizedPlacementAction::HandleMouseRelease() { + if (!IsValidTile(this->cur_tile)) return; + this->handler.Execute(this->cur_tile); +} + +template +ToolGUIInfo SizedPlacementAction::GetGUIInfo() { + if (!IsValidTile(this->cur_tile)) return {}; + auto [sct, rad] = this->handler.GetCatchmentParams(); + return this->PrepareGUIInfo( + this->handler.GetObjectHighlight(this->cur_tile), + this->handler.GetCommand(this->cur_tile, INVALID_STATION), + sct, + rad + ); +} + +template +void SizedPlacementAction::OnStationRemoved(const Station *) {} + +// --- DragNDropPlacementAction --- + +template +std::optional DragNDropPlacementAction::GetArea() const { + // TODO separate common fuctions with RemoveAction into base class + if (!IsValidTile(this->cur_tile)) return std::nullopt; + if (!IsValidTile(this->start_tile)) return TileArea{this->cur_tile, this->cur_tile}; + return TileArea{this->start_tile, this->cur_tile}; +} + +template +void DragNDropPlacementAction::Update(Point, TileIndex tile) { + this->cur_tile = tile; +} + +template +bool DragNDropPlacementAction::HandleMousePress() { + if (!IsValidTile(this->cur_tile)) return false; + this->start_tile = this->cur_tile; return true; } -void StationPreviewBase::HandleMouseRelease() { - if (!IsValidTile(this->type->cur_tile)) return; - - if (this->type->start_tile != INVALID_TILE) { - this->Execute(); - this->type->start_tile = INVALID_TILE; - } +template +void DragNDropPlacementAction::HandleMouseRelease() { + auto area = this->GetArea(); + if (!area.has_value()) return; + this->handler.Execute(area.value()); + this->start_tile = INVALID_TILE; } -std::optional StationPreviewBase::GetStationCoverageAreaText(int rad, StationCoverageType sct, bool supplies) { - auto params = this->type->GetOverlayParams(); - if (params.area.tile == INVALID_TILE) return std::nullopt; +template +ToolGUIInfo DragNDropPlacementAction::GetGUIInfo() { + auto area = this->GetArea(); + if (!area.has_value()) return {}; + auto ohl = this->handler.GetObjectHighlight(area.value()); + auto [sct, rad] = this->handler.GetCatchmentParams(); + return this->PrepareGUIInfo( + this->handler.GetObjectHighlight(area.value()), + this->handler.GetCommand(area.value(), INVALID_STATION), + sct, + rad + ); +} - CargoArray cargoes; - if (supplies) { - cargoes = ::GetProductionAroundTiles(params.area.tile, params.area.w, params.area.h, params.radius); +template +void DragNDropPlacementAction::OnStationRemoved(const Station *) {} + +// --- StationSelectAction --- +template +void StationSelectAction::Update(Point, TileIndex tile) { this->cur_tile = tile; } + +template +bool StationSelectAction::HandleMousePress() { return true; } + +template +void StationSelectAction::HandleMouseRelease() { + // TODO station sign click + if (!IsValidTile(this->cur_tile)) return; + this->selected_station = INVALID_STATION; + if (IsTileType(this->cur_tile, MP_STATION)) { + auto st = Station::GetByTile(this->cur_tile); + if (st) this->selected_station = st->index; + } + this->handler.SelectStationToJoin(this->selected_station); +} + +template +ToolGUIInfo StationSelectAction::GetGUIInfo() { + if (!IsValidTile(this->cur_tile)) return {}; + HighlightMap hlmap; + hlmap.Add(this->cur_tile, ObjectTileHighlight::make_border(CM_PALETTE_TINT_BLUE, ZoningBorder::FULL)); + BuildInfoOverlayData data; + Station *st = IsTileType(this->cur_tile, MP_STATION) ? Station::GetByTile(this->cur_tile) : nullptr; + if (st) { + SetDParam(0, st->index); + data.emplace_back(0, PAL_NONE, GetString(CM_STR_BULID_INFO_OVERLAY_JOIN_STATION)); } else { - cargoes = ::GetAcceptanceAroundTiles(params.area.tile, params.area.w, params.area.h, params.radius); + data.emplace_back(0, PAL_NONE, GetString(CM_STR_BULID_INFO_OVERLAY_NEW_STATION)); + } + return {hlmap, data, {}}; +} + +template +void StationSelectAction::OnStationRemoved(const Station *station) { + if (this->selected_station == station->index) this->selected_station = INVALID_STATION; +} + +// --- StationBuildTool --- + +StationID StationBuildTool::station_to_join = INVALID_STATION; +StationID StationBuildTool::current_selected_station = INVALID_STATION; +std::optional StationBuildTool::active_highlight = std::nullopt; + +TileArea GetCommandArea(const up &cmd) { + if (auto rail_cmd = dynamic_cast(cmd.get())) { + auto w = rail_cmd->numtracks; + auto h = rail_cmd->plat_len; + if (!rail_cmd->axis) Swap(w, h); + return {rail_cmd->tile_org, w, h}; + } else if (auto road_cmd = dynamic_cast(cmd.get())) { + return {road_cmd->tile, road_cmd->width, road_cmd->length}; + } else if (auto dock_cmd = dynamic_cast(cmd.get())) { + DiagDirection dir = GetInclinedSlopeDirection(GetTileSlope(dock_cmd->tile)); + TileIndex tile_to = (dir != INVALID_DIAGDIR ? TileAddByDiagDir(dock_cmd->tile, ReverseDiagDir(dir)) : dock_cmd->tile); + return {dock_cmd->tile, tile_to}; + } else if (auto airport_cmd = dynamic_cast(cmd.get())) { + const AirportSpec *as = AirportSpec::Get(airport_cmd->airport_type); + return {airport_cmd->tile, as->size_x, as->size_y}; + } + NOT_REACHED(); +} + +template +bool StationBuildTool::ExecuteBuildCommand(Thandler *handler, Tcallback callback, Targ arg) { + if (UseImprovedStationJoin()) { + auto cmd = handler->GetCommand(arg, StationBuildTool::station_to_join); + StationBuildTool::active_highlight = std::nullopt; + return cmd ? cmd->post(callback) : false; } - CargoTypes cargo_mask = 0; - /* Convert cargo counts to a set of cargo bits, and draw the result. */ - for (CargoID i = 0; i < NUM_CARGO; i++) { - switch (sct) { - case SCT_PASSENGERS_ONLY: if (!IsCargoInClass(i, CC_PASSENGERS)) continue; break; - case SCT_NON_PASSENGERS_ONLY: if (IsCargoInClass(i, CC_PASSENGERS)) continue; break; - case SCT_ALL: break; - default: NOT_REACHED(); - } - if (cargoes[i] >= (supplies ? 1U : 8U)) SetBit(cargo_mask, i); - } - SetDParam(0, cargo_mask); - return GetString(supplies ? STR_STATION_BUILD_SUPPLIES_CARGO : STR_STATION_BUILD_ACCEPTS_CARGO); -} - -std::vector> StationPreviewBase::GetOverlayData() { - if (this->remove_mode) return {}; - - std::vector> res; - auto params = this->type->GetOverlayParams(); - - if (!_settings_game.station.modified_catchment) params.radius = CA_UNMODIFIED; - auto production = citymania::GetProductionAroundTiles(params.area.tile, params.area.w, params.area.h, params.radius); - bool has_header = false; - for (CargoID i = 0; i < NUM_CARGO; i++) { - if (production[i] == 0) continue; - - switch (params.coverage_type) { - case SCT_PASSENGERS_ONLY: if (!IsCargoInClass(i, CC_PASSENGERS)) continue; break; - case SCT_NON_PASSENGERS_ONLY: if (IsCargoInClass(i, CC_PASSENGERS)) continue; break; - case SCT_ALL: break; - default: NOT_REACHED(); - } - - const CargoSpec *cs = CargoSpec::Get(i); - if (cs == nullptr) continue; - - if (!has_header) { - res.emplace_back(PAL_NONE, GetString(CM_STR_BUILD_INFO_OVERLAY_STATION_SUPPLIES)); - has_header = true; - } - SetDParam(0, i); - SetDParam(1, production[i] >> 8); - res.emplace_back(cs->GetCargoIcon(), GetString(CM_STR_BUILD_INFO_OVERLAY_STATION_CARGO)); - } - return res; -} - -up StationPreviewBase::GetCommand(bool adjacent, StationID join_to) { - if (this->remove_mode) return this->type->GetRemoveCommand(); - return this->type->GetCommand(adjacent, join_to); -} - -HighlightMap VanillaStationPreview::GetHighlightMap() { - HighlightMap hlmap; - - if (!IsValidTile(this->type->cur_tile)) return hlmap; - - if (this->remove_mode) { - hlmap.AddTileAreaWithBorder(this->type->GetArea(true), CM_PALETTE_TINT_RED_DEEP); - return hlmap; - } - - this->AddAreaTiles(hlmap, true, false); - this->type->AddPreviewTiles(hlmap, this->palette); - - return hlmap; -} - -void VanillaStationPreview::Update(Point pt, TileIndex tile) { - StationPreviewBase::Update(pt, tile); - this->palette = CM_PALETTE_TINT_WHITE; - - if (this->remove_mode) return; - if (this->selected_station_to_join != INVALID_STATION) { - this->station_to_join = this->selected_station_to_join; - return; - } - - if (!IsValidTile(this->type->cur_tile)) return; - this->station_to_join = INVALID_STATION; - auto area = this->type->GetArea(false); - area.Expand(1); - area.ClampToMap(); - for (auto tile : area) { - if (IsTileType(tile, MP_STATION) && GetTileOwner(tile) == _local_company) { - Station *st = Station::GetByTile(tile); - if (st == nullptr || st->index == this->station_to_join) continue; - if (this->station_to_join != INVALID_STATION) { - this->station_to_join = INVALID_STATION; - this->palette = CM_PALETTE_TINT_YELLOW; - break; - } - this->station_to_join = st->index; - // TODO check for command to return multiple? but also check each to - // see if they can be built - // if (this->GetCommand(true, st->index)->test().Succeeded()) { - // if (this->station_to_join != INVALID_STATION) { - // this->station_to_join = INVALID_STATION; - // this->palette = CM_PALETTE_TINT_YELLOW; - // break; - // } else this->station_to_join = st->index; - // } - } - } - if (this->station_to_join == INVALID_STATION && !this->GetCommand(true, NEW_STATION)->test().Succeeded()) - this->palette = CM_PALETTE_TINT_RED_DEEP; -} - -void VanillaStationPreview::Execute() { - if (this->remove_mode) { - this->type->Execute(this->type->GetRemoveCommand(), true); - return; - } - auto proc = [type=this->type](bool test, StationID to_join) -> bool { - auto cmd = type->GetCommand(_fn_mod, to_join); - if (test) return cmd->test().Succeeded(); - return type->Execute(std::move(cmd), false); - }; - ShowSelectStationIfNeeded(this->type->GetArea(false), proc); -} - -void VanillaStationPreview::OnStationRemoved(const Station *station) { - if (this->station_to_join == station->index) this->station_to_join = INVALID_STATION; - if (this->selected_station_to_join == station->index) this->station_to_join = INVALID_STATION; -} - -StationPreview::StationPreview(sp type) - :StationPreviewBase{type} -{ - auto seconds_since_selected = std::chrono::duration_cast(std::chrono::system_clock::now() - _station_to_join_selected).count(); - if (seconds_since_selected < 30) this->station_to_join = _station_to_join; - else this->station_to_join = INVALID_STATION; -} - -StationPreview::~StationPreview() { - _station_to_join_selected = std::chrono::system_clock::now(); -} - -up StationPreview::GetCommand() { - if (this->select_mode) return nullptr; - - auto res = StationPreviewBase::GetCommand(true, this->station_to_join); - if (this->remove_mode) return res; - - res->with_callback([] (bool res) -> bool { - if (!res) return false; - if (_last_built_station == nullptr) return false; - _station_to_join = _last_built_station->index; - _station_to_join_selected = std::chrono::system_clock::now(); - auto p = dynamic_cast(_ap.preview.get()); - if (p == nullptr) return false; - p->station_to_join = _last_built_station->index; - return true; - }); - return res; -} - -HighlightMap StationPreview::GetHighlightMap() { - HighlightMap hlmap; - - if (!IsValidTile(this->type->cur_tile)) return hlmap; - - if (this->remove_mode) { - hlmap.AddTileAreaWithBorder(this->type->GetArea(true), CM_PALETTE_TINT_RED_DEEP); - return hlmap; - } - - this->AddAreaTiles(hlmap, !this->select_mode, true); - - if (this->select_mode) { - hlmap.Add(this->type->cur_tile, ObjectTileHighlight::make_border(CM_PALETTE_TINT_BLUE, ZoningBorder::FULL)); - return hlmap; - } - - this->type->AddPreviewTiles(hlmap, PAL_NONE); - - return hlmap; -} - -void StationPreview::Update(Point pt, TileIndex tile) { - this->select_mode = false; - StationPreviewBase::Update(pt, tile); - if (!this->remove_mode && _fn_mod) { - this->select_mode = true; - this->type->start_tile = INVALID_TILE; - } -} - -bool StationPreview::HandleMousePress() { - if (!IsValidTile(this->type->cur_tile)) return false; - - if (this->select_mode) { - if (IsTileType(this->type->cur_tile, MP_STATION)) { - auto st = Station::GetByTile(this->type->cur_tile); - this->station_to_join = st->index; - _station_to_join = this->station_to_join; - _station_to_join_selected = std::chrono::system_clock::now(); + // Vanilla joining behaviour + auto cmd = handler->GetCommand(arg, INVALID_STATION); + auto proc = [cmd=sp{std::move(cmd)}, callback](bool test, StationID to_join) -> bool { + StationBuildTool::station_to_join = to_join; + if (!cmd) return false; + auto station_cmd = dynamic_cast(cmd.get()); + if (station_cmd == nullptr) return false; + station_cmd->station_to_join = to_join; + if (test) { + return cmd->test().Succeeded(); } else { - this->station_to_join = INVALID_STATION; - _station_to_join = INVALID_STATION; + StationBuildTool::active_highlight = std::nullopt; + return cmd->post(callback); } - return true; - } + }; - return StationPreviewBase::HandleMousePress(); + auto ohl = handler->GetObjectHighlight(arg); + if (!ohl.has_value()) return false; + StationBuildTool::active_highlight = ohl; + auto area = ohl->GetArea(); + if (!area.has_value()) return false; + ShowSelectStationIfNeeded(area.value(), proc); + return true; } -void StationPreview::Execute() { - this->type->Execute(this->GetCommand(), this->remove_mode); + +// --- RailStationBuildTool --- + +up RailStationBuildTool::RemoveHandler::GetCommand(TileArea area) { + auto cmd = make_up( + area.tile, + area.CMGetEndTile(), + !citymania::_fn_mod + ); + cmd->with_error(STR_ERROR_CAN_T_REMOVE_PART_OF_STATION); + return cmd; } -void StationPreview::OnStationRemoved(const Station *station) { - if (this->station_to_join == station->index) this->station_to_join = INVALID_STATION; +bool RailStationBuildTool::RemoveHandler::Execute(TileArea area) { + auto cmd = this->GetCommand(area); + return cmd->post(&CcPlaySound_CONSTRUCTION_RAIL); } -void SetSelectedStationToJoin(StationID station_id) { - auto p = dynamic_cast(_ap.preview.get()); - if (p == nullptr) return; - p->selected_station_to_join = station_id; - UpdateActivePreview(); +up RailStationBuildTool::SizedPlacementHandler::GetCommand(TileIndex tile, StationID to_join) { + // TODO mostly same as DragNDropPlacement + auto cmd = make_up( + tile, + _cur_railtype, + _railstation.orientation, + _settings_client.gui.station_numtracks, + _settings_client.gui.station_platlength, + _railstation.station_class, + _railstation.station_type, + to_join, + true + ); + cmd->with_error(STR_ERROR_CAN_T_BUILD_RAILROAD_STATION); + return cmd; } -bool HandleStationPlacePushButton(Window *w, WidgetID widget, sp type) { - up preview; - if (citymania::UseImprovedStationJoin()) { - preview = make_up(type); +bool RailStationBuildTool::SizedPlacementHandler::Execute(TileIndex tile) { + return this->tool.ExecuteBuildCommand(this, &CcStation, tile); +} + +up RailStationBuildTool::DragNDropPlacementHandler::GetCommand(TileArea area, StationID to_join) { + uint numtracks = area.w; + uint platlength = area.h; + + if (_railstation.orientation == AXIS_X) Swap(numtracks, platlength); + + auto cmd = make_up( + area.tile, + _cur_railtype, + _railstation.orientation, + numtracks, + platlength, + _railstation.station_class, + _railstation.station_type, + to_join, + true + ); + cmd->with_error(STR_ERROR_CAN_T_BUILD_RAILROAD_STATION); + return cmd; +} + +bool RailStationBuildTool::DragNDropPlacementHandler::Execute(TileArea area) { + return this->tool.ExecuteBuildCommand(this, &CcStation, area); +} + +std::optional RailStationBuildTool::DragNDropPlacementHandler::GetObjectHighlight(TileArea area) { + return this->tool.GetStationObjectHighlight(area.tile, area.CMGetEndTile()); +} + +std::optional RailStationBuildTool::SizedPlacementHandler::GetObjectHighlight(TileIndex tile) { + return this->tool.GetStationObjectHighlight(tile, INVALID_TILE); +} + +RailStationBuildTool::RailStationBuildTool() : mode(Mode::SIZED) { + this->action = make_up>(SizedPlacementHandler(*this)); +} + +void RailStationBuildTool::Update(Point pt, TileIndex tile) { + Mode new_mode; + if (_remove_button_clicked) { + new_mode = Mode::REMOVE; + } else if (citymania::UseImprovedStationJoin() && _fn_mod) { + new_mode = Mode::SELECT; + } else if (_settings_client.gui.station_dragdrop) { + new_mode = Mode::DRAGDROP; } else { - preview = make_up(type); + new_mode = Mode::SIZED; } - return citymania::HandlePlacePushButton(w, widget, std::move(preview)); + if (new_mode != this->mode) { + switch (new_mode) { + case Mode::REMOVE: + this->action = make_up>(*this); + break; + case Mode::SELECT: + this->action = make_up>(*this); + break; + case Mode::DRAGDROP: + this->action = make_up>(*this); + break; + case Mode::SIZED: + this->action = make_up>(*this); + break; + default: + NOT_REACHED(); + } + this->mode = new_mode; + } + this->action->Update(pt, tile); } +std::optional RailStationBuildTool::GetStationObjectHighlight(TileIndex start_tile, TileIndex end_tile) const { + assert(IsValidTile(start_tile)); + assert(!IsValidTile(end_tile) || (TileX(start_tile) <= TileX(end_tile) && TileY(start_tile) <= TileY(end_tile))); + if (!IsValidTile(end_tile)) { + // Sized placement mode + if (_railstation.orientation == AXIS_X) + end_tile = TILE_ADDXY(start_tile, _settings_client.gui.station_platlength - 1, _settings_client.gui.station_numtracks - 1); + else + end_tile = TILE_ADDXY(start_tile, _settings_client.gui.station_numtracks - 1, _settings_client.gui.station_platlength - 1); + } else { + + } + return ObjectHighlight::make_rail_station(start_tile, end_tile, _railstation.orientation); +} + +CursorID RailStationBuildTool::GetCursor() { return SPR_CURSOR_RAIL_STATION; } + +// --- RoadStopBuildTool Handler Implementations --- + +up RoadStopBuildTool::RemoveHandler::GetCommand(TileArea area) { + auto cmd = make_up( + area.tile, + area.w, + area.h, + this->tool.stop_type, + _fn_mod + ); + auto rti = GetRoadTypeInfo(_cur_roadtype); + cmd->with_error(rti->strings.err_remove_station[this->tool.stop_type]); + return cmd; +} + +bool RoadStopBuildTool::RemoveHandler::Execute(TileArea area) { + auto cmd = this->GetCommand(area); + return cmd->post(&CcPlaySound_CONSTRUCTION_OTHER); +} + +up RoadStopBuildTool::DragNDropPlacementHandler::GetCommand(TileArea area, StationID to_join) { + DiagDirection ddir = this->tool.ddir; + bool drive_through = this->tool.ddir >= DIAGDIR_END; + if (drive_through) ddir = static_cast(this->tool.ddir - DIAGDIR_END); // Adjust picker result to actual direction. + + auto res = make_up( + area.tile, + area.w, + area.h, + this->tool.stop_type, + drive_through, + ddir, + _cur_roadtype, + _roadstop_gui_settings.roadstop_class, + _roadstop_gui_settings.roadstop_type, + to_join, + true + ); + + return res; +} + +bool RoadStopBuildTool::DragNDropPlacementHandler::Execute(TileArea area) { + return this->tool.ExecuteBuildCommand(this, &CcRoadStop, area); +} + +std::optional RoadStopBuildTool::DragNDropPlacementHandler::GetObjectHighlight(TileArea area) { + return ObjectHighlight::make_road_stop( + area.tile, + area.CMGetEndTile(), + _cur_roadtype, + this->tool.ddir, + this->tool.stop_type == ROADSTOP_TRUCK, + _roadstop_gui_settings.roadstop_class, + _roadstop_gui_settings.roadstop_type + ); +} + +// --- RoadStopBuildTool Implementation --- + +RoadStopBuildTool::RoadStopBuildTool(RoadStopType stop_type) : mode(Mode::DRAGDROP), stop_type(stop_type) +{ + this->action = make_up>(*this); +} + +void RoadStopBuildTool::Update(Point pt, TileIndex tile) { + Mode new_mode; + if (_remove_button_clicked) { + new_mode = Mode::REMOVE; + } else if (UseImprovedStationJoin() && _fn_mod) { + new_mode = Mode::SELECT; + } else { + new_mode = Mode::DRAGDROP; + } + + if (new_mode != this->mode) { + switch (new_mode) { + case Mode::REMOVE: + this->action = make_up>(*this); + break; + case Mode::SELECT: + this->action = make_up>(*this); + break; + case Mode::DRAGDROP: + this->action = make_up>(*this); + break; + } + this->mode = new_mode; + } + this->action->Update(pt, tile); + + this->ddir = DIAGDIR_NE; + auto area = this->action->GetArea(); + if (pt.x != -1 && this->mode == Mode::DRAGDROP && area.has_value()) { + auto ddir = _roadstop_gui_settings.orientation; + + if (ddir >= DIAGDIR_END && ddir < STATIONDIR_AUTO) { + // When placed on road autorotate anyway + if (ddir == STATIONDIR_X) { + if (!CheckDriveThroughRoadStopDirection(area.value(), ROAD_X)) + ddir = STATIONDIR_Y; + } else { + if (!CheckDriveThroughRoadStopDirection(area.value(), ROAD_Y)) + ddir = STATIONDIR_X; + } + } else if (ddir == STATIONDIR_AUTO) { + ddir = AddAutodetectionRotation(AutodetectRoadObjectDirection(tile, pt, _cur_roadtype)); + } else if (ddir == STATIONDIR_AUTO_XY) { + ddir = AddAutodetectionRotation(AutodetectDriveThroughRoadStopDirection(area.value(), pt, _cur_roadtype)); + } + this->ddir = ddir; + } +} + +CursorID RoadStopBuildTool::GetCursor() { + return this->stop_type == ROADSTOP_TRUCK ? SPR_CURSOR_TRUCK_STATION : SPR_CURSOR_BUS_STATION; +} + +// --- DockBuildTool Handler Implementations --- + +// RemoveHandler +up DockBuildTool::RemoveHandler::GetCommand(TileArea area) { + // TODO: Implement dock removal command if available + return nullptr; +} + +bool DockBuildTool::RemoveHandler::Execute(TileArea area) { + // TODO: Implement dock removal execution if available + return false; +} + +// SizedPlacementHandler +up DockBuildTool::SizedPlacementHandler::GetCommand(TileIndex tile, StationID to_join) { + return make_up( + tile, + to_join, + true + ); +} + +bool DockBuildTool::SizedPlacementHandler::Execute(TileIndex tile) { + return this->tool.ExecuteBuildCommand(this, &CcBuildDocks, tile); +} + +std::optional DockBuildTool::SizedPlacementHandler::GetObjectHighlight(TileIndex tile) { + return ObjectHighlight::make_dock(tile, this->tool.ddir); +} + +// --- DockBuildTool Implementation --- + +DockBuildTool::DockBuildTool() : mode(Mode::SIZED) { + this->action = make_up>(*this); +} + +void DockBuildTool::Update(Point pt, TileIndex tile) { + Mode new_mode; + if (_remove_button_clicked) { + new_mode = Mode::REMOVE; + } else if (citymania::UseImprovedStationJoin() && _fn_mod) { + new_mode = Mode::SELECT; + } else { + new_mode = Mode::SIZED; + } + if (new_mode != this->mode) { + switch (new_mode) { + case Mode::REMOVE: + this->action = make_up>(*this); + break; + case Mode::SELECT: + this->action = make_up>(*this); + break; + case Mode::SIZED: + this->action = make_up>(*this); + break; + default: + NOT_REACHED(); + } + this->mode = new_mode; + } + this->action->Update(pt, tile); + this->ddir = DIAGDIR_SE; + if (pt.x != -1 && this->mode == Mode::SIZED) { + auto slope_dir = GetInclinedSlopeDirection(GetTileSlope(tile)); + if (slope_dir != INVALID_DIAGDIR) + this->ddir = ReverseDiagDir(slope_dir); + } + } + +CursorID DockBuildTool::GetCursor() { + return SPR_CURSOR_DOCK; +} + +// --- AirportBuildTool Handler Implementations --- + +// RemoveHandler +up AirportBuildTool::RemoveHandler::GetCommand(TileArea area) { + // TODO: Implement aiport removal command if available + return nullptr; +} + +bool AirportBuildTool::RemoveHandler::Execute(TileArea area) { + // TODO: Implement airport removal execution if available + return false; +} + +// SizedPlacementHandler +up AirportBuildTool::SizedPlacementHandler::GetCommand(TileIndex tile, StationID to_join) { + // STR_ERROR_CAN_T_BUILD_AIRPORT_HERE, + byte airport_type = AirportClass::Get(_selected_airport_class)->GetSpec(_selected_airport_index)->GetIndex(); + byte layout = _selected_airport_layout; + return make_up( + tile, + airport_type, + layout, + StationBuildTool::station_to_join, + true + ); +} + +bool AirportBuildTool::SizedPlacementHandler::Execute(TileIndex tile) { + this->tool.ExecuteBuildCommand(this, &CcBuildAirport, tile); +} + +std::optional AirportBuildTool::SizedPlacementHandler::GetObjectHighlight(TileIndex tile) { + byte airport_type = AirportClass::Get(_selected_airport_class)->GetSpec(_selected_airport_index)->GetIndex(); + byte layout = _selected_airport_layout; + return ObjectHighlight::make_airport(tile, airport_type, layout); +} + +std::pair AirportBuildTool::SizedPlacementHandler::GetCatchmentParams() { + auto rad = AirportClass::Get(_selected_airport_class)->GetSpec(_selected_airport_index)->catchment; + return {SCT_ALL, rad}; +} + + +// --- AirportBuildTool Implementation --- + +AirportBuildTool::AirportBuildTool() : mode(Mode::SIZED) { + this->action = make_up>(*this); +} + +void AirportBuildTool::Update(Point pt, TileIndex tile) { + Mode new_mode; + if (_remove_button_clicked) { + new_mode = Mode::REMOVE; + } else if (citymania::UseImprovedStationJoin() && _fn_mod) { + new_mode = Mode::SELECT; + } else { + new_mode = Mode::SIZED; + } + if (new_mode != this->mode) { + switch (new_mode) { + case Mode::REMOVE: + this->action = make_up>(*this); + break; + case Mode::SELECT: + this->action = make_up>(*this); + break; + case Mode::SIZED: + this->action = make_up>(*this); + break; + default: + NOT_REACHED(); + } + this->mode = new_mode; + } + this->action->Update(pt, tile); +} + +CursorID AirportBuildTool::GetCursor() { + return SPR_CURSOR_AIRPORT; +} + +// --- Explicit template instantiations for handlers --- +template class StationSelectAction; + +template class DragNDropPlacementAction; +template class RemoveAction; +template class SizedPlacementAction; + +template class RemoveAction; +template class DragNDropPlacementAction; + +template class RemoveAction; +template class SizedPlacementAction; + + } // namespace citymania diff --git a/src/citymania/cm_station_gui.hpp b/src/citymania/cm_station_gui.hpp index 05f80d8992..a2930df0fc 100644 --- a/src/citymania/cm_station_gui.hpp +++ b/src/citymania/cm_station_gui.hpp @@ -1,6 +1,7 @@ #ifndef CM_STATION_GUI_HPP #define CM_STATION_GUI_HPP +#include "cm_command_type.hpp" #include "cm_highlight_type.hpp" #include "../core/geometry_type.hpp" @@ -9,6 +10,9 @@ #include "../station_gui.h" #include "../station_type.h" +#include +#include + namespace citymania { const DiagDirection DEPOTDIR_AUTO = DIAGDIR_END; @@ -26,29 +30,15 @@ struct RailStationGUISettings { uint16_t station_count; ///< Number of custom stations (if newstations is \c true ) }; -enum class StationBuildingStatus { - IMPOSSIBLE = 0, - QUERY = 1, - JOIN = 2, - NEW = 3, -}; - -// void SetStationBiildingStatus(StationBuildingStatus status); // void SetStationTileSelectSize(int w, int h, int catchment); bool UseImprovedStationJoin(); void OnStationTileSetChange(const Station *station, bool adding, StationType type); void OnStationPartBuilt(const Station *station); void OnStationRemoved(const Station *station); -void PlaceRoadStop(TileIndex start_tile, TileIndex end_tile, RoadStopType stop_type, bool adjacent, RoadType rt, StringID err_msg); -void HandleStationPlacement(TileIndex start, TileIndex end); -void PlaceRail_Station(TileIndex tile); -void PlaceDock(TileIndex tile, TileIndex tile_to); -void PlaceAirport(TileIndex tile); -void SelectStationToJoin(const Station *station); +// void SelectStationToJoin(const Station *station); // const Station *GetStationToJoin(); void MarkCoverageHighlightDirty(); -bool CheckRedrawStationCoverage(); void AbortStationPlacement(); std::optional GetStationCoverageAreaText(TileIndex tile, int w, int h, int rad, StationCoverageType sct, bool supplies); @@ -58,6 +48,7 @@ DiagDirection AutodetectRoadObjectDirection(TileIndex tile, Point pt, RoadType r DiagDirection AutodetectDriveThroughRoadStopDirection(TileArea area, Point pt, RoadType roadtype); DiagDirection AutodetectRailObjectDirection(TileIndex tile, Point pt); void SetSelectedStationToJoin(StationID station_id); +void ResetJoinStationHighlight(); struct OverlayParams { @@ -66,144 +57,305 @@ struct OverlayParams { StationCoverageType coverage_type; }; - -class PreviewStationType { +// Remove action classes +class RemoveHandler { public: + virtual ~RemoveHandler() = default; + virtual up GetCommand(TileArea area) = 0; + virtual bool Execute(TileArea area) = 0; +}; +template +concept ImplementsRemoveHandler = std::derived_from; + +template +class RemoveAction : public Action { +private: + Handler handler; TileIndex start_tile = INVALID_TILE; TileIndex cur_tile = INVALID_TILE; - - virtual ~PreviewStationType() {}; - - TileIndex GetStartTile() const { return start_tile == INVALID_TILE ? cur_tile : start_tile; } - virtual bool IsDragDrop() const { return true; }; - virtual CursorID GetCursor() const =0; - virtual TileArea GetArea(bool remove_mode) const =0; - virtual void Update(Point /* pt */, TileIndex /* tile */) {}; - virtual up GetCommand(bool adjacent, StationID join_to) const =0; - virtual up GetRemoveCommand() const =0; - virtual void AddPreviewTiles(HighlightMap &hlmap, SpriteID palette) const =0; - virtual bool Execute(up cmd, bool remove_mode) const =0; - virtual OverlayParams GetOverlayParams() const =0; -}; - -class RailStationPreview : public PreviewStationType { public: - virtual ~RailStationPreview() {}; - - // RailPreviewStation(RailStationGUISettings &settings) :settings{settings} {} - bool IsDragDrop() const override; - CursorID GetCursor() const override; - TileArea GetArea(bool remove_mode) const override; - up GetCommand(bool adjacent, StationID join_to) const override; - up GetRemoveCommand() const override; - void AddPreviewTiles(HighlightMap &hlmap, SpriteID palette) const override; - bool Execute(up cmd, bool remove_mode) const override; - OverlayParams GetOverlayParams() const override; -}; - -class RoadStationPreview : public PreviewStationType { -protected: - DiagDirection ddir; - RoadStopType stop_type; - -public: - RoadStationPreview(RoadStopType stop_type) :stop_type{stop_type} {} - virtual ~RoadStationPreview() {}; - - bool IsDragDrop() const override; - CursorID GetCursor() const override; - TileArea GetArea(bool remove_mode) const override; + RemoveAction(const Handler &handler) : handler{handler} {} + ~RemoveAction() override = default; void Update(Point pt, TileIndex tile) override; - up GetCommand(bool adjacent, StationID join_to) const override; - up GetRemoveCommand() const override; - void AddPreviewTiles(HighlightMap &hlmap, SpriteID palette) const override; - bool Execute(up cmd, bool remove_mode) const override; - OverlayParams GetOverlayParams() const override; + std::optional GetArea() const override; + bool HandleMousePress() override; + void HandleMouseRelease() override; + ToolGUIInfo GetGUIInfo() override; + void OnStationRemoved(const Station *) override; }; -class DockPreview : public PreviewStationType { -protected: - DiagDirection ddir; +// StationSelect classes +class StationSelectHandler { public: - DockPreview() {} - virtual ~DockPreview() {}; - - bool IsDragDrop() const override; - CursorID GetCursor() const override; - TileArea GetArea(bool remove_mode) const override; - void Update(Point pt, TileIndex tile) override; - up GetCommand(bool adjacent, StationID join_to) const override; - up GetRemoveCommand() const override; - void AddPreviewTiles(HighlightMap &hlmap, SpriteID palette) const override; - bool Execute(up cmd, bool remove_mode) const override; - OverlayParams GetOverlayParams() const override; + virtual ~StationSelectHandler() = default; + virtual void SelectStationToJoin(StationID station_id) = 0; }; +template +concept ImplementsStationSelectHandler = std::derived_from; -class StationPreviewBase : public Preview { -protected: - sp type; - bool remove_mode = false; - bool keep_rail = true; // whether to keep rail in remove mode - StationID station_to_join = INVALID_STATION; - bool adjacent_stations = false; - bool show_coverage = true; - - void AddAreaTiles(HighlightMap &hlmap, bool add_current, bool show_join_area); - virtual void Execute() = 0; - up GetCommand(bool adjacent, StationID join_to); - void AddStationPreview(HighlightMap &hlmap, SpriteID palette); - +template +class StationSelectAction : public Action { +private: + Handler handler; + TileIndex cur_tile = INVALID_TILE; + StationID selected_station = INVALID_STATION; public: - StationPreviewBase(sp type) :type{type} {}; - CursorID GetCursor() override { return this->type->GetCursor(); }; + StationSelectAction(const Handler &handler) : handler{handler} {} + ~StationSelectAction() override = default; void Update(Point pt, TileIndex tile) override; bool HandleMousePress() override; void HandleMouseRelease() override; - std::optional GetStationCoverageAreaText(int rad, StationCoverageType sct, bool supplies); - std::vector> GetOverlayData(); -}; - - -class VanillaStationPreview : public StationPreviewBase { -protected: - SpriteID palette; - - void Execute() override; - -public: - StationID selected_station_to_join = INVALID_STATION; - - VanillaStationPreview(sp type) :StationPreviewBase{type} {}; - virtual ~VanillaStationPreview() {}; - void Update(Point pt, TileIndex tile) override; - - HighlightMap GetHighlightMap() override; + ToolGUIInfo GetGUIInfo() override; void OnStationRemoved(const Station *station) override; }; - -class StationPreview : public StationPreviewBase { -protected: - bool select_mode = false; - - void Execute() override; - up GetCommand(); - +// PlacementAction +class PlacementAction : public Action { public: - StationPreview(sp type); - virtual ~StationPreview(); + ~PlacementAction() override = default; + ToolGUIInfo PrepareGUIInfo(std::optional ohl, up cmd, StationCoverageType sct, uint rad); +}; + +// SizedPlacement classes +class SizedPlacementHandler { +public: + virtual ~SizedPlacementHandler() = default; + virtual up GetCommand(TileIndex tile, StationID to_join) = 0; + virtual bool Execute(TileIndex tile) = 0; + virtual std::optional GetObjectHighlight(TileIndex tile) = 0; + virtual std::pair GetCatchmentParams() = 0; +}; +template +concept ImplementsSizedPlacementHandler = std::derived_from; + +template +class SizedPlacementAction : public PlacementAction { +private: + Handler handler; + TileIndex cur_tile = INVALID_TILE; +public: + SizedPlacementAction(const Handler &handler) : handler{handler} {} + ~SizedPlacementAction() override = default; void Update(Point pt, TileIndex tile) override; + std::optional GetArea() const override { return std::nullopt; }; bool HandleMousePress() override; - - HighlightMap GetHighlightMap() override; - void OnStationRemoved(const Station *station) override; + void HandleMouseRelease() override; + ToolGUIInfo GetGUIInfo() override; + void OnStationRemoved(const Station *) override; }; -// SPR_CURSOR_BUS_STATION SPR_CURSOR_TRUCK_STATION +// DragNDropPlacement classes +class DragNDropPlacementHandler { +public: + virtual ~DragNDropPlacementHandler() = default; + virtual up GetCommand(TileArea area, StationID to_join) = 0; + virtual bool Execute(TileArea area) = 0; + virtual std::optional GetObjectHighlight(TileArea area) = 0; + virtual std::pair GetCatchmentParams() = 0; +}; +template +concept ImplementsDragNDropPlacementHandler = std::derived_from; -bool HandleStationPlacePushButton(Window *w, WidgetID widget, sp type); +template +class DragNDropPlacementAction : public PlacementAction { +private: + TileIndex start_tile = INVALID_TILE; + TileIndex cur_tile = INVALID_TILE; + Handler handler; +public: + DragNDropPlacementAction(const Handler &handler) :handler{handler} {}; + ~DragNDropPlacementAction() override = default; + void Update(Point pt, TileIndex tile) override; + std::optional GetArea() const override; + bool HandleMousePress() override; + void HandleMouseRelease() override; + ToolGUIInfo GetGUIInfo() override; + void OnStationRemoved(const Station *) override; +}; +class StationBuildTool : public Tool { +public: + static StationID station_to_join; + static StationID current_selected_station; + static std::optional active_highlight; + + class StationSelectHandler : public citymania::StationSelectHandler { + public: + StationBuildTool &tool; + StationSelectHandler(StationBuildTool &tool) : tool(tool) {} + ~StationSelectHandler() {} + void SelectStationToJoin(StationID station_id) override { this->tool.SelectStationToJoin(station_id); }; + }; + + ~StationBuildTool() override = default; + void SelectStationToJoin(StationID station_id) { StationBuildTool::station_to_join = station_id; }; + ToolGUIInfo GetGUIInfo() override { + if (!this->action) return {}; + return this->action->GetGUIInfo(); + } + void OnStationRemoved(const Station *station) override { + if (this->action) this->action->OnStationRemoved(station); + } +protected: + template + bool ExecuteBuildCommand(Thandler *handler, Tcallback callback, Targ arg); +}; + +// RailStationBuildTool +class RailStationBuildTool : public StationBuildTool { +private: + class RemoveHandler : public citymania::RemoveHandler { + public: + RailStationBuildTool &tool; + RemoveHandler(RailStationBuildTool &tool) : tool(tool) {} + ~RemoveHandler() override = default; + up GetCommand(TileArea area) override; + bool Execute(TileArea area) override; + }; + + class SizedPlacementHandler : public citymania::SizedPlacementHandler { + public: + RailStationBuildTool &tool; + SizedPlacementHandler(RailStationBuildTool &tool) : tool(tool) {} + ~SizedPlacementHandler() override = default; + up GetCommand(TileIndex tile, StationID to_join) override; + bool Execute(TileIndex tile) override; + std::optional GetObjectHighlight(TileIndex tile) override; + std::pair GetCatchmentParams() override { return {this->tool.GetCatchmentParams()}; }; + }; + + class DragNDropPlacementHandler: public citymania::DragNDropPlacementHandler { + public: + RailStationBuildTool &tool; + DragNDropPlacementHandler(RailStationBuildTool &tool) :tool{tool} {} + ~DragNDropPlacementHandler() override = default; + up GetCommand(TileArea area, StationID to_join) override; + bool Execute(TileArea area) override; + std::optional GetObjectHighlight(TileArea area) override; + std::pair GetCatchmentParams() override { return {this->tool.GetCatchmentParams()}; }; + }; + + std::optional GetStationObjectHighlight(TileIndex start_tile, TileIndex end_tile) const; + std::pair GetCatchmentParams() { return {SCT_ALL, CA_TRAIN}; }; + +public: + RailStationBuildTool(); + ~RailStationBuildTool() override = default; + void Update(Point pt, TileIndex tile) override; + CursorID GetCursor() override; +private: + enum class Mode { REMOVE, SELECT, DRAGDROP, SIZED }; + Mode mode; +}; + +// RoadStopBuildTool +class RoadStopBuildTool : public StationBuildTool { +private: + class RemoveHandler : public citymania::RemoveHandler { + public: + RoadStopBuildTool &tool; + RemoveHandler(RoadStopBuildTool &tool) : tool(tool) {} + ~RemoveHandler() override = default; + up GetCommand(TileArea area) override; + bool Execute(TileArea area) override; + }; + + class DragNDropPlacementHandler: public citymania::DragNDropPlacementHandler { + public: + RoadStopBuildTool &tool; + DragNDropPlacementHandler(RoadStopBuildTool &tool) :tool{tool} {} + ~DragNDropPlacementHandler() override = default; + up GetCommand(TileArea area, StationID to_join) override; + bool Execute(TileArea area) override; + std::optional GetObjectHighlight(TileArea area) override; + std::pair GetCatchmentParams() override { return this->tool.GetCatchmentParams(); }; + }; + + std::pair GetCatchmentParams() { + if (this->stop_type == ROADSTOP_BUS) return {SCT_PASSENGERS_ONLY, CA_BUS}; + else return {SCT_NON_PASSENGERS_ONLY, CA_TRUCK}; + }; +public: + RoadStopBuildTool(RoadStopType stop_type); + ~RoadStopBuildTool() override = default; + void Update(Point pt, TileIndex tile) override; + CursorID GetCursor() override; +private: + enum class Mode { REMOVE, SELECT, DRAGDROP }; + Mode mode; + RoadStopType stop_type; + DiagDirection ddir = DIAGDIR_NE; +}; + +// --- DockBuildTool --- +class DockBuildTool : public StationBuildTool { +private: + class RemoveHandler : public citymania::RemoveHandler { + public: + DockBuildTool &tool; + RemoveHandler(DockBuildTool &tool) : tool(tool) {} + ~RemoveHandler() override = default; + up GetCommand(TileArea area) override; + bool Execute(TileArea area) override; + }; + + class SizedPlacementHandler : public citymania::SizedPlacementHandler { + public: + DockBuildTool &tool; + SizedPlacementHandler(DockBuildTool &tool) : tool(tool) {} + ~SizedPlacementHandler() override = default; + up GetCommand(TileIndex tile, StationID to_join) override; + bool Execute(TileIndex tile) override; + std::optional GetObjectHighlight(TileIndex tile) override; + std::pair GetCatchmentParams() override { return {SCT_ALL, CA_DOCK}; }; + }; + +public: + DockBuildTool(); + ~DockBuildTool() override = default; + void Update(Point pt, TileIndex tile) override; + CursorID GetCursor() override; +private: + enum class Mode { REMOVE, SELECT, SIZED }; + Mode mode; + DiagDirection ddir; +}; + +// --- AirportBuildTool --- +class AirportBuildTool : public StationBuildTool { +private: + class RemoveHandler : public citymania::RemoveHandler { + public: + AirportBuildTool &tool; + RemoveHandler(AirportBuildTool &tool) : tool(tool) {} + ~RemoveHandler() override = default; + up GetCommand(TileArea area) override; + bool Execute(TileArea area) override; + }; + + class SizedPlacementHandler : public citymania::SizedPlacementHandler { + public: + AirportBuildTool &tool; + SizedPlacementHandler(AirportBuildTool &tool) : tool(tool) {} + ~SizedPlacementHandler() override = default; + up GetCommand(TileIndex tile, StationID to_join) override; + bool Execute(TileIndex tile) override; + std::optional GetObjectHighlight(TileIndex tile) override; + std::pair GetCatchmentParams() override; + }; + +public: + AirportBuildTool(); + ~AirportBuildTool() override = default; + void Update(Point pt, TileIndex tile) override; + CursorID GetCursor() override; +private: + enum class Mode { REMOVE, SELECT, SIZED }; + Mode mode; +}; + +ToolGUIInfo GetSelectedStationGUIInfo(); } // namespace citymania diff --git a/src/citymania/cm_type.hpp b/src/citymania/cm_type.hpp index 85a4ecae16..4ee075e909 100644 --- a/src/citymania/cm_type.hpp +++ b/src/citymania/cm_type.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -72,6 +73,56 @@ enum class ControllerType: uint8_t { TOWN_DEFENCE = 6, }; +// Some utility funcitons for strings +namespace string { + +template +static inline std::string join(T strings, std::string separator) { + // TODO add map function (can be used in ListGameModeCodes)? + std::ostringstream res; + bool first = true; + for (auto s: strings) { + if (!first)res << separator; + res << s; + first = false; + } + return res.str(); +} + +static inline void iltrim(std::string &s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int c) { + return !std::isspace(c); + })); +} + +static inline void irtrim(std::string &s) { + s.erase(std::find_if(s.rbegin(), s.rend(), [](int c) { + return !std::isspace(c); + }).base(), s.end()); +} + +static inline void itrim(std::string &s) { + iltrim(s); + irtrim(s); +} + +static inline std::string ltrim(std::string s) { + iltrim(s); + return s; +} + +static inline std::string rtrim(std::string s) { + irtrim(s); + return s; +} + +static inline std::string trim(std::string s) { + itrim(s); + return s; +} + +}; // namespace string + } // namespace citymania diff --git a/src/citymania/generated/cm_gen_commands.hpp b/src/citymania/generated/cm_gen_commands.hpp index 9f48e08f54..b43d4af057 100644 --- a/src/citymania/generated/cm_gen_commands.hpp +++ b/src/citymania/generated/cm_gen_commands.hpp @@ -237,16 +237,14 @@ public: Commands get_command() override; }; -class BuildAirport: public Command { +class BuildAirport: public StationBuildCommand { public: TileIndex tile; byte airport_type; byte layout; - StationID station_to_join; - bool adjacent; BuildAirport(TileIndex tile, byte airport_type, byte layout, StationID station_to_join, bool adjacent) - :tile{tile}, airport_type{airport_type}, layout{layout}, station_to_join{station_to_join}, adjacent{adjacent} {} + :StationBuildCommand{station_to_join, adjacent}, tile{tile}, airport_type{airport_type}, layout{layout} {} ~BuildAirport() override {} bool _post(::CommandCallback * callback) override; @@ -254,14 +252,12 @@ public: Commands get_command() override; }; -class BuildDock: public Command { +class BuildDock: public StationBuildCommand { public: TileIndex tile; - StationID station_to_join; - bool adjacent; BuildDock(TileIndex tile, StationID station_to_join, bool adjacent) - :tile{tile}, station_to_join{station_to_join}, adjacent{adjacent} {} + :StationBuildCommand{station_to_join, adjacent}, tile{tile} {} ~BuildDock() override {} bool _post(::CommandCallback * callback) override; @@ -269,7 +265,7 @@ public: Commands get_command() override; }; -class BuildRailStation: public Command { +class BuildRailStation: public StationBuildCommand { public: TileIndex tile_org; RailType rt; @@ -278,11 +274,9 @@ public: byte plat_len; StationClassID spec_class; uint16_t spec_index; - StationID station_to_join; - bool adjacent; BuildRailStation(TileIndex tile_org, RailType rt, Axis axis, byte numtracks, byte plat_len, StationClassID spec_class, uint16_t spec_index, StationID station_to_join, bool adjacent) - :tile_org{tile_org}, rt{rt}, axis{axis}, numtracks{numtracks}, plat_len{plat_len}, spec_class{spec_class}, spec_index{spec_index}, station_to_join{station_to_join}, adjacent{adjacent} {} + :StationBuildCommand{station_to_join, adjacent}, tile_org{tile_org}, rt{rt}, axis{axis}, numtracks{numtracks}, plat_len{plat_len}, spec_class{spec_class}, spec_index{spec_index} {} ~BuildRailStation() override {} bool _post(::CommandCallback * callback) override; @@ -305,7 +299,7 @@ public: Commands get_command() override; }; -class BuildRoadStop: public Command { +class BuildRoadStop: public StationBuildCommand { public: TileIndex tile; uint8_t width; @@ -316,11 +310,9 @@ public: RoadType rt; RoadStopClassID spec_class; uint16_t spec_index; - StationID station_to_join; - bool adjacent; BuildRoadStop(TileIndex tile, uint8_t width, uint8_t length, RoadStopType stop_type, bool is_drive_through, DiagDirection ddir, RoadType rt, RoadStopClassID spec_class, uint16_t spec_index, StationID station_to_join, bool adjacent) - :tile{tile}, width{width}, length{length}, stop_type{stop_type}, is_drive_through{is_drive_through}, ddir{ddir}, rt{rt}, spec_class{spec_class}, spec_index{spec_index}, station_to_join{station_to_join}, adjacent{adjacent} {} + :StationBuildCommand{station_to_join, adjacent}, tile{tile}, width{width}, length{length}, stop_type{stop_type}, is_drive_through{is_drive_through}, ddir{ddir}, rt{rt}, spec_class{spec_class}, spec_index{spec_index} {} ~BuildRoadStop() override {} bool _post(::CommandCallback * callback) override; diff --git a/src/dock_gui.cpp b/src/dock_gui.cpp index 428c62e7d6..46b69e9676 100644 --- a/src/dock_gui.cpp +++ b/src/dock_gui.cpp @@ -38,10 +38,12 @@ #include "table/sprites.h" #include "table/strings.h" +#include "citymania/cm_highlight.hpp" #include "citymania/cm_hotkeys.hpp" #include "citymania/cm_station_gui.hpp" #include "safeguards.h" +#include static void ShowBuildDockStationPicker(Window *parent); static void ShowBuildDocksDepotPicker(Window *parent); @@ -171,8 +173,8 @@ struct BuildDocksToolbarWindow : Window { break; case WID_DT_STATION: // Build station button - // if (HandlePlacePushButton(this, WID_DT_STATION, SPR_CURSOR_DOCK, HT_SPECIAL, CM_DDSP_BUILD_DOCK)) ShowBuildDockStationPicker(this); - if (citymania::HandleStationPlacePushButton(this, WID_DT_STATION, std::make_shared())) { + // if (HandlePlacePushButton(this, WID_DT_STATION, SPR_CURSOR_DOCK, HT_SPECIAL, CM_DDSP_BUILD_DOCK)) { + if (citymania::HandlePlacePushButton(this, WID_DT_STATION, std::make_unique())) { ShowBuildDockStationPicker(this); } @@ -218,14 +220,13 @@ struct BuildDocksToolbarWindow : Window { break; case WID_DT_STATION: { // Build station button + NOT_REACHED(); // CityMania uses tools + /* Determine the watery part of the dock. */ + /* DiagDirection dir = GetInclinedSlopeDirection(GetTileSlope(tile)); TileIndex tile_to = (dir != INVALID_DIAGDIR ? TileAddByDiagDir(tile, ReverseDiagDir(dir)) : tile); - if (citymania::UseImprovedStationJoin()) { - citymania::PlaceDock(tile, tile_to); - break; - } bool adjacent = citymania::_fn_mod; auto proc = [=](bool test, StationID to_join) -> bool { @@ -237,7 +238,7 @@ struct BuildDocksToolbarWindow : Window { }; ShowSelectStationIfNeeded(TileArea(tile, tile_to), proc); - break; + break; */ } case WID_DT_BUOY: // Build buoy button @@ -290,7 +291,7 @@ struct BuildDocksToolbarWindow : Window { CloseWindowById(WC_BUILD_DEPOT, TRANSPORT_WATER); CloseWindowById(WC_SELECT_STATION, 0); CloseWindowByClass(WC_BUILD_BRIDGE); - + citymania::AbortStationPlacement(); } diff --git a/src/lang/english.txt b/src/lang/english.txt index c9379d9b08..b504386f44 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -6298,13 +6298,26 @@ CM_BUILDING_PREVIEW_COST_ENOUGH :Cost CM_STR_NO_BLUEPRINT_IN_SLOT :{WHITE}No blueprint in slot {NUM} CM_STR_ABOUT_MENU_LOGIN_WINDOW :CityMania server login -CM_STR_BUILD_INFO_OVERLAY_COST_OK :{WHITE}Cost: {CURRENCY_LONG} +CM_STR_BUILD_INFO_OVERLAY_COST_OK :{GREEN}Cost: {CURRENCY_LONG} CM_STR_BUILD_INFO_OVERLAY_COST_NO_MONEY :{RED}Cost: {CURRENCY_LONG} -CM_STR_BUILD_INFO_OVERLAY_STATION_ACCEPTS :{WHITE}Accepts: +CM_STR_BULID_INFO_OVERLAY_ACCEPTS_CARGO_PARTIAL :{SILVER}{CARGO_LIST}({NUM}/8) +CM_STR_BULID_INFO_OVERLAY_ACCEPTS_CARGO_FULL :{GOLD}{CARGO_LIST}({NUM}/8) +CM_STR_BULID_INFO_OVERLAY_ACCEPTS_CARGO :{GOLD}{CARGO_LIST} +CM_STR_BULID_INFO_OVERLAY_ACCEPTS :{WHITE}Accepts: {GOLD}{RAW_STRING} CM_STR_BUILD_INFO_OVERLAY_STATION_SUPPLIES :{WHITE}Supplies: -CM_STR_BUILD_INFO_OVERLAY_STATION_CARGO :{WHITE}{CARGO_LONG} -CM_STR_BUILD_INFO_OVERLAY_ERROR :{RED}{STRING} +CM_STR_BUILD_INFO_OVERLAY_STATION_CARGO :{GOLD}{CARGO_LONG} +CM_STR_BUILD_INFO_OVERLAY_ERROR :{RED}Error: {STRING} CM_STR_BUILD_INFO_OVERLAY_ERROR_UNKNOWN :{RED}Unknown Error! +CM_STR_BULID_INFO_OVERLAY_JOIN_STATION :{LTBLUE}Join {STATION} +CM_STR_BULID_INFO_OVERLAY_NEW_STATION :{GOLD}New station +CM_STR_BULID_INFO_OVERLAY_TOWN_NONE :{WHITE}Town: {GOLD}None +CM_STR_BULID_INFO_OVERLAY_TOWN_ALLOWS :{WHITE}Town: {GREEN}{TOWN}[{NUM}], {STRING} +CM_STR_BULID_INFO_OVERLAY_TOWN_DENIES :{WHITE}Town: {RED}{TOWN}[{NUM}], {STRING} +CM_STR_BULID_INFO_OVERLAY_TOWN_NO_ADS :{WHITE}no ad zone +CM_STR_BULID_INFO_OVERLAY_TOWN_L_ADS :{GOLD}large {WHITE}ad zone +CM_STR_BULID_INFO_OVERLAY_TOWN_M_ADS :{GOLD}medium {WHITE}ad zone +CM_STR_BULID_INFO_OVERLAY_TOWN_S_ADS :{GOLD}small {WHITE}ad zone +CM_STR_BULID_INFO_OVERLAY_STATION_SIZE :{WHITE}Size: {GOLD}{NUM}x{NUM} CM_STR_VEHICLE_INFO_BUILT_VALUE_WITH_ID :{LTBLUE}{ENGINE} {BLACK}Built: {LTBLUE}{NUM}{BLACK} Value: {LTBLUE}{CURRENCY_LONG}{BLACK} ID: {LTBLUE}{NUM} @@ -6316,3 +6329,4 @@ CM_STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN :{BLACK}Show CM_STR_SHOW_HIDDEN_ENGINES_VEHICLE_ROAD_VEHICLE :{BLACK}Show hidden ({NUM}) CM_STR_SHOW_HIDDEN_ENGINES_VEHICLE_SHIP :{BLACK}Show hidden ({NUM}) CM_STR_SHOW_HIDDEN_ENGINES_VEHICLE_AIRCRAFT :{BLACK}Show hidden ({NUM}) + diff --git a/src/main_gui.cpp b/src/main_gui.cpp index 24d948a645..8a30cec488 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp @@ -81,13 +81,6 @@ bool HandlePlacePushButton(Window *w, WidgetID widget, CursorID cursor, HighLigh } SetObjectToPlace(cursor, PAL_NONE, mode, w->window_class, w->window_number, cm_process); - // if (cm_process == DDSP_BUILD_STATION) { - // if (citymania::UseImprovedStationJoin()) { - // citymania::SetActivePreview(std::make_unique()); - // } else { - // citymania::SetActivePreview(std::make_unique()); - // } - // } w->LowerWidget(widget); return true; } diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp index 7a936b7c7f..15db979b90 100644 --- a/src/rail_gui.cpp +++ b/src/rail_gui.cpp @@ -202,6 +202,7 @@ void CcStation(Commands, const CommandCost &result, TileIndex tile) */ static void PlaceRail_Station(TileIndex tile) { + NOT_REACHED(); // CityMania uses tools if (_remove_button_clicked) { VpStartPlaceSizing(tile, VPM_X_AND_Y_LIMITED, DDSP_BUILD_STATION); VpSetPlaceSizingLimit(-1); @@ -209,10 +210,6 @@ static void PlaceRail_Station(TileIndex tile) VpStartPlaceSizing(tile, VPM_X_AND_Y_LIMITED, DDSP_BUILD_STATION); VpSetPlaceSizingLimit(_settings_game.station.station_spread); } else { - if (citymania::UseImprovedStationJoin()) { - citymania::PlaceRail_Station(tile); - return; - } int w = _settings_client.gui.station_numtracks; int h = _settings_client.gui.station_platlength; if (!_railstation.orientation) Swap(w, h); @@ -680,13 +677,13 @@ struct BuildRailToolbarWindow : Window { if (was_open) ResetObjectToPlace(); if (!was_open || dragdrop != _settings_client.gui.station_dragdrop) { _settings_client.gui.station_dragdrop = dragdrop; - if (citymania::HandleStationPlacePushButton(this, WID_RAT_BUILD_STATION, std::make_shared())) + if (citymania::HandlePlacePushButton(this, WID_RAT_BUILD_STATION, std::make_unique())) // if (HandlePlacePushButton(this, WID_RAT_BUILD_STATION, SPR_CURSOR_BRIDGE, HT_RECT, DDSP_BUILD_STATION)) ShowStationBuilder(this); } this->last_user_action = WID_RAT_BUILD_STATION; } else { /* button */ - if (citymania::HandleStationPlacePushButton(this, WID_RAT_BUILD_STATION, std::make_shared())) { + if (citymania::HandlePlacePushButton(this, WID_RAT_BUILD_STATION, std::make_unique())) { // if (HandlePlacePushButton(this, WID_RAT_BUILD_STATION, SPR_CURSOR_BRIDGE, HT_RECT, DDSP_BUILD_STATION)) { ShowStationBuilder(this); this->last_user_action = WID_RAT_BUILD_STATION; @@ -1043,7 +1040,7 @@ struct BuildRailToolbarWindow : Window { Hotkey((uint16)0, "cm_blueprint_save_7", HOTKEY_BLUEPRINT_SAVE + 7), Hotkey((uint16)0, "cm_blueprint_save_8", HOTKEY_BLUEPRINT_SAVE + 8), Hotkey((uint16)0, "cm_blueprint_save_9", HOTKEY_BLUEPRINT_SAVE + 9), - Hotkey(CM_WKC_MOUSE_MIDDLE, "cm_blueprint_rotate", HOTKEY_BLUEPRINT_ROTATE), + Hotkey(CM_WKC_MOUSE_MIDDLE, "cm_blueprint_rotate", HOTKEY_BLUEPRINT_ROTATE), }, RailToolbarGlobalHotkeys}; }; @@ -1125,11 +1122,7 @@ Window *ShowBuildRailToolbar(RailType railtype) static void HandleStationPlacement(TileIndex start, TileIndex end) { - if (citymania::UseImprovedStationJoin()) { - citymania::HandleStationPlacement(start, end); - return; - } - + NOT_REACHED(); // CityMania uses tools TileArea ta(start, end); uint numtracks = ta.w; uint platlength = ta.h; diff --git a/src/road_gui.cpp b/src/road_gui.cpp index 53a73b5c40..8eef63d601 100644 --- a/src/road_gui.cpp +++ b/src/road_gui.cpp @@ -52,6 +52,7 @@ #include "citymania/cm_station_gui.hpp" #include "safeguards.h" +#include static void ShowRVStationPicker(Window *parent, RoadStopType rs); static void ShowRoadDepotPicker(Window *parent); @@ -220,11 +221,7 @@ void CcRoadStop(Commands, const CommandCost &result, TileIndex tile, uint8_t wid */ static void PlaceRoadStop(TileIndex start_tile, TileIndex end_tile, RoadStopType stop_type, bool adjacent, RoadType rt, StringID err_msg) { - if (citymania::UseImprovedStationJoin()) { - citymania::PlaceRoadStop(start_tile, end_tile, stop_type, adjacent, rt, err_msg); - return; - } - + NOT_REACHED(); // CityMania uses tools TileArea ta(start_tile, end_tile); assert(_thd.cm.type == citymania::ObjectHighlight::Type::ROAD_STOP); // DiagDirection ddir = _roadstop_gui_settings.orientation; @@ -518,7 +515,8 @@ struct BuildRoadToolbarWindow : Window { break; case WID_ROT_BUS_STATION: { - if (citymania::HandleStationPlacePushButton(this, WID_ROT_BUS_STATION, std::make_shared(ROADSTOP_BUS))) { + if (citymania::HandlePlacePushButton(this, WID_ROT_BUS_STATION, std::make_unique(ROADSTOP_BUS))) { + // if (HandlePlacePushButton(this, WID_ROT_BUS_STATION, SPR_CURSOR_BUS_STATION, HT_RECT, DDSP_BUILD_BUSSTOP)) { ShowRVStationPicker(this, ROADSTOP_BUS); this->last_started_action = widget; } @@ -526,7 +524,8 @@ struct BuildRoadToolbarWindow : Window { } case WID_ROT_TRUCK_STATION: - if (citymania::HandleStationPlacePushButton(this, WID_ROT_TRUCK_STATION, std::make_shared(ROADSTOP_TRUCK))) { + if (citymania::HandlePlacePushButton(this, WID_ROT_TRUCK_STATION, std::make_unique(ROADSTOP_TRUCK))) { + // if (HandlePlacePushButton(this, WID_ROT_TRUCK_STATION, SPR_CURSOR_TRUCK_STATION, HT_RECT, DDSP_BUILD_TRUCKSTOP)) { ShowRVStationPicker(this, ROADSTOP_TRUCK); this->last_started_action = widget; } @@ -660,8 +659,8 @@ struct BuildRoadToolbarWindow : Window { CloseWindowById(WC_BUILD_DEPOT, TRANSPORT_ROAD); CloseWindowById(WC_SELECT_STATION, 0); CloseWindowByClass(WC_BUILD_BRIDGE); - - citymania::AbortStationPlacement(); + + citymania::AbortStationPlacement(); } void OnPlaceDrag(ViewportPlaceMethod select_method, [[maybe_unused]] ViewportDragDropSelectionProcess select_proc, [[maybe_unused]] Point pt) override @@ -1176,7 +1175,7 @@ static void ShowRoadDepotPicker(Window *parent) /** Enum referring to the Hotkeys in the build road stop window */ enum BuildRoadStopHotkeys { BROSHK_FOCUS_FILTER_BOX, ///< Focus the edit box for editing the filter string - CM_BROSHK_ROTATE, + CM_BROSHK_ROTATE, }; struct BuildRoadStationWindow : public PickerWindowBase { @@ -1797,7 +1796,7 @@ static constexpr NWidgetPart _nested_road_station_picker_widgets[] = { NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1), NWidget(WWT_TEXTBTN, COLOUR_GREY, CM_WID_BROS_STATION_AUTO), SetMinimalSize(134, 12), SetDataTip(CM_STR_STATION_BUILD_ORIENTATION_AUTO, CM_STR_STATION_BUILD_ORIENTATION_AUTO_TOOLTIP), NWidget(WWT_TEXTBTN, COLOUR_GREY, CM_WID_BROS_STATION_XY_AUTO), SetMinimalSize(66, 12), SetDataTip(CM_STR_STATION_BUILD_ORIENTATION_AUTO, CM_STR_STATION_BUILD_ORIENTATION_AUTO_TOOLTIP), - EndContainer(), + EndContainer(), EndContainer(), NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_TYPE_SEL), NWidget(WWT_LABEL, COLOUR_DARK_GREEN, WID_BROS_SHOW_NEWST_TYPE), SetMinimalSize(144, 8), SetDataTip(STR_JUST_STRING, STR_NULL), SetTextStyle(TC_ORANGE), SetFill(1, 0), diff --git a/src/station_gui.cpp b/src/station_gui.cpp index 025638f153..de2fda931a 100644 --- a/src/station_gui.cpp +++ b/src/station_gui.cpp @@ -59,13 +59,6 @@ int DrawStationCoverageAreaText(int left, int right, int top, StationCoverageTyp { TileIndex tile = TileVirtXY(_thd.pos.x, _thd.pos.y); - /* CityMania code begin */ - auto s = citymania::GetStationCoverageAreaText(tile, _thd.size.x / TILE_SIZE, _thd.size.y / TILE_SIZE, rad, sct, supplies); - if (s.has_value()) { - return DrawStringMultiLine(left, right, top, INT32_MAX, s.value()); - } - /* CityMania code end */ - CargoTypes cargo_mask = 0; if (_thd.drawstyle == HT_RECT && tile < Map::Size()) { CargoArray cargoes; @@ -156,14 +149,9 @@ void FindStationsAroundSelection() */ void CheckRedrawStationCoverage(Window *w) { - /* CityMania code begin */ - if (citymania::UseImprovedStationJoin()) { - if (citymania::CheckRedrawStationCoverage()) w->SetDirty(); - return; - } - /* CityMania code end */ - /* Test if ctrl state changed */ + /* CityMania uses tools and handles redraws differently + static bool _last_fn_pressed; if (citymania::_fn_mod != _last_fn_pressed) { _thd.dirty = 0xff; @@ -178,6 +166,7 @@ void CheckRedrawStationCoverage(Window *w) FindStationsAroundSelection(); } } + */ } void CheckRedrawWaypointCoverage(const Window *) @@ -2351,7 +2340,7 @@ struct SelectStationWindow : WindowPopup { void Close([[maybe_unused]] int data = 0) override { if constexpr (std::is_same_v) SetViewportCatchmentSpecializedStation(nullptr, true); - else citymania::SetSelectedStationToJoin(INVALID_STATION); + else citymania::ResetJoinStationHighlight(); _thd.freeze = false; this->Window::Close(); @@ -2448,7 +2437,7 @@ struct SelectStationWindow : WindowPopup { auto it = this->vscroll->GetScrolledItemFromWidget(_stations_nearby_list, pt.y, this, WID_JS_PANEL, WidgetDimensions::scaled.framerect.top); const T *st = it == _stations_nearby_list.end() || *it == NEW_STATION ? nullptr : T::Get(*it); if constexpr (std::is_same_v) SetViewportCatchmentSpecializedStation(st, true); - else citymania::SetSelectedStationToJoin(*it); + else citymania::SetSelectedStationToJoin(st ? st->index : INVALID_STATION); } }; diff --git a/src/tilearea_type.h b/src/tilearea_type.h index 16e5968c26..9eda87bc9f 100644 --- a/src/tilearea_type.h +++ b/src/tilearea_type.h @@ -11,6 +11,7 @@ #define TILEAREA_TYPE_H #include "map_func.h" +#include "tile_type.h" class OrthogonalTileIterator; class DiagonalTileIterator; @@ -62,6 +63,12 @@ struct OrthogonalTileArea { return TILE_ADDXY(this->tile, this->w / 2, this->h / 2); } + TileIndex CMGetEndTile() const + { + if (this->w == 0 || this->h == 0 || this->tile == INVALID_TILE) return INVALID_TILE; + return TILE_ADDXY(this->tile, this->w - 1, this->h - 1); + } + OrthogonalTileIterator begin() const; OrthogonalTileIterator end() const; diff --git a/src/viewport.cpp b/src/viewport.cpp index d356165e6b..b24a885abb 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -243,7 +243,7 @@ static Point MapXYZToViewport(const Viewport *vp, int x, int y, int z) } void DeleteWindowViewport(Window *w) -{ +{ delete w->viewport; w->viewport = nullptr; } @@ -2916,7 +2916,7 @@ void UpdateTileSelection() if ((new_drawstyle & HT_DRAG_MASK) != HT_NONE) SetSelectionTilesDirty(); } - citymania::UpdateActivePreview(); + citymania::UpdateActiveTool(); } /** @@ -3645,7 +3645,7 @@ static void CalcRaildirsDrawstyle(int x, int y, int method) /* CityMania code start */ _thd.dir2 = HT_DIR_END; - ShowLengthMeasurement(b, TileVirtXY(_thd.selstart.x, _thd.selstart.y), TileVirtXY(_thd.selend.x, _thd.selend.y)); + ShowLengthMeasurement(b, TileVirtXY(_thd.selstart.x, _thd.selstart.y), TileVirtXY(_thd.selend.x, _thd.selend.y)); /* CityMania code end */ } @@ -3826,7 +3826,7 @@ calc_heightdiff_single_direction:; ShowMeasurementTooltips(measure_strings_length[index], index); } - #endif + #endif /* With current code passing a HT_LINE style to calculate the height * difference is enough. However if/when a point-tool is created * with this method, function should be called with new_style (below) @@ -3995,7 +3995,7 @@ void SetObjectToPlaceWnd(CursorID icon, PaletteID pal, HighLightStyle mode, Wind */ void SetObjectToPlace(CursorID icon, PaletteID pal, HighLightStyle mode, WindowClass window_class, WindowNumber window_num, ViewportDragDropSelectionProcess cm_process) { - citymania::ResetActivePreview(); + citymania::ResetActiveTool(); if (_thd.window_class != WC_INVALID) { /* Undo clicking on button and drag & drop */ Window *w = _thd.GetCallbackWnd(); @@ -4046,7 +4046,7 @@ void SetObjectToPlace(CursorID icon, PaletteID pal, HighLightStyle mode, WindowC void ResetObjectToPlace() { SetObjectToPlace(SPR_CURSOR_MOUSE, PAL_NONE, HT_NONE, WC_MAIN_WINDOW, 0, CM_DDSP_NONE); - citymania::ResetActivePreview(); + citymania::ResetActiveTool(); } Point GetViewportStationMiddle(const Viewport *vp, const Station *st)