static CommandCost RemoveShipDepot(TileIndex tile, DoCommandFlag flags) { if (!IsShipDepot(tile)) return CMD_ERROR; CommandCost ret = CheckTileOwnership(tile); if (ret.Failed()) return ret; TileIndex tile2 = GetOtherShipDepotTile(tile); /* do not check for ship on tile when company goes bankrupt */ if (!(flags & DC_BANKRUPT)) { CommandCost ret = EnsureNoVehicleOnGround(tile); if (ret.Succeeded()) ret = EnsureNoVehicleOnGround(tile2); if (ret.Failed()) return ret; } if (flags & DC_EXEC) { delete Depot::GetByTile(tile); MakeWaterKeepingClass(tile, GetTileOwner(tile)); MakeWaterKeepingClass(tile2, GetTileOwner(tile2)); } return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_SHIP]); }
/** * Remove a lock. * @param tile Central tile of the lock. * @param flags Operation to perform. * @return The cost in case of success, or an error code if it failed. */ static CommandCost RemoveLock(TileIndex tile, DoCommandFlag flags) { if (GetTileOwner(tile) != OWNER_NONE) { CommandCost ret = CheckTileOwnership(tile); if (ret.Failed()) return ret; } TileIndexDiff delta = TileOffsByDiagDir(GetLockDirection(tile)); /* make sure no vehicle is on the tile. */ CommandCost ret = EnsureNoVehicleOnGround(tile); if (ret.Succeeded()) ret = EnsureNoVehicleOnGround(tile + delta); if (ret.Succeeded()) ret = EnsureNoVehicleOnGround(tile - delta); if (ret.Failed()) return ret; if (flags & DC_EXEC) { DoClearSquare(tile); MakeWaterKeepingClass(tile + delta, GetTileOwner(tile + delta)); MakeWaterKeepingClass(tile - delta, GetTileOwner(tile - delta)); MarkCanalsAndRiversAroundDirty(tile - delta); MarkCanalsAndRiversAroundDirty(tile + delta); } return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_LOCK]); }
static CommandCost RemoveShipDepot(TileIndex tile, DoCommandFlag flags) { if (!IsShipDepot(tile)) return CMD_ERROR; CommandCost ret = CheckTileOwnership(tile); if (ret.Failed()) return ret; TileIndex tile2 = GetOtherShipDepotTile(tile); /* do not check for ship on tile when company goes bankrupt */ if (!(flags & DC_BANKRUPT)) { CommandCost ret = EnsureNoVehicleOnGround(tile); if (ret.Succeeded()) ret = EnsureNoVehicleOnGround(tile2); if (ret.Failed()) return ret; } if (flags & DC_EXEC) { delete Depot::GetByTile(tile); Company *c = Company::GetIfValid(GetTileOwner(tile)); if (c != NULL) { c->infrastructure.water -= 2 * LOCK_DEPOT_TILE_FACTOR; DirtyCompanyInfrastructureWindows(c->index); } MakeWaterKeepingClass(tile, GetTileOwner(tile)); MakeWaterKeepingClass(tile2, GetTileOwner(tile2)); } return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_SHIP]); }
/** * Builds a lock. * @param tile Central tile of the lock. * @param dir Uphill direction. * @param flags Operation to perform. * @return The cost in case of success, or an error code if it failed. */ static CommandCost DoBuildLock(TileIndex tile, DiagDirection dir, DoCommandFlag flags) { CommandCost cost(EXPENSES_CONSTRUCTION); int delta = TileOffsByDiagDir(dir); CommandCost ret = EnsureNoVehicleOnGround(tile); if (ret.Succeeded()) ret = EnsureNoVehicleOnGround(tile + delta); if (ret.Succeeded()) ret = EnsureNoVehicleOnGround(tile - delta); if (ret.Failed()) return ret; /* middle tile */ ret = DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); if (ret.Failed()) return ret; cost.AddCost(ret); /* lower tile */ WaterClass wc_lower = IsWaterTile(tile - delta) ? GetWaterClass(tile - delta) : WATER_CLASS_CANAL; if (!IsWaterTile(tile - delta)) { ret = DoCommand(tile - delta, 0, 0, flags, CMD_LANDSCAPE_CLEAR); if (ret.Failed()) return ret; cost.AddCost(ret); cost.AddCost(_price[PR_BUILD_CANAL]); } if (GetTileSlope(tile - delta, NULL) != SLOPE_FLAT) { return_cmd_error(STR_ERROR_LAND_SLOPED_IN_WRONG_DIRECTION); } /* upper tile */ WaterClass wc_upper = IsWaterTile(tile + delta) ? GetWaterClass(tile + delta) : WATER_CLASS_CANAL; if (!IsWaterTile(tile + delta)) { ret = DoCommand(tile + delta, 0, 0, flags, CMD_LANDSCAPE_CLEAR); if (ret.Failed()) return ret; cost.AddCost(ret); cost.AddCost(_price[PR_BUILD_CANAL]); } if (GetTileSlope(tile + delta, NULL) != SLOPE_FLAT) { return_cmd_error(STR_ERROR_LAND_SLOPED_IN_WRONG_DIRECTION); } if ((MayHaveBridgeAbove(tile) && IsBridgeAbove(tile)) || (MayHaveBridgeAbove(tile - delta) && IsBridgeAbove(tile - delta)) || (MayHaveBridgeAbove(tile + delta) && IsBridgeAbove(tile + delta))) { return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); } if (flags & DC_EXEC) { MakeLock(tile, _current_company, dir, wc_lower, wc_upper); MarkTileDirtyByTile(tile); MarkTileDirtyByTile(tile - delta); MarkTileDirtyByTile(tile + delta); MarkCanalsAndRiversAroundDirty(tile - delta); MarkCanalsAndRiversAroundDirty(tile + delta); } cost.AddCost(_price[PR_BUILD_LOCK]); return cost; }
/** * Build a ship depot. * @param tile tile where ship depot is built * @param flags type of operation * @param p1 bit 0 depot orientation (Axis) * @param p2 unused * @param text unused * @return the cost of this operation or an error */ CommandCost CmdBuildShipDepot(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { Axis axis = Extract<Axis, 0, 1>(p1); TileIndex tile2 = tile + (axis == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1)); if (!HasTileWaterGround(tile) || !HasTileWaterGround(tile2)) { return_cmd_error(STR_ERROR_MUST_BE_BUILT_ON_WATER); } if ((MayHaveBridgeAbove(tile) && IsBridgeAbove(tile)) || (MayHaveBridgeAbove(tile2) && IsBridgeAbove(tile2))) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); if (!IsTileFlat(tile) || !IsTileFlat(tile2)) { /* Prevent depots on rapids */ return_cmd_error(STR_ERROR_SITE_UNSUITABLE); } if (!Depot::CanAllocateItem()) return CMD_ERROR; WaterClass wc1 = GetWaterClass(tile); WaterClass wc2 = GetWaterClass(tile2); CommandCost cost = CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_DEPOT_SHIP]); bool add_cost = !IsWaterTile(tile); CommandCost ret = DoCommand(tile, 0, 0, flags | DC_AUTO, CMD_LANDSCAPE_CLEAR); if (ret.Failed()) return ret; if (add_cost) { cost.AddCost(ret); } add_cost = !IsWaterTile(tile2); ret = DoCommand(tile2, 0, 0, flags | DC_AUTO, CMD_LANDSCAPE_CLEAR); if (ret.Failed()) return ret; if (add_cost) { cost.AddCost(ret); } if (flags & DC_EXEC) { Depot *depot = new Depot(tile); depot->build_date = _date; if (wc1 == WATER_CLASS_CANAL || wc2 == WATER_CLASS_CANAL) { /* Update infrastructure counts after the unconditional clear earlier. */ Company::Get(_current_company)->infrastructure.water += wc1 == WATER_CLASS_CANAL && wc2 == WATER_CLASS_CANAL ? 2 : 1; } Company::Get(_current_company)->infrastructure.water += 2 * LOCK_DEPOT_TILE_FACTOR; DirtyCompanyInfrastructureWindows(_current_company); MakeShipDepot(tile, _current_company, depot->index, DEPOT_PART_NORTH, axis, wc1); MakeShipDepot(tile2, _current_company, depot->index, DEPOT_PART_SOUTH, axis, wc2); MarkTileDirtyByTile(tile); MarkTileDirtyByTile(tile2); MakeDefaultName(depot); } return cost; }
/** * Copy a piece of map and instantly paste at given location. * * @param tile Tile where to paste (northern). * @param flags Command flags. * @param p1 Various bits: * \li bits 0..27 [28] - northern tile of the source area * \li bits 28..31 [4] - rail type (RailType) to convert to, ignored if CPM_CONVERT_RAILTYPE mode is off * @param p2 Various bits: * \li bits 0..5 [6] - source area width * \li bits 6..11 [6] - source area height * \li bits 12..15 [4] - additional amount of tile heights to add to each tile (-8..7) * \li bits 16..18 [3] - transformation to perform (DirTransformation) * \li bits 19..27 [9] - mode (CopyPasteMode) * \li bits 28..31 [4] - [ unused ] * @param text Unused. * @return The cost of this operation or an error. */ CommandCost CmdInstantCopyPaste(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { CopyPasteParams copy_paste; /* extract and validate source area */ copy_paste.src_area.tile = GenericTileIndex(TileIndex(GB(p1, 0, 28))); copy_paste.src_area.w = GB(p2, 0, 6); copy_paste.src_area.h = GB(p2, 6, 6); CommandCost ret = ValParamCopyPasteArea(copy_paste.src_area); if (ret.Failed()) return ret; /* calculate and validate destination area */ copy_paste.dst_area = TransformTileArea(copy_paste.src_area, GenericTileIndex(tile), copy_paste.transformation); ret = ValParamCopyPasteArea(copy_paste.dst_area); if (ret.Failed()) return ret; /* extract and validate copy/paste mode */ copy_paste.mode = (CopyPasteMode)GB(p2, 19, 9); if (!ValParamCopyPasteMode(copy_paste.mode)) return CMD_ERROR; /* extract and validate rail type */ copy_paste.railtype = (RailType)GB(p1, 28, 4); if (!ValParamRailtype(copy_paste.railtype)) return CMD_ERROR; /* extract transformation */ copy_paste.transformation = (DirTransformation)GB(p2, 16, 3); /* extract the additional number of height units */ int additional_height_delta = GB(p2, 12, 4); // this is a 4-bit SIGNED integer (-8..7) additional_height_delta |= -(additional_height_delta & (1 << 3)); // propagate the sign bit /* calculate the height */ copy_paste.height_delta = CalcCopyPasteHeightDelta(copy_paste.src_area, copy_paste.dst_area, copy_paste.transformation, additional_height_delta); /* when copy and paste areas are too close each other, firstly * copy to the clipboard and then from the clipboard to the map */ if (CopyPasteAreasMayColide(copy_paste)) { Map *clipboard = GetClipboardBuffer(INSTANT_COPY_PASTE_BUFFER); /* Copy to a buffer, but only in the first stage of the command. * In a single player game and also while we are a server, the first one is non-DC_EXEC * stage (which is fallowed then by a DC_EXEC stage). When we are a client, there is only * one stage which is either a single non-DC_EXEC stage (shift pressed), or a single DC_EXEC * stage (command comming from the network). */ if ((_networking && !_network_server) || !(flags & DC_EXEC)) { CopyToClipboard(clipboard, copy_paste.src_area); } /* paste from the clipboard */ ret = PasteFromClipboard(clipboard, tile, flags, copy_paste.mode, copy_paste.transformation, copy_paste.railtype, additional_height_delta); } else { /* copy/paste directly */ InitializePasting(flags, copy_paste); DoCopyPaste(copy_paste); ret = FinalizePasting(); } return ret; }
/*! * This function executes a given command with the parameters from the #CommandProc parameter list. * Depending on the flags parameter it execute or test a command. * * @param tile The tile to apply the command on (for the #CommandProc) * @param p1 Additional data for the command (for the #CommandProc) * @param p2 Additional data for the command (for the #CommandProc) * @param flags Flags for the command and how to execute the command * @param cmd The command-id to execute (a value of the CMD_* enums) * @param text The text to pass * @see CommandProc * @return the cost */ CommandCost DoCommand(TileIndex tile, uint32 p1, uint32 p2, DoCommandFlag flags, uint32 cmd, const char *text) { CommandCost res; /* Do not even think about executing out-of-bounds tile-commands */ if (tile != 0 && (tile >= MapSize() || (!IsValidTile(tile) && (flags & DC_ALL_TILES) == 0))) return CMD_ERROR; /* Chop of any CMD_MSG or other flags; we don't need those here */ CommandProc *proc = _command_proc_table[cmd & CMD_ID_MASK].proc; _docommand_recursive++; /* only execute the test call if it's toplevel, or we're not execing. */ if (_docommand_recursive == 1 || !(flags & DC_EXEC) ) { if (_docommand_recursive == 1) _cleared_object_areas.Clear(); SetTownRatingTestMode(true); res = proc(tile, flags & ~DC_EXEC, p1, p2, text); SetTownRatingTestMode(false); if (res.Failed()) { goto error; } if (_docommand_recursive == 1 && !(flags & DC_QUERY_COST) && !(flags & DC_BANKRUPT) && !CheckCompanyHasMoney(res)) { // CheckCompanyHasMoney() modifies 'res' to an error if it fails. goto error; } if (!(flags & DC_EXEC)) { _docommand_recursive--; return res; } } /* Execute the command here. All cost-relevant functions set the expenses type * themselves to the cost object at some point */ if (_docommand_recursive == 1) _cleared_object_areas.Clear(); res = proc(tile, flags, p1, p2, text); if (res.Failed()) { error: _docommand_recursive--; return res; } /* if toplevel, subtract the money. */ if (--_docommand_recursive == 0 && !(flags & DC_BANKRUPT)) { SubtractMoneyFromCompany(res); } return res; }
/** * Build a ship depot. * @param tile tile where ship depot is built * @param flags type of operation * @param p1 bit 0 depot orientation (Axis) * @param p2 unused * @param text unused * @return the cost of this operation or an error */ CommandCost CmdBuildShipDepot(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { Axis axis = Extract<Axis, 0, 1>(p1); TileIndex tile2 = tile + (axis == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1)); if (!HasTileWaterGround(tile) || !HasTileWaterGround(tile2)) { return_cmd_error(STR_ERROR_MUST_BE_BUILT_ON_WATER); } if ((MayHaveBridgeAbove(tile) && IsBridgeAbove(tile)) || (MayHaveBridgeAbove(tile2) && IsBridgeAbove(tile2))) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); if (GetTileSlope(tile, NULL) != SLOPE_FLAT || GetTileSlope(tile2, NULL) != SLOPE_FLAT) { /* Prevent depots on rapids */ return_cmd_error(STR_ERROR_SITE_UNSUITABLE); } if (!Depot::CanAllocateItem()) return CMD_ERROR; WaterClass wc1 = GetWaterClass(tile); WaterClass wc2 = GetWaterClass(tile2); CommandCost cost = CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_DEPOT_SHIP]); bool add_cost = !IsWaterTile(tile); CommandCost ret = DoCommand(tile, 0, 0, flags | DC_AUTO, CMD_LANDSCAPE_CLEAR); if (ret.Failed()) return ret; if (add_cost) { cost.AddCost(ret); } add_cost = !IsWaterTile(tile2); ret = DoCommand(tile2, 0, 0, flags | DC_AUTO, CMD_LANDSCAPE_CLEAR); if (ret.Failed()) return ret; if (add_cost) { cost.AddCost(ret); } if (flags & DC_EXEC) { Depot *depot = new Depot(tile); depot->build_date = _date; MakeShipDepot(tile, _current_company, depot->index, DEPOT_NORTH, axis, wc1); MakeShipDepot(tile2, _current_company, depot->index, DEPOT_SOUTH, axis, wc2); MarkTileDirtyByTile(tile); MarkTileDirtyByTile(tile2); MakeDefaultName(depot); } return cost; }
/** * Terraform multiple tiles. * * @param iter Iterator pointing tiles to terraform and their target heights. * @return The cost of all successfull operations and the last error. * * @note _terraform_err_tile will be set to the tile where the last error occured * * @warning Note non-standard return behaviour - booth the cost \b and the error combined. */ static TerraformTilesResult TerraformTiles(TerraformingIterator *iter, DoCommandFlag flags, Money available_money) { TerraformTilesResult result = { 0, // cost false, // had_success STR_NULL // last_error }; TileIndex last_err_tile = INVALID_TILE; const Company *c = Company::GetIfValid(_current_company); int limit = (c == NULL ? INT32_MAX : GB(c->terraform_limit, 16, 16)); if (limit == 0) result.last_error = STR_ERROR_TERRAFORM_LIMIT_REACHED; for (; *iter != INVALID_TILE && limit > 0; ++(*iter)) { int h = iter->GetTileTargetHeight(); TileIndex t = *iter; for (int curh = TileHeight(t); curh != h; curh += (curh > h) ? -1 : 1) { CommandCost ret = DoCommand(t, SLOPE_N, (curh > h) ? 0 : 1, flags & ~DC_EXEC, CMD_TERRAFORM_LAND); if (ret.Failed()) { result.last_error = ret.GetErrorMessage(); last_err_tile = _terraform_err_tile; /* Did we reach the limit? */ if (ret.GetErrorMessage() == STR_ERROR_TERRAFORM_LIMIT_REACHED) limit = 0; break; } if (flags & DC_EXEC) { available_money -= ret.GetCost(); if (available_money < 0) { _additional_cash_required = ret.GetCost(); _terraform_err_tile = t; return result; } DoCommand(t, SLOPE_N, (curh > h) ? 0 : 1, flags, CMD_TERRAFORM_LAND); } else { /* When we're at the terraform limit we better bail (unneeded) testing as well. * This will probably cause the terraforming cost to be underestimated, but only * when it's near the terraforming limit. Even then, the estimation is * completely off due to it basically counting terraforming double, so it being * cut off earlier might even give a better estimate in some cases. */ if (--limit <= 0) { result.had_success = true; break; } } result.cost += ret.GetCost(); result.had_success = true; } } if (!result.had_success && result.last_error == STR_NULL) { result.last_error = STR_ERROR_ALREADY_LEVELLED; last_err_tile = INVALID_TILE; } _terraform_err_tile = last_err_tile; return result; }
/** Refits a ship to the specified cargo type. * @param tile unused * @param flags type of operation * @param p1 vehicle ID of the ship to refit * @param p2 various bitstuffed elements * - p2 = (bit 0-7) - the new cargo type to refit to (p2 & 0xFF) * - p2 = (bit 8-15) - the new cargo subtype to refit to * - p2 = (bit 16) - refit only this vehicle (ignored) * @param text unused * @return the cost of this operation or an error */ CommandCost CmdRefitShip(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { CargoID new_cid = GB(p2, 0, 8); // gets the cargo number byte new_subtype = GB(p2, 8, 8); Ship *v = Ship::GetIfValid(p1); if (v == NULL) return CMD_ERROR; CommandCost ret = CheckOwnership(v->owner); if (ret.Failed()) return ret; if (!v->IsStoppedInDepot()) return_cmd_error(STR_ERROR_SHIP_MUST_BE_STOPPED_IN_DEPOT); if (v->vehstatus & VS_CRASHED) return_cmd_error(STR_ERROR_CAN_T_REFIT_DESTROYED_VEHICLE); /* Check cargo */ if (new_cid >= NUM_CARGO) return CMD_ERROR; CommandCost cost = RefitVehicle(v, true, new_cid, new_subtype, flags); if (flags & DC_EXEC) { v->colourmap = PAL_NONE; // invalidate vehicle colour map SetWindowDirty(WC_VEHICLE_DETAILS, v->index); SetWindowDirty(WC_VEHICLE_DEPOT, v->tile); InvalidateWindowClassesData(WC_SHIPS_LIST, 0); } v->InvalidateNewGRFCacheOfChain(); // always invalidate; querycost might have filled it return cost; }
/** * Set the start date of the timetable. * @param tile Not used. * @param flags Operation to perform. * @param p1 Vehicle id. * @param p2 The timetable start date in ticks. */ CommandCost CmdSetTimetableStart(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { if (!_settings_game.order.timetabling) return CMD_ERROR; Vehicle *v = Vehicle::GetIfValid(GB(p1, 0, 16)); if (v == NULL || !v->IsPrimaryVehicle()) return CMD_ERROR; CommandCost ret = CheckOwnership(v->owner); if (ret.Failed()) return ret; /* Don't let a timetable start more than 15 years into the future or 1 year in the past. */ Date start_date = (Date)p2; if (start_date < 0 || start_date > MAX_DAY) return CMD_ERROR; if (start_date - _date > 15 * DAYS_IN_LEAP_YEAR) return CMD_ERROR; if (_date - start_date > DAYS_IN_LEAP_YEAR) return CMD_ERROR; if (flags & DC_EXEC) { v->lateness_counter = 0; ClrBit(v->vehicle_flags, VF_TIMETABLE_STARTED); v->timetable_start = start_date; SetWindowDirty(WC_VEHICLE_TIMETABLE, v->index); } return CommandCost(); }
/** * Rename a depot. * @param tile unused * @param flags type of operation * @param p1 id of depot * @param p2 unused * @param text the new name or an empty string when resetting to the default * @return the cost of this operation or an error */ CommandCost CmdRenameDepot(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { Depot *d = Depot::GetIfValid(p1); if (d == NULL) return CMD_ERROR; CommandCost ret = CheckTileOwnership(d->xy); if (ret.Failed()) return ret; bool reset = StrEmpty(text); if (!reset) { if (Utf8StringLength(text) >= MAX_LENGTH_DEPOT_NAME_CHARS) return CMD_ERROR; if (!IsUniqueDepotName(text)) return_cmd_error(STR_ERROR_NAME_MUST_BE_UNIQUE); } if (flags & DC_EXEC) { free(d->name); if (reset) { d->name = NULL; MakeDefaultName(d); } else { d->name = strdup(text); } /* Update the orders and depot */ SetWindowClassesDirty(WC_VEHICLE_ORDERS); SetWindowDirty(WC_VEHICLE_DEPOT, d->xy); /* Update the depot list */ VehicleType vt = GetDepotVehicleType(d->xy); SetWindowDirty(GetWindowClassForVehicleType(vt), VehicleListIdentifier(VL_DEPOT_LIST, vt, GetTileOwner(d->xy), d->index).Pack()); } return CommandCost(); }
/** * Change the service interval of a vehicle * @param tile unused * @param flags type of operation * @param p1 vehicle ID that is being service-interval-changed * @param p2 bitmask * - p2 = (bit 0-15) - new service interval * - p2 = (bit 16) - service interval is custom flag * - p2 = (bit 17) - service interval is percentage flag * @param text unused * @return the cost of this operation or an error */ CommandCost CmdChangeServiceInt(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { Vehicle *v = Vehicle::GetIfValid(p1); if (v == NULL || !v->IsPrimaryVehicle()) return CMD_ERROR; CommandCost ret = CheckOwnership(v->owner); if (ret.Failed()) return ret; const Company *company = Company::Get(v->owner); bool iscustom = HasBit(p2, 16); bool ispercent = iscustom ? HasBit(p2, 17) : company->settings.vehicle.servint_ispercent; uint16 serv_int; if (iscustom) { serv_int = GB(p2, 0, 16); if (serv_int != GetServiceIntervalClamped(serv_int, ispercent)) return CMD_ERROR; } else { serv_int = CompanyServiceInterval(company, v->type); } if (flags & DC_EXEC) { v->SetServiceInterval(serv_int); v->SetServiceIntervalIsCustom(iscustom); v->SetServiceIntervalIsPercent(ispercent); SetWindowDirty(WC_VEHICLE_DETAILS, v->index); } return CommandCost(); }
/** * Callback function that is called after a sign is placed * @param result of the operation * @param tile unused * @param p1 unused * @param p2 unused */ void CcPlaceSign(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2) { if (result.Failed()) return; ShowRenameSignWindow(Sign::Get(_new_sign_id)); ResetObjectToPlace(); }
void CcBuildAirport(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2) { if (result.Failed()) return; SndPlayTileFx(SND_1F_SPLAT, tile); if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace(); }
void CcBuildDocks(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2) { if (result.Failed()) return; if (_settings_client.sound.confirm) SndPlayTileFx(SND_02_SPLAT, tile); if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace(); }
/** * Check whether the given tile is suitable for a waypoint. * @param tile the tile to check for suitability * @param axis the axis of the waypoint * @param waypoint Waypoint the waypoint to check for is already joined to. If we find another waypoint it can join to it will throw an error. */ static CommandCost IsValidTileForWaypoint(TileIndex tile, Axis axis, StationID *waypoint) { /* if waypoint is set, then we have special handling to allow building on top of already existing waypoints. * so waypoint points to INVALID_STATION if we can build on any waypoint. * Or it points to a waypoint if we're only allowed to build on exactly that waypoint. */ if (waypoint != NULL && IsTileType(tile, MP_STATION)) { if (!IsRailWaypoint(tile)) { return ClearTile_Station(tile, DC_AUTO); // get error message } else { StationID wp = GetStationIndex(tile); if (*waypoint == INVALID_STATION) { *waypoint = wp; } else if (*waypoint != wp) { return_cmd_error(STR_ERROR_WAYPOINT_ADJOINS_MORE_THAN_ONE_EXISTING); } } } if (GetAxisForNewWaypoint(tile) != axis) return_cmd_error(STR_ERROR_NO_SUITABLE_RAILROAD_TRACK); Owner owner = GetTileOwner(tile); CommandCost ret = CheckOwnership(owner); if (ret.Succeeded()) ret = EnsureNoVehicleOnGround(tile); if (ret.Failed()) return ret; Slope tileh = GetTileSlope(tile); if (tileh != SLOPE_FLAT && (!_settings_game.construction.build_on_slopes || IsSteepSlope(tileh) || !(tileh & (0x3 << axis)) || !(tileh & ~(0x3 << axis)))) { return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); } if (MayHaveBridgeAbove(tile) && IsBridgeAbove(tile)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); return CommandCost(); }
/** * Remove a buoy * @param tile TileIndex been queried * @param flags operation to perform * @pre IsBuoyTile(tile) * @return cost or failure of operation */ CommandCost RemoveBuoy(TileIndex tile, DoCommandFlag flags) { /* XXX: strange stuff, allow clearing as invalid company when clearing landscape */ if (!Company::IsValidID(_current_company) && !(flags & DC_BANKRUPT)) return_cmd_error(INVALID_STRING_ID); Waypoint *wp = Waypoint::GetByTile(tile); if (HasStationInUse(wp->index, false, _current_company)) return_cmd_error(STR_ERROR_BUOY_IS_IN_USE); /* remove the buoy if there is a ship on tile when company goes bankrupt... */ if (!(flags & DC_BANKRUPT)) { CommandCost ret = EnsureNoVehicleOnGround(tile); if (ret.Failed()) return ret; } if (flags & DC_EXEC) { wp->facilities &= ~FACIL_DOCK; InvalidateWindowData(WC_WAYPOINT_VIEW, wp->index); /* We have to set the water tile's state to the same state as before the * buoy was placed. Otherwise one could plant a buoy on a canal edge, * remove it and flood the land (if the canal edge is at level 0) */ MakeWaterKeepingClass(tile, GetTileOwner(tile)); wp->rect.AfterRemoveTile(wp, tile); wp->UpdateVirtCoord(); wp->delete_ctr = 0; } return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_WAYPOINT_BUOY]); }
/** * Rename a waypoint. * @param tile unused * @param flags type of operation * @param p1 id of waypoint * @param p2 unused * @param text the new name or an empty string when resetting to the default * @return the cost of this operation or an error */ CommandCost CmdRenameWaypoint(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { Waypoint *wp = Waypoint::GetIfValid(p1); if (wp == NULL) return CMD_ERROR; if (wp->owner != OWNER_NONE) { CommandCost ret = CheckOwnership(wp->owner); if (ret.Failed()) return ret; } bool reset = StrEmpty(text); if (!reset) { if (Utf8StringLength(text) >= MAX_LENGTH_STATION_NAME_CHARS) return CMD_ERROR; if (!IsUniqueWaypointName(text)) return_cmd_error(STR_ERROR_NAME_MUST_BE_UNIQUE); } if (flags & DC_EXEC) { free(wp->name); wp->name = reset ? NULL : strdup(text); wp->UpdateVirtCoord(); } return CommandCost(); }
/** * This is the Callback method after the cloning attempt of a vehicle * @param result the result of the cloning command * @param tile unused * @param p1 unused * @param p2 unused */ void CcCloneVehicle(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2) { if (result.Failed()) return; const Vehicle *v = Vehicle::Get(_new_vehicle_id); ShowVehicleViewWindow(v); }
/** * Check whether the given tile is suitable for a waypoint. * @param tile the tile to check for suitability * @param axis the axis of the waypoint * @param waypoint Waypoint the waypoint to check for is already joined to. If we find another waypoint it can join to it will throw an error. * @param flags flags for the command */ static CommandCost IsValidTileForWaypoint(TileIndex tile, Axis axis, StationID *waypoint, DoCommandFlag flags) { /* if waypoint is set, then we have special handling to allow building on top of already existing waypoints. * so waypoint points to INVALID_STATION if we can build on any waypoint. * Or it points to a waypoint if we're only allowed to build on exactly that waypoint. */ if (waypoint != NULL && IsTileType(tile, MP_STATION)) { if (!IsRailWaypoint(tile)) { return ClearTile_Station(tile, DC_AUTO); // get error message } else { StationID wp = GetStationIndex(tile); if (*waypoint == INVALID_STATION) { *waypoint = wp; } else if (*waypoint != wp) { return_cmd_error(STR_ERROR_WAYPOINT_ADJOINS_MORE_THAN_ONE_EXISTING); } } } /* When pasting a waypoint, there may be no track yet (it will be placed when DC_EXEC'ing). * Ignore that so we can calculate the cost more precisely. */ bool ignore_lack_of_tracks = (flags & DC_PASTE) && !(flags & DC_EXEC); if (!ignore_lack_of_tracks || IsTileType(tile, MP_RAILWAY)) { if (GetAxisForNewWaypoint(tile) != axis) return_cmd_error(STR_ERROR_NO_SUITABLE_RAILROAD_TRACK); } Owner owner = GetTileOwner(tile); if (!ignore_lack_of_tracks || owner != OWNER_NONE) { CommandCost ret = CheckOwnership(owner); if (ret.Failed()) return ret; } CommandCost ret = EnsureNoVehicleOnGround(tile); if (ret.Failed()) return ret; Slope tileh = GetTileSlope(tile); if (tileh != SLOPE_FLAT && (!_settings_game.construction.build_on_slopes || IsSteepSlope(tileh) || !(tileh & (0x3 << axis)) || !(tileh & ~(0x3 << axis)))) { return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); } if (IsBridgeAbove(tile)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); return CommandCost(); }
void CcRoadDepot(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2) { if (result.Failed()) return; DiagDirection dir = (DiagDirection)GB(p1, 0, 2); if (_settings_client.sound.confirm) SndPlayTileFx(SND_1F_SPLAT_OTHER, tile); if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace(); ConnectRoadToStructure(tile, dir); }
/** * Build a buoy. * @param tile tile where to place the buoy * @param flags operation to perform * @param p1 unused * @param p2 unused * @param text unused * @return the cost of this operation or an error */ CommandCost CmdBuildBuoy(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { if (tile == 0) return_cmd_error(STR_ERROR_SITE_UNSUITABLE); WaterClass wc; if ((flags & DC_PASTE) && !(flags & DC_EXEC)) { /* When pasting a buoy, there may be no water yet (a canal will be placed when DC_EXE'ing). * Ignore that there is no water so we can calculate the cost more precisely. */ wc = WATER_CLASS_INVALID; } else { if (!HasTileWaterGround(tile)) return_cmd_error(STR_ERROR_SITE_UNSUITABLE); wc = GetWaterClass(tile); } if (IsBridgeAbove(tile)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); if (!IsTileFlat(tile)) return_cmd_error(STR_ERROR_SITE_UNSUITABLE); /* Check if there is an already existing, deleted, waypoint close to us that we can reuse. */ Waypoint *wp = FindDeletedWaypointCloseTo(tile, STR_SV_STNAME_BUOY, OWNER_NONE); if (wp == NULL && !Waypoint::CanAllocateItem()) return_cmd_error(STR_ERROR_TOO_MANY_STATIONS_LOADING); CommandCost cost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_WAYPOINT_BUOY]); if (!IsWaterTile(tile)) { CommandCost ret = DoCommand(tile, 0, 0, flags | DC_AUTO, CMD_LANDSCAPE_CLEAR); if (ret.Failed()) return ret; cost.AddCost(ret); } if (flags & DC_EXEC) { if (wp == NULL) { wp = new Waypoint(tile); } else { /* Move existing (recently deleted) buoy to the new location */ wp->xy = tile; InvalidateWindowData(WC_WAYPOINT_VIEW, wp->index); } wp->rect.BeforeAddTile(tile, StationRect::ADD_TRY); wp->string_id = STR_SV_STNAME_BUOY; wp->facilities |= FACIL_DOCK; wp->owner = OWNER_NONE; wp->build_date = _date; if (wp->town == NULL) MakeDefaultName(wp); assert(wc != WATER_CLASS_INVALID); MakeBuoy(tile, wp->index, wc); wp->UpdateVirtCoord(); InvalidateWindowData(WC_WAYPOINT_VIEW, wp->index); } return cost; }
/*! * Toplevel network safe docommand function for the current company. Must not be called recursively. * The callback is called when the command succeeded or failed. The parameters * \a tile, \a p1, and \a p2 are from the #CommandProc function. The parameter \a cmd is the command to execute. * The parameter \a my_cmd is used to indicate if the command is from a company or the server. * * @param tile The tile to perform a command on (see #CommandProc) * @param p1 Additional data for the command (see #CommandProc) * @param p2 Additional data for the command (see #CommandProc) * @param cmd The command to execute (a CMD_* value) * @param callback A callback function to call after the command is finished * @param text The text to pass * @param my_cmd indicator if the command is from a company or server (to display error messages for a user) * @return \c true if the command succeeded, else \c false. */ bool DoCommandP(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const char *text, bool my_cmd) { /* Cost estimation is generally only done when the * local user presses shift while doing somthing. * However, in case of incoming network commands, * map generation or the pause button we do want * to execute. */ bool estimate_only = _shift_pressed && IsLocalCompany() && !_generating_world && !(cmd & CMD_NETWORK_COMMAND) && (cmd & CMD_ID_MASK) != CMD_PAUSE; /* We're only sending the command, so don't do * fancy things for 'success'. */ bool only_sending = _networking && !(cmd & CMD_NETWORK_COMMAND); /* Where to show the message? */ int x = TileX(tile) * TILE_SIZE; int y = TileY(tile) * TILE_SIZE; if (_pause_mode != PM_UNPAUSED && !IsCommandAllowedWhilePaused(cmd)) { ShowErrorMessage(GB(cmd, 16, 16), STR_ERROR_NOT_ALLOWED_WHILE_PAUSED, WL_INFO, x, y); return false; } #ifdef ENABLE_NETWORK /* Only set p2 when the command does not come from the network. */ if (!(cmd & CMD_NETWORK_COMMAND) && GetCommandFlags(cmd) & CMD_CLIENT_ID && p2 == 0) p2 = CLIENT_ID_SERVER; #endif CommandCost res = DoCommandPInternal(tile, p1, p2, cmd, callback, text, my_cmd, estimate_only); if (res.Failed()) { /* Only show the error when it's for us. */ StringID error_part1 = GB(cmd, 16, 16); if (estimate_only || (IsLocalCompany() && error_part1 != 0 && my_cmd)) { ShowErrorMessage(error_part1, res.GetErrorMessage(), WL_INFO, x, y, res.GetTextRefStackSize(), res.GetTextRefStack()); } } else if (estimate_only) { ShowEstimatedCostOrIncome(res.GetCost(), x, y); } else if (!only_sending && res.GetCost() != 0 && tile != 0 && IsLocalCompany() && _game_mode != GM_EDITOR) { /* Only show the cost animation when we did actually * execute the command, i.e. we're not sending it to * the server, when it has cost the local company * something. Furthermore in the editor there is no * concept of cost, so don't show it there either. */ ShowCostOrIncomeAnimation(x, y, GetSlopePixelZ(x, y), res.GetCost()); } if (!estimate_only && !only_sending && callback != NULL) { callback(res, tile, p1, p2); } return res.Succeeded(); }
bool AIObject::DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint cmd, const char *text, AISuspendCallbackProc *callback) { if (!AIObject::CanSuspend()) { throw AI_FatalError("You are not allowed to execute any DoCommand (even indirect) in your constructor, Save(), Load(), and any valuator."); } /* Set the default callback to return a true/false result of the DoCommand */ if (callback == NULL) callback = &AIInstance::DoCommandReturn; /* Are we only interested in the estimate costs? */ bool estimate_only = GetDoCommandMode() != NULL && !GetDoCommandMode()(); #ifdef ENABLE_NETWORK /* Only set p2 when the command does not come from the network. */ if (GetCommandFlags(cmd) & CMD_CLIENT_ID && p2 == 0) p2 = UINT32_MAX; #endif /* Try to perform the command. */ CommandCost res = ::DoCommandPInternal(tile, p1, p2, cmd, _networking ? CcAI : NULL, text, false, estimate_only); /* We failed; set the error and bail out */ if (res.Failed()) { SetLastError(AIError::StringToError(res.GetErrorMessage())); return false; } /* No error, then clear it. */ SetLastError(AIError::ERR_NONE); /* Estimates, update the cost for the estimate and be done */ if (estimate_only) { IncreaseDoCommandCosts(res.GetCost()); return true; } /* Costs of this operation. */ SetLastCost(res.GetCost()); SetLastCommandRes(true); if (_networking) { /* Suspend the AI till the command is really executed. */ throw AI_VMSuspend(-(int)GetDoCommandDelay(), callback); } else { IncreaseDoCommandCosts(res.GetCost()); /* Suspend the AI player for 1+ ticks, so it simulates multiplayer. This * both avoids confusion when a developer launched his AI in a * multiplayer game, but also gives time for the GUI and human player * to interact with the game. */ throw AI_VMSuspend(GetDoCommandDelay(), callback); } NOT_REACHED(); }
/** * Build a piece of canal. * @param tile end tile of stretch-dragging * @param flags type of operation * @param p1 start tile of stretch-dragging * @param p2 waterclass to build. sea and river can only be built in scenario editor * @param text unused * @return the cost of this operation or an error */ CommandCost CmdBuildCanal(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { WaterClass wc = Extract<WaterClass, 0, 2>(p2); if (p1 >= MapSize() || wc == WATER_CLASS_INVALID) return CMD_ERROR; /* Outside of the editor you can only build canals, not oceans */ if (wc != WATER_CLASS_CANAL && _game_mode != GM_EDITOR) return CMD_ERROR; TileArea ta(tile, p1); /* Outside the editor you can only drag canals, and not areas */ if (_game_mode != GM_EDITOR && ta.w != 1 && ta.h != 1) return CMD_ERROR; CommandCost cost(EXPENSES_CONSTRUCTION); TILE_AREA_LOOP(tile, ta) { CommandCost ret; Slope slope = GetTileSlope(tile, NULL); if (slope != SLOPE_FLAT && (wc != WATER_CLASS_RIVER || !IsInclinedSlope(slope))) { return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); } /* can't make water of water! */ if (IsTileType(tile, MP_WATER) && (!IsTileOwner(tile, OWNER_WATER) || wc == WATER_CLASS_SEA)) continue; ret = DoCommand(tile, 0, 0, flags | DC_FORCE_CLEAR_TILE, CMD_LANDSCAPE_CLEAR); if (ret.Failed()) return ret; cost.AddCost(ret); if (flags & DC_EXEC) { switch (wc) { case WATER_CLASS_RIVER: MakeRiver(tile, Random()); break; case WATER_CLASS_SEA: if (TileHeight(tile) == 0) { MakeSea(tile); break; } /* FALL THROUGH */ default: MakeCanal(tile, _current_company, Random()); break; } MarkTileDirtyByTile(tile); MarkCanalsAndRiversAroundDirty(tile); } cost.AddCost(_price[PR_BUILD_CANAL]); }
/** * Add or remove waiting times from an order. * @param tile Not used. * @param flags Operation to perform. * @param p1 Various bitstuffed elements * - p1 = (bit 0-15) - Vehicle with the orders to change. * - p1 = (bit 16-23) - Order index to modify. * - p1 = (bit 24) - Whether to change the waiting time or the travelling * time. * - p1 = (bit 25) - Whether p2 contains waiting and travelling time. * @param p2 The amount of time to wait. * - p2 = (bit 0-15) - Waiting or travelling time as specified by p1 bit 24 if p1 bit 25 is not set, * Travelling time if p1 bit 25 is set. * - p2 = (bit 16-31) - Waiting time if p1 bit 25 is set * @param text unused * @return the cost of this operation or an error */ CommandCost CmdChangeTimetable(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { if (!_settings_game.order.timetabling) return CMD_ERROR; VehicleID veh = GB(p1, 0, 16); Vehicle *v = Vehicle::GetIfValid(veh); if (v == NULL || !v->IsPrimaryVehicle()) return CMD_ERROR; CommandCost ret = CheckOwnership(v->owner); if (ret.Failed()) return ret; VehicleOrderID order_number = GB(p1, 16, 8); Order *order = v->GetOrder(order_number); if (order == NULL) return CMD_ERROR; bool packed_time = HasBit(p1, 25); bool is_journey = HasBit(p1, 24) || packed_time; int wait_time = order->wait_time; int travel_time = order->travel_time; if (packed_time) { travel_time = GB(p2, 0, 16); wait_time = GB(p2, 16, 16); } else if (is_journey) { travel_time = GB(p2, 0, 16); } else { wait_time = GB(p2, 0, 16); } if (wait_time != order->wait_time) { switch (order->GetType()) { case OT_GOTO_STATION: if (order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION) return_cmd_error(STR_ERROR_TIMETABLE_NOT_STOPPING_HERE); break; case OT_CONDITIONAL: break; default: return_cmd_error(STR_ERROR_TIMETABLE_ONLY_WAIT_AT_STATIONS); } } if (travel_time != order->travel_time && order->IsType(OT_CONDITIONAL)) return CMD_ERROR; if (flags & DC_EXEC) { if (wait_time != order->wait_time) ChangeTimetable(v, order_number, wait_time, false); if (travel_time != order->travel_time) ChangeTimetable(v, order_number, travel_time, true); } return CommandCost(); }
/** * Command callback for building road stops. * @param result Result of the build road stop command. * @param tile Start tile. * @param p1 bit 0..7: Width of the road stop. * bit 8..15: Length of the road stop. * @param p2 bit 0: 0 For bus stops, 1 for truck stops. * bit 1: 0 For normal stops, 1 for drive-through. * bit 2..3: The roadtypes. * bit 5: Allow stations directly adjacent to other stations. * bit 6..7: Entrance direction (#DiagDirection). * bit 16..31: Station ID to join (NEW_STATION if build new one). * @see CmdBuildRoadStop */ void CcRoadStop(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2) { if (result.Failed()) return; DiagDirection dir = (DiagDirection)GB(p2, 6, 2); if (_settings_client.sound.confirm) SndPlayTileFx(SND_1F_SPLAT_OTHER, tile); if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace(); TileArea roadstop_area(tile, GB(p1, 0, 8), GB(p1, 8, 8)); TILE_AREA_LOOP(cur_tile, roadstop_area) { ConnectRoadToStructure(cur_tile, dir); /* For a drive-through road stop build connecting road for other entrance. */ if (HasBit(p2, 1)) ConnectRoadToStructure(cur_tile, ReverseDiagDir(dir)); }
void ScriptInstance::DoCommandCallback(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2) { ScriptObject::ActiveInstance active(this); ScriptObject::SetLastCommandRes(result.Succeeded()); if (result.Failed()) { ScriptObject::SetLastError(ScriptError::StringToError(result.GetErrorMessage())); } else { ScriptObject::IncreaseDoCommandCosts(result.GetCost()); ScriptObject::SetLastCost(result.GetCost()); } }
void CcAI(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2) { AIObject::SetLastCommandRes(result.Succeeded()); if (result.Failed()) { AIObject::SetLastError(AIError::StringToError(result.GetErrorMessage())); } else { AIObject::IncreaseDoCommandCosts(result.GetCost()); AIObject::SetLastCost(result.GetCost()); } Company::Get(_current_company)->ai_instance->Continue(); }