/** * Is there a Chunnel in the way in the given direction? * Only between height level 0 and 1. * Used to avoid building bridge or tunnel between existing chunnel. * @param tile the tile to search from. * @param dir the direction to start searching to. * @return true if and only if there is a chunnel. */ bool IsBetweenChunnelPortals(TileIndex tile, DiagDirection dir) { uint h = 0; TileIndexDiff delta = TileOffsByDiagDir(dir); if (GetTileZ(tile) > 0) return false; do { if (dir == DIAGDIR_NE || dir == DIAGDIR_NW) { do { tile += delta; if (!IsValidTile(tile)) return false; } while (TileHeight(tile) != h); } else { tile += delta; do { tile += delta; if (!IsValidTile(tile)) return false; } while (TileHeight(tile) != h); tile -= delta; } (h == 0) ? h = 1 : h = 0; } while (!IsTunnelTile(tile)); if (GetTunnelBridgeDirection(tile) != ReverseDiagDir(dir)) return false; return true; }
/** * Clean up unnecessary RoadBits of a planed tile. * @param tile current tile * @param org_rb planed RoadBits * @return optimised RoadBits */ RoadBits CleanUpRoadBits(const TileIndex tile, RoadBits org_rb) { if (!IsValidTile(tile)) return ROAD_NONE; for (DiagDirection dir = DIAGDIR_BEGIN; dir < DIAGDIR_END; dir++) { const TileIndex neighbor_tile = TileAddByDiagDir(tile, dir); /* Get the Roadbit pointing to the neighbor_tile */ const RoadBits target_rb = DiagDirToRoadBits(dir); /* If the roadbit is in the current plan */ if (org_rb & target_rb) { bool connective = false; const RoadBits mirrored_rb = MirrorRoadBits(target_rb); if (IsValidTile(neighbor_tile)) { switch (GetTileType(neighbor_tile)) { /* Always connective ones */ case MP_CLEAR: case MP_TREES: connective = true; break; /* The conditionally connective ones */ case MP_TUNNELBRIDGE: case MP_STATION: case MP_ROAD: if (IsNormalRoadTile(neighbor_tile)) { /* Always connective */ connective = true; } else { const RoadBits neighbor_rb = GetAnyRoadBits(neighbor_tile, ROADTYPE_ROAD) | GetAnyRoadBits(neighbor_tile, ROADTYPE_TRAM); /* Accept only connective tiles */ connective = (neighbor_rb & mirrored_rb) != ROAD_NONE; } break; case MP_RAILWAY: connective = IsPossibleCrossing(neighbor_tile, DiagDirToAxis(dir)); break; case MP_WATER: /* Check for real water tile */ connective = !IsWater(neighbor_tile); break; /* The definitely not connective ones */ default: break; } } /* If the neighbor tile is inconnective, remove the planed road connection to it */ if (!connective) org_rb ^= target_rb; } } return org_rb; }
/** * Gets the other end of the aqueduct, if possible. * @param tile_from The begin tile for the aqueduct. * @param [out] tile_to The tile till where to show a selection for the aqueduct. * @return The other end of the aqueduct, or otherwise a tile in line with the aqueduct to cause the right error message. */ static TileIndex GetOtherAqueductEnd(TileIndex tile_from, TileIndex *tile_to = NULL) { int z; DiagDirection dir = GetInclinedSlopeDirection(GetTileSlope(tile_from, &z)); /* If the direction isn't right, just return the next tile so the command * complains about the wrong slope instead of the ends not matching up. * Make sure the coordinate is always a valid tile within the map, so we * don't go "off" the map. That would cause the wrong error message. */ if (!IsValidDiagDirection(dir)) return TILE_ADDXY(tile_from, TileX(tile_from) > 2 ? -1 : 1, 0); /* Direction the aqueduct is built to. */ TileIndexDiff offset = TileOffsByDiagDir(ReverseDiagDir(dir)); /* The maximum length of the aqueduct. */ int max_length = min(_settings_game.construction.max_bridge_length, DistanceFromEdgeDir(tile_from, ReverseDiagDir(dir)) - 1); TileIndex endtile = tile_from; for (int length = 0; IsValidTile(endtile) && TileX(endtile) != 0 && TileY(endtile) != 0; length++) { endtile = TILE_ADD(endtile, offset); if (length > max_length) break; if (GetTileMaxZ(endtile) > z) { if (tile_to != NULL) *tile_to = endtile; break; } } return endtile; }
void SetDestination(PathFinder* pf, int x, int y) { if (!IsValidTile(pf->map, x, y)) return; pf->endx = x; pf->endy = y; }
void SetOrigin(PathFinder* pf, int x, int y) { if (!IsValidTile(pf->map, x, y)) return; pf->startx = x; pf->starty = y; }
/** * Tests if at least one surrounding tile is non-desert * @param tile tile to check * @return does this tile have at least one non-desert tile around? */ static inline bool NeighbourIsNormal(TileIndex tile) { for (DiagDirection dir = DIAGDIR_BEGIN; dir < DIAGDIR_END; dir++) { TileIndex t = tile + TileOffsByDiagDir(dir); if (!IsValidTile(t)) continue; if (GetTropicZone(t) != TROPICZONE_DESERT) return true; if (HasTileWaterClass(t) && GetWaterClass(t) == WATER_CLASS_SEA) return true; } return false; }
/** * Calculates a hash value for use in the NPF. * @param key1 The TileIndex of the tile to hash * @param key2 The Trackdir of the track on the tile. * * @todo Think of a better hash. */ static uint NPFHash(uint key1, uint key2) { /* TODO: think of a better hash? */ uint part1 = TileX(key1) & NPF_HASH_HALFMASK; uint part2 = TileY(key1) & NPF_HASH_HALFMASK; assert(IsValidTrackdir((Trackdir)key2)); assert(IsValidTile(key1)); return ((part1 << NPF_HASH_HALFBITS | part2) + (NPF_HASH_SIZE * key2 / TRACKDIR_END)) % NPF_HASH_SIZE; }
/*! * 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; if (_docommand_recursive == 0) _error_message = INVALID_STRING_ID; _docommand_recursive++; /* only execute the test call if it's toplevel, or we're not execing. */ if (_docommand_recursive == 1 || !(flags & DC_EXEC) ) { SetTownRatingTestMode(true); res = proc(tile, flags & ~DC_EXEC, p1, p2, text); SetTownRatingTestMode(false); if (CmdFailed(res)) { res.SetGlobalErrorMessage(); goto error; } if (_docommand_recursive == 1 && !(flags & DC_QUERY_COST) && !(flags & DC_BANKRUPT) && res.GetCost() != 0 && !CheckCompanyHasMoney(res)) { 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 */ res = proc(tile, flags, p1, p2, text); if (CmdFailed(res)) { res.SetGlobalErrorMessage(); error: _docommand_recursive--; return CMD_ERROR; } /* if toplevel, subtract the money. */ if (--_docommand_recursive == 0 && !(flags & DC_BANKRUPT)) { SubtractMoneyFromCompany(res); } return res; }
/** * Collect a paste error without calling PastingState::DoCommand or PastingState::CollectCost. * * The function works similary to PastingState::DoCommand and PastingState::CollectCost, * but it only generates an error. After return, call PastingState::IsInterrupted to test whether * the paste operation is allowd to be continued. * * @param tile The tile the error concerns. * @param error_message Error message. * @param error_message Summary error message. * * @see PastingState::IsInterrupted * @see PastingState::CollectCost * @see PastingState::DoCommand */ void PastingState::CollectError(TileIndex tile, StringID error_message, StringID error_summary) { /* store the error only if it is more important then the previous one */ if (GetPasteErrorImportance(error_message) > GetPasteErrorImportance(this->err_message)) { this->err_tile = IsValidTile(tile) ? tile : INVALID_TILE; this->err_message = error_message; this->err_summary = error_summary; CopyOutDParam(this->err_params, 0, lengthof(this->err_params)); } this->last_result = CommandCost(error_message); }
uint16 GetHouseCallback(CallbackID callback, uint32 param1, uint32 param2, HouseID house_id, Town *town, TileIndex tile, bool not_yet_constructed, uint8 initial_random_bits, uint32 watched_cargo_triggers) { assert(IsValidTile(tile) && (not_yet_constructed || IsTileType(tile, MP_HOUSE))); HouseResolverObject object(house_id, tile, town, callback, param1, param2, not_yet_constructed, initial_random_bits, watched_cargo_triggers); const SpriteGroup *group = SpriteGroup::Resolve(HouseSpec::Get(house_id)->grf_prop.spritegroup[0], &object); if (group == NULL) return CALLBACK_FAILED; return group->GetCallbackResult(); }
/** * Create a new custom news item. * @param tile unused * @param flags type of operation * @param p1 various bitstuffed elements * - p1 = (bit 0 - 7) - NewsType of the message. * - p1 = (bit 8 - 15) - NewsReferenceType of first reference. * - p1 = (bit 16 - 23) - Company this news message is for. * @param p2 First reference of the news message. * @param text The text of the news message. * @return the cost of this operation or an error */ CommandCost CmdCustomNewsItem(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { if (_current_company != OWNER_DEITY) return CMD_ERROR; NewsType type = (NewsType)GB(p1, 0, 8); NewsReferenceType reftype1 = (NewsReferenceType)GB(p1, 8, 8); CompanyID company = (CompanyID)GB(p1, 16, 8); if (company != INVALID_OWNER && !Company::IsValidID(company)) return CMD_ERROR; if (type >= NT_END) return CMD_ERROR; if (StrEmpty(text)) return CMD_ERROR; switch (reftype1) { case NR_NONE: break; case NR_TILE: if (!IsValidTile(p2)) return CMD_ERROR; break; case NR_VEHICLE: if (!Vehicle::IsValidID(p2)) return CMD_ERROR; break; case NR_STATION: if (!Station::IsValidID(p2)) return CMD_ERROR; break; case NR_INDUSTRY: if (!Industry::IsValidID(p2)) return CMD_ERROR; break; case NR_TOWN: if (!Town::IsValidID(p2)) return CMD_ERROR; break; case NR_ENGINE: if (!Engine::IsValidID(p2)) return CMD_ERROR; break; default: return CMD_ERROR; } if (company != INVALID_OWNER && company != _local_company) return CommandCost(); if (flags & DC_EXEC) { char *news = stredup(text); SetDParamStr(0, news); AddNewsItem(STR_NEWS_CUSTOM_ITEM, type, NF_NORMAL, reftype1, p2, NR_NONE, UINT32_MAX, news); } return CommandCost(); }
/** * Create a new goal. * @param tile unused. * @param flags type of operation * @param p1 various bitstuffed elements * - p1 = (bit 0 - 7) - GoalType of destination. * - p1 = (bit 8 - 15) - Company for which this goal is. * @param p2 GoalTypeID of destination. * @param text Text of the goal. * @return the cost of this operation or an error */ CommandCost CmdCreateGoal(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { if (!Goal::CanAllocateItem()) return CMD_ERROR; GoalType type = (GoalType)GB(p1, 0, 8); CompanyID company = (CompanyID)GB(p1, 8, 8); if (_current_company != OWNER_DEITY) return CMD_ERROR; if (StrEmpty(text)) return CMD_ERROR; if (company != INVALID_COMPANY && !Company::IsValidID(company)) return CMD_ERROR; switch (type) { case GT_NONE: if (p2 != 0) return CMD_ERROR; break; case GT_TILE: if (!IsValidTile(p2)) return CMD_ERROR; break; case GT_INDUSTRY: if (!Industry::IsValidID(p2)) return CMD_ERROR; break; case GT_TOWN: if (!Town::IsValidID(p2)) return CMD_ERROR; break; case GT_COMPANY: if (!Company::IsValidID(p2)) return CMD_ERROR; break; default: return CMD_ERROR; } if (flags & DC_EXEC) { Goal *g = new Goal(); g->type = type; g->dst = p2; g->company = company; g->text = strdup(text); g->progress = NULL; g->completed = false; InvalidateWindowData(WC_GOALS_LIST, 0); _new_goal_id = g->index; } return CommandCost(); }
/** * Test if a given TileArea is valid. * @return \c true if the area is non-empty and fits inside the map, \c false otherwise. */ static CommandCost ValParamCopyPasteArea(const GenericTileArea &ta) { if (!IsValidTile(ta.tile) || !IsInsideBS(ta.w, 1, _settings_game.construction.clipboard_capacity) || !IsInsideBS(ta.h, 1, _settings_game.construction.clipboard_capacity)) { return CMD_ERROR; } if (TileX(ta.tile) + ta.w > MapMaxX(MapOf(ta.tile)) || TileY(ta.tile) + ta.h > MapMaxY(MapOf(ta.tile))) { return_cmd_error(STR_ERROR_TOO_CLOSE_TO_EDGE_OF_MAP_SUB); } return CommandCost(); }
/** * Is there a tunnel in the way in the given direction? * Between level 0 and 1 terraforming is allowed. (No search) * @param tile the tile to search from. * @param z the 'z' to search on. * @param dir the direction to start searching to. * @return true if and only if there is a tunnel. */ bool IsTunnelInWayDir(TileIndex tile, int z, DiagDirection dir) { /* Between level 0 and 1 terraforming is allowed. */ if (GetTileZ(tile) <= 1) return false; TileIndexDiff delta = TileOffsByDiagDir(dir); int height; do { tile -= delta; if (!IsValidTile(tile)) return false; height = GetTileZ(tile); } while (z < height); return z == height && IsTunnelTile(tile) && GetTunnelBridgeDirection(tile) == dir; }
void Level::SurroundingTiles( std::vector<tile_t>& s, const math::Vector2& tile_position ) { std::vector<tile_t> tile_copy; int tp = static_cast<int>(tile_position.y / tile_height); tp = tp * num_tiles_wide; tp += static_cast<int>(tile_position.x / tile_width); if(tp > tiles.size()-1 || tp < 0 || !tiles[tp].is_walkable) { std::cerr << "invalid tile position " << tp << std::endl; s = tile_copy; return; } for(int i = -1; i < 2; ++i) { int j = -1; int k = 2; //check to see if tile is on the left hand side if (tp % num_tiles_wide == 0) j = 0; //check to see if tile is on the right hand side if (tp % num_tiles_wide == num_tiles_wide - 1) k = 1; for (j; j < k; ++j) { int adj = tp; adj = adj - (i * num_tiles_wide) + j; if (IsValidTile(adj, tp) && adj != tp) { tile_t tile = tiles[adj]; tile_copy.push_back(tile); } } } s = tile_copy; }
/** * This helper for Create/Update PageElement Cmd procedure verifies if the page * element parameters are correct for the given page element type. * @param page_id The page id of the page which the page element (will) belong to * @param type The type of the page element to create/update * @param tile The tile parameter of the DoCommand proc * @param reference The reference parameter of the DoCommand proc (p2) * @param text The text parameter of the DoCommand proc * @return true, if and only if the given parameters are valid for the given page elment type and page id. */ static bool VerifyElementContentParameters(StoryPageID page_id, StoryPageElementType type, TileIndex tile, uint32 reference, const char *text) { switch (type) { case SPET_TEXT: if (StrEmpty(text)) return false; break; case SPET_LOCATION: if (StrEmpty(text)) return false; if (!IsValidTile(tile)) return false; break; case SPET_GOAL: if (!Goal::IsValidID((GoalID)reference)) return false; /* Reject company specific goals on global pages */ if (StoryPage::Get(page_id)->company == INVALID_COMPANY && Goal::Get((GoalID)reference)->company != INVALID_COMPANY) return false; break; default: return false; } return true; }
static void ShipController(Ship *v) { uint32 r; const byte *b; Direction dir; Track track; TrackBits tracks; v->tick_counter++; v->current_order_time++; if (v->HandleBreakdown()) return; if (v->vehstatus & VS_STOPPED) return; ProcessOrders(v); v->HandleLoading(); if (v->current_order.IsType(OT_LOADING)) return; if (CheckShipLeaveDepot(v)) return; v->ShowVisualEffect(); if (!ShipAccelerate(v)) return; GetNewVehiclePosResult gp = GetNewVehiclePos(v); if (v->state != TRACK_BIT_WORMHOLE) { /* Not on a bridge */ if (gp.old_tile == gp.new_tile) { /* Staying in tile */ if (v->IsInDepot()) { gp.x = v->x_pos; gp.y = v->y_pos; } else { /* Not inside depot */ r = VehicleEnterTile(v, gp.new_tile, gp.x, gp.y); if (HasBit(r, VETS_CANNOT_ENTER)) goto reverse_direction; /* A leave station order only needs one tick to get processed, so we can * always skip ahead. */ if (v->current_order.IsType(OT_LEAVESTATION)) { v->current_order.Free(); SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); /* Test if continuing forward would lead to a dead-end, moving into the dock. */ DiagDirection exitdir = VehicleExitDir(v->direction, v->state); TileIndex tile = TileAddByDiagDir(v->tile, exitdir); if (TrackStatusToTrackBits(GetTileTrackStatus(tile, TRANSPORT_WATER, 0, exitdir)) == TRACK_BIT_NONE) goto reverse_direction; } else if (v->dest_tile != 0) { /* We have a target, let's see if we reached it... */ if (v->current_order.IsType(OT_GOTO_WAYPOINT) && DistanceManhattan(v->dest_tile, gp.new_tile) <= 3) { /* We got within 3 tiles of our target buoy, so let's skip to our * next order */ UpdateVehicleTimetable(v, true); v->IncrementRealOrderIndex(); v->current_order.MakeDummy(); } else { /* Non-buoy orders really need to reach the tile */ if (v->dest_tile == gp.new_tile) { if (v->current_order.IsType(OT_GOTO_DEPOT)) { if ((gp.x & 0xF) == 8 && (gp.y & 0xF) == 8) { VehicleEnterDepot(v); return; } } else if (v->current_order.IsType(OT_GOTO_STATION)) { v->last_station_visited = v->current_order.GetDestination(); /* Process station in the orderlist. */ Station *st = Station::Get(v->current_order.GetDestination()); if (st->facilities & FACIL_DOCK) { // ugly, ugly workaround for problem with ships able to drop off cargo at wrong stations ShipArrivesAt(v, st); v->BeginLoading(); } else { // leave stations without docks right aways v->current_order.MakeLeaveStation(); v->IncrementRealOrderIndex(); } } } } } } } else { /* New tile */ if (!IsValidTile(gp.new_tile)) goto reverse_direction; DiagDirection diagdir = DiagdirBetweenTiles(gp.old_tile, gp.new_tile); assert(diagdir != INVALID_DIAGDIR); tracks = GetAvailShipTracks(gp.new_tile, diagdir); if (tracks == TRACK_BIT_NONE) goto reverse_direction; /* Choose a direction, and continue if we find one */ track = ChooseShipTrack(v, gp.new_tile, diagdir, tracks); if (track == INVALID_TRACK) goto reverse_direction; b = _ship_subcoord[diagdir][track]; gp.x = (gp.x & ~0xF) | b[0]; gp.y = (gp.y & ~0xF) | b[1]; /* Call the landscape function and tell it that the vehicle entered the tile */ r = VehicleEnterTile(v, gp.new_tile, gp.x, gp.y); if (HasBit(r, VETS_CANNOT_ENTER)) goto reverse_direction; if (!HasBit(r, VETS_ENTERED_WORMHOLE)) { v->tile = gp.new_tile; v->state = TrackToTrackBits(track); /* Update ship cache when the water class changes. Aqueducts are always canals. */ WaterClass old_wc = GetEffectiveWaterClass(gp.old_tile); WaterClass new_wc = GetEffectiveWaterClass(gp.new_tile); if (old_wc != new_wc) v->UpdateCache(); } v->direction = (Direction)b[2]; } } else { /* On a bridge */ if (!IsTileType(gp.new_tile, MP_TUNNELBRIDGE) || !HasBit(VehicleEnterTile(v, gp.new_tile, gp.x, gp.y), VETS_ENTERED_WORMHOLE)) { v->x_pos = gp.x; v->y_pos = gp.y; v->UpdatePosition(); if ((v->vehstatus & VS_HIDDEN) == 0) v->Vehicle::UpdateViewport(true); return; } } /* update image of ship, as well as delta XY */ v->x_pos = gp.x; v->y_pos = gp.y; v->z_pos = GetSlopePixelZ(gp.x, gp.y); getout: v->UpdatePosition(); v->UpdateViewport(true, true); return; reverse_direction: dir = ReverseDir(v->direction); v->direction = dir; goto getout; }
/** * Create a new goal. * @param tile unused. * @param flags type of operation * @param p1 various bitstuffed elements * - p1 = (bit 0 - 7) - GoalType of destination. * - p1 = (bit 8 - 15) - Company for which this goal is. * @param p2 GoalTypeID of destination. * @param text Text of the goal. * @return the cost of this operation or an error */ CommandCost CmdCreateGoal(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { if (!Goal::CanAllocateItem()) return CMD_ERROR; GoalType type = (GoalType)GB(p1, 0, 8); CompanyID company = (CompanyID)GB(p1, 8, 8); if (_current_company != OWNER_DEITY) return CMD_ERROR; if (StrEmpty(text)) return CMD_ERROR; if (company != INVALID_COMPANY && !Company::IsValidID(company)) return CMD_ERROR; switch (type) { case GT_NONE: if (p2 != 0) return CMD_ERROR; break; case GT_TILE: if (!IsValidTile(p2)) return CMD_ERROR; break; case GT_INDUSTRY: if (!Industry::IsValidID(p2)) return CMD_ERROR; break; case GT_TOWN: if (!Town::IsValidID(p2)) return CMD_ERROR; break; case GT_COMPANY: if (!Company::IsValidID(p2)) return CMD_ERROR; break; case GT_STORY_PAGE: { if (!StoryPage::IsValidID(p2)) return CMD_ERROR; CompanyByte story_company = StoryPage::Get(p2)->company; if (company == INVALID_COMPANY ? story_company != INVALID_COMPANY : story_company != INVALID_COMPANY && story_company != company) return CMD_ERROR; break; } default: return CMD_ERROR; } if (flags & DC_EXEC) { Goal *g = new Goal(); g->type = type; g->dst = p2; g->company = company; g->text = stredup(text); g->progress = NULL; g->completed = false; if (g->company == INVALID_COMPANY) { InvalidateWindowClassesData(WC_GOALS_LIST); } else { InvalidateWindowData(WC_GOALS_LIST, g->company); } if (Goal::GetNumItems() == 1) InvalidateWindowData(WC_MAIN_TOOLBAR, 0); _new_goal_id = g->index; } return CommandCost(); }
void SetTile(Map* map, int x, int y, Tile value) { if (!IsValidTile(map, x, y)) return; map->tiles[x][y] = value; }
/*! * 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 * tile, p1 and p2 are from the #CommandProc function. The paramater cmd is the command to execute. * The parameter 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 true if the command succeeded, else false */ bool DoCommandP(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const char *text, bool my_cmd) { assert(_docommand_recursive == 0); CommandCost res, res2; int x = TileX(tile) * TILE_SIZE; int y = TileY(tile) * TILE_SIZE; _error_message = INVALID_STRING_ID; StringID error_part1 = GB(cmd, 16, 16); _additional_cash_required = 0; /* get pointer to command handler */ byte cmd_id = cmd & CMD_ID_MASK; assert(cmd_id < lengthof(_command_proc_table)); CommandProc *proc = _command_proc_table[cmd_id].proc; if (proc == NULL) return false; /* Command flags are used internally */ uint cmd_flags = GetCommandFlags(cmd); /* Flags get send to the DoCommand */ DoCommandFlag flags = CommandFlagsToDCFlags(cmd_flags); /* Do not even think about executing out-of-bounds tile-commands */ if (tile != 0 && (tile >= MapSize() || (!IsValidTile(tile) && (cmd_flags & CMD_ALL_TILES) == 0))) return false; /* Always execute server and spectator commands as spectator */ if (cmd_flags & (CMD_SPECTATOR | CMD_SERVER)) _current_company = COMPANY_SPECTATOR; CompanyID old_company = _current_company; /* If the company isn't valid it may only do server command or start a new company! * The server will ditch any server commands a client sends to it, so effectively * this guards the server from executing functions for an invalid company. */ if (_game_mode == GM_NORMAL && (cmd_flags & (CMD_SPECTATOR | CMD_SERVER)) == 0 && !Company::IsValidID(_current_company)) { if (my_cmd) ShowErrorMessage(error_part1, _error_message, x, y); return false; } bool notest = (cmd_flags & CMD_NO_TEST) != 0; _docommand_recursive = 1; /* cost estimation only? */ if (!IsGeneratingWorld() && _shift_pressed && IsLocalCompany() && !(cmd & CMD_NETWORK_COMMAND) && cmd_id != CMD_PAUSE) { /* estimate the cost. */ SetTownRatingTestMode(true); res = proc(tile, flags, p1, p2, text); SetTownRatingTestMode(false); if (CmdFailed(res)) { res.SetGlobalErrorMessage(); ShowErrorMessage(error_part1, _error_message, x, y); } else { ShowEstimatedCostOrIncome(res.GetCost(), x, y); } _docommand_recursive = 0; ClearStorageChanges(false); return false; } if (!((cmd & CMD_NO_TEST_IF_IN_NETWORK) && _networking)) { /* first test if the command can be executed. */ SetTownRatingTestMode(true); res = proc(tile, flags, p1, p2, text); SetTownRatingTestMode(false); assert(cmd_id == CMD_COMPANY_CTRL || old_company == _current_company); if (CmdFailed(res)) { res.SetGlobalErrorMessage(); goto show_error; } /* no money? Only check if notest is off */ if (!notest && res.GetCost() != 0 && !CheckCompanyHasMoney(res)) goto show_error; } #ifdef ENABLE_NETWORK /* * If we are in network, and the command is not from the network * send it to the command-queue and abort execution */ if (_networking && !(cmd & CMD_NETWORK_COMMAND)) { NetworkSend_Command(tile, p1, p2, cmd & ~CMD_FLAGS_MASK, callback, text); _docommand_recursive = 0; ClearStorageChanges(false); return true; } #endif /* ENABLE_NETWORK */ DEBUG(desync, 1, "cmd: %08x; %08x; %1x; %06x; %08x; %08x; %04x; %s\n", _date, _date_fract, (int)_current_company, tile, p1, p2, cmd & ~CMD_NETWORK_COMMAND, text); /* update last build coordinate of company. */ if (tile != 0) { Company *c = Company::GetIfValid(_current_company); if (c != NULL) c->last_build_coordinate = tile; } /* Actually try and execute the command. If no cost-type is given * use the construction one */ res2 = proc(tile, flags | DC_EXEC, p1, p2, text); assert(cmd_id == CMD_COMPANY_CTRL || old_company == _current_company); /* If notest is on, it means the result of the test can be different than * the real command.. so ignore the test */ if (!notest && !((cmd & CMD_NO_TEST_IF_IN_NETWORK) && _networking)) { assert(res.GetCost() == res2.GetCost() && CmdFailed(res) == CmdFailed(res2)); // sanity check } else { if (CmdFailed(res2)) { res2.SetGlobalErrorMessage(); goto show_error; } } SubtractMoneyFromCompany(res2); /* update signals if needed */ UpdateSignalsInBuffer(); if (IsLocalCompany() && _game_mode != GM_EDITOR) { if (res2.GetCost() != 0 && tile != 0) ShowCostOrIncomeAnimation(x, y, GetSlopeZ(x, y), res2.GetCost()); if (_additional_cash_required != 0) { SetDParam(0, _additional_cash_required); if (my_cmd) ShowErrorMessage(error_part1, STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY, x, y); if (res2.GetCost() == 0) goto callb_err; } } _docommand_recursive = 0; if (callback) callback(true, tile, p1, p2); ClearStorageChanges(true); return true; show_error: /* show error message if the command fails? */ if (IsLocalCompany() && error_part1 != 0 && my_cmd) { ShowErrorMessage(error_part1, _error_message, x, y); } callb_err: _docommand_recursive = 0; if (callback) callback(false, tile, p1, p2); ClearStorageChanges(false); return false; }
Tile GetTile(Map* map, int x, int y) { if (!IsValidTile(map, x, y)) return Invalid; return map->tiles[x][y]; }
/*! * Helper function for the toplevel network safe docommand function for the current company. * * @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) * @param estimate_only whether to give only the estimate or also execute the command * @return the command cost of this function. */ CommandCost DoCommandPInternal(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const char *text, bool my_cmd, bool estimate_only) { /* Prevent recursion; it gives a mess over the network */ assert(_docommand_recursive == 0); _docommand_recursive = 1; /* Reset the state. */ _additional_cash_required = 0; /* Get pointer to command handler */ byte cmd_id = cmd & CMD_ID_MASK; assert(cmd_id < lengthof(_command_proc_table)); CommandProc *proc = _command_proc_table[cmd_id].proc; /* Shouldn't happen, but you never know when someone adds * NULLs to the _command_proc_table. */ assert(proc != NULL); /* Command flags are used internally */ CommandFlags cmd_flags = GetCommandFlags(cmd); /* Flags get send to the DoCommand */ DoCommandFlag flags = CommandFlagsToDCFlags(cmd_flags); #ifdef ENABLE_NETWORK /* Make sure p2 is properly set to a ClientID. */ assert(!(cmd_flags & CMD_CLIENT_ID) || p2 != 0); #endif /* Do not even think about executing out-of-bounds tile-commands */ if (tile != 0 && (tile >= MapSize() || (!IsValidTile(tile) && (cmd_flags & CMD_ALL_TILES) == 0))) return_dcpi(CMD_ERROR, false); /* Always execute server and spectator commands as spectator */ bool exec_as_spectator = (cmd_flags & (CMD_SPECTATOR | CMD_SERVER)) != 0; /* If the company isn't valid it may only do server command or start a new company! * The server will ditch any server commands a client sends to it, so effectively * this guards the server from executing functions for an invalid company. */ if (_game_mode == GM_NORMAL && !exec_as_spectator && !Company::IsValidID(_current_company) && !(_current_company == OWNER_DEITY && (cmd_flags & CMD_DEITY) != 0)) { return_dcpi(CMD_ERROR, false); } Backup<CompanyByte> cur_company(_current_company, FILE_LINE); if (exec_as_spectator) cur_company.Change(COMPANY_SPECTATOR); bool test_and_exec_can_differ = (cmd_flags & CMD_NO_TEST) != 0; /* Test the command. */ _cleared_object_areas.Clear(); SetTownRatingTestMode(true); ClearStorageChanges(false); CommandCost res = proc(tile, flags, p1, p2, text); SetTownRatingTestMode(false); /* Make sure we're not messing things up here. */ assert(exec_as_spectator ? _current_company == COMPANY_SPECTATOR : cur_company.Verify()); /* If the command fails, we're doing an estimate * or the player does not have enough money * (unless it's a command where the test and * execution phase might return different costs) * we bail out here. */ if (res.Failed() || estimate_only || (!test_and_exec_can_differ && !CheckCompanyHasMoney(res))) { if (!_networking || _generating_world || (cmd & CMD_NETWORK_COMMAND) != 0) { /* Log the failed command as well. Just to be able to be find * causes of desyncs due to bad command test implementations. */ DEBUG(desync, 1, "cmdf: %08x; %02x; %02x; %06x; %08x; %08x; %08x; \"%s\" (%s)", _date, _date_fract, (int)_current_company, tile, p1, p2, cmd & ~CMD_NETWORK_COMMAND, text, GetCommandName(cmd)); } cur_company.Restore(); return_dcpi(res, false); } #ifdef ENABLE_NETWORK /* * If we are in network, and the command is not from the network * send it to the command-queue and abort execution */ if (_networking && !_generating_world && !(cmd & CMD_NETWORK_COMMAND)) { NetworkSendCommand(tile, p1, p2, cmd & ~CMD_FLAGS_MASK, callback, text, _current_company); cur_company.Restore(); /* Don't return anything special here; no error, no costs. * This way it's not handled by DoCommand and only the * actual execution of the command causes messages. Also * reset the storages as we've not executed the command. */ return_dcpi(CommandCost(), false); } #endif /* ENABLE_NETWORK */ DEBUG(desync, 1, "cmd: %08x; %02x; %02x; %06x; %08x; %08x; %08x; \"%s\" (%s)", _date, _date_fract, (int)_current_company, tile, p1, p2, cmd & ~CMD_NETWORK_COMMAND, text, GetCommandName(cmd)); /* Actually try and execute the command. If no cost-type is given * use the construction one */ _cleared_object_areas.Clear(); ClearStorageChanges(false); CommandCost res2 = proc(tile, flags | DC_EXEC, p1, p2, text); if (cmd_id == CMD_COMPANY_CTRL) { cur_company.Trash(); /* We are a new company -> Switch to new local company. * We were closed down -> Switch to spectator * Some other company opened/closed down -> The outside function will switch back */ _current_company = _local_company; } else { /* Make sure nothing bad happened, like changing the current company. */ assert(exec_as_spectator ? _current_company == COMPANY_SPECTATOR : cur_company.Verify()); cur_company.Restore(); } /* If the test and execution can differ we have to check the * return of the command. Otherwise we can check whether the * test and execution have yielded the same result, * i.e. cost and error state are the same. */ if (!test_and_exec_can_differ) { assert(res.GetCost() == res2.GetCost() && res.Failed() == res2.Failed()); // sanity check } else if (res2.Failed()) { return_dcpi(res2, false); } /* If we're needing more money and we haven't done * anything yet, ask for the money! */ if (_additional_cash_required != 0 && res2.GetCost() == 0) { /* It could happen we removed rail, thus gained money, and deleted something else. * So make sure the signal buffer is empty even in this case */ UpdateSignalsInBuffer(); SetDParam(0, _additional_cash_required); return_dcpi(CommandCost(STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY), false); } /* update last build coordinate of company. */ if (tile != 0) { Company *c = Company::GetIfValid(_current_company); if (c != NULL) c->last_build_coordinate = tile; } SubtractMoneyFromCompany(res2); /* update signals if needed */ UpdateSignalsInBuffer(); return_dcpi(res2, true); }
/* virtual */ void HouseScopeResolver::SetTriggers(int triggers) const { assert(!this->not_yet_constructed && IsValidTile(this->tile) && IsTileType(this->tile, MP_HOUSE)); SetHouseTriggers(this->tile, triggers); }
/* virtual */ uint32 HouseScopeResolver::GetTriggers() const { /* Note: Towns build houses over houses. So during construction checks 'tile' may be a valid but unrelated house. */ assert(IsValidTile(this->tile) && (this->not_yet_constructed || IsTileType(this->tile, MP_HOUSE))); return this->not_yet_constructed ? 0 : GetHouseTriggers(this->tile); }
/** * Marks tile dirty if it is a canal or river tile. * Called to avoid glitches when flooding tiles next to canal tile. * * @param tile tile to check */ static inline void MarkTileDirtyIfCanalOrRiver(TileIndex tile) { if (IsValidTile(tile) && IsTileType(tile, MP_WATER) && (IsCanal(tile) || IsRiver(tile))) MarkTileDirtyByTile(tile); }