/** * Get the EngineID of the replacement for a vehicle * @param v The vehicle to find a replacement for * @param c The vehicle's owner (it's faster to forward the pointer than refinding it) * @param [out] e the EngineID of the replacement. INVALID_ENGINE if no replacement is found * @return Error if the engine to build is not available */ static CommandCost GetNewEngineType(const Vehicle *v, const Company *c, EngineID &e) { assert(v->type != VEH_TRAIN || !v->IsArticulatedPart()); e = INVALID_ENGINE; if (v->type == VEH_TRAIN && Train::From(v)->IsRearDualheaded()) { /* we build the rear ends of multiheaded trains with the front ones */ return CommandCost(); } e = EngineReplacementForCompany(c, v->engine_type, v->group_id); /* Autoreplace, if engine is available */ if (e != INVALID_ENGINE && IsEngineBuildable(e, v->type, _current_company)) { return CommandCost(); } /* Autorenew if needed */ if (v->NeedsAutorenewing(c)) e = v->engine_type; /* Nothing to do or all is fine? */ if (e == INVALID_ENGINE || IsEngineBuildable(e, v->type, _current_company)) return CommandCost(); /* The engine we need is not available. Report error to user */ return CommandCost(STR_ERROR_RAIL_VEHICLE_NOT_AVAILABLE + v->type); }
/* Figure out what train EngineIDs to put in the list */ void GenerateBuildTrainList() { EngineID sel_id = INVALID_ENGINE; int num_engines = 0; int num_wagons = 0; this->filter.railtype = (this->listview_mode) ? RAILTYPE_END : GetRailType(this->window_number); this->eng_list.Clear(); /* Make list of all available train engines and wagons. * Also check to see if the previously selected engine is still available, * and if not, reset selection to INVALID_ENGINE. This could be the case * when engines become obsolete and are removed */ const Engine *e; FOR_ALL_ENGINES_OF_TYPE(e, VEH_TRAIN) { EngineID eid = e->index; const RailVehicleInfo *rvi = &e->u.rail; if (this->filter.railtype != RAILTYPE_END && !HasPowerOnRail(rvi->railtype, this->filter.railtype)) continue; if (!IsEngineBuildable(eid, VEH_TRAIN, _local_company)) continue; /* Filter now! So num_engines and num_wagons is valid */ if (!FilterSingleEngine(eid)) continue; *this->eng_list.Append() = eid; if (rvi->railveh_type != RAILVEH_WAGON) { num_engines++; } else { num_wagons++; } if (eid == this->sel_engine) sel_id = eid; }
/** * Checks some basic properties whether autoreplace is allowed * @param from Origin engine * @param to Destination engine * @param company Company to check for * @return true if autoreplace is allowed */ bool CheckAutoreplaceValidity(EngineID from, EngineID to, CompanyID company) { assert(Engine::IsValidID(from) && Engine::IsValidID(to)); /* we can't replace an engine into itself (that would be autorenew) */ if (from == to) return false; const Engine *e_from = Engine::Get(from); const Engine *e_to = Engine::Get(to); VehicleType type = e_from->type; /* check that the new vehicle type is available to the company and its type is the same as the original one */ if (!IsEngineBuildable(to, type, company)) return false; switch (type) { case VEH_TRAIN: { /* make sure the railtypes are compatible */ if ((GetRailTypeInfo(e_from->u.rail.railtype)->compatible_railtypes & GetRailTypeInfo(e_to->u.rail.railtype)->compatible_railtypes) == 0) return false; /* make sure we do not replace wagons with engines or vise versa */ if ((e_from->u.rail.railveh_type == RAILVEH_WAGON) != (e_to->u.rail.railveh_type == RAILVEH_WAGON)) return false; break; } case VEH_ROAD: /* make sure that we do not replace a tram with a normal road vehicles or vise versa */ if (HasBit(e_from->info.misc_flags, EF_ROAD_TRAM) != HasBit(e_to->info.misc_flags, EF_ROAD_TRAM)) return false; break; case VEH_AIRCRAFT: /* make sure that we do not replace a plane with a helicopter or vise versa */ if ((e_from->u.air.subtype & AIR_CTOL) != (e_to->u.air.subtype & AIR_CTOL)) return false; break; default: break; } /* the engines needs to be able to carry the same cargo */ return EnginesHaveCargoInCommon(from, to); }
/** * Build a vehicle. * @param tile tile of depot where the vehicle is built * @param flags for command * @param p1 various bitstuffed data * bits 0-15: vehicle type being built. * bits 16-31: vehicle type specific bits passed on to the vehicle build functions. * @param p2 User * @param text unused * @return the cost of this operation or an error */ CommandCost CmdBuildVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { /* Elementary check for valid location. */ if (!IsDepotTile(tile) || !IsTileOwner(tile, _current_company)) return CMD_ERROR; VehicleType type; switch (GetTileType(tile)) { case MP_RAILWAY: type = VEH_TRAIN; break; case MP_ROAD: type = VEH_ROAD; break; case MP_WATER: type = VEH_SHIP; break; case MP_STATION: type = VEH_AIRCRAFT; break; default: NOT_REACHED(); // Safe due to IsDepotTile() } /* Validate the engine type. */ EngineID eid = GB(p1, 0, 16); if (!IsEngineBuildable(eid, type, _current_company)) return_cmd_error(STR_ERROR_RAIL_VEHICLE_NOT_AVAILABLE + type); const Engine *e = Engine::Get(eid); CommandCost value(EXPENSES_NEW_VEHICLES, e->GetCost()); /* Engines without valid cargo should not be available */ if (e->GetDefaultCargoType() == CT_INVALID) return CMD_ERROR; /* Check whether the number of vehicles we need to build can be built according to pool space. */ uint num_vehicles; switch (type) { case VEH_TRAIN: num_vehicles = (e->u.rail.railveh_type == RAILVEH_MULTIHEAD ? 2 : 1) + CountArticulatedParts(eid, false); break; case VEH_ROAD: num_vehicles = 1 + CountArticulatedParts(eid, false); break; case VEH_SHIP: num_vehicles = 1; break; case VEH_AIRCRAFT: num_vehicles = e->u.air.subtype & AIR_CTOL ? 2 : 3; break; default: NOT_REACHED(); // Safe due to IsDepotTile() } if (!Vehicle::CanAllocateItem(num_vehicles)) return_cmd_error(STR_ERROR_TOO_MANY_VEHICLES_IN_GAME); /* Check whether we can allocate a unit number. Autoreplace does not allocate * an unit number as it will (always) reuse the one of the replaced vehicle * and (train) wagons don't have an unit number in any scenario. */ UnitID unit_num = (flags & DC_AUTOREPLACE || (type == VEH_TRAIN && e->u.rail.railveh_type == RAILVEH_WAGON)) ? 0 : GetFreeUnitNumber(type); if (unit_num == UINT16_MAX) return_cmd_error(STR_ERROR_TOO_MANY_VEHICLES_IN_GAME); Vehicle *v; switch (type) { case VEH_TRAIN: value.AddCost(CmdBuildRailVehicle(tile, flags, e, GB(p1, 16, 16), &v)); break; case VEH_ROAD: value.AddCost(CmdBuildRoadVehicle(tile, flags, e, GB(p1, 16, 16), &v)); break; case VEH_SHIP: value.AddCost(CmdBuildShip (tile, flags, e, GB(p1, 16, 16), &v)); break; case VEH_AIRCRAFT: value.AddCost(CmdBuildAircraft (tile, flags, e, GB(p1, 16, 16), &v)); break; default: NOT_REACHED(); // Safe due to IsDepotTile() } if (value.Succeeded() && flags & DC_EXEC) { v->unitnumber = unit_num; v->value = value.GetCost(); InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); InvalidateWindowClassesData(GetWindowClassForVehicleType(type), 0); SetWindowDirty(WC_COMPANY, _current_company); if (IsLocalCompany()) { InvalidateAutoreplaceWindow(v->engine_type, v->group_id); // updates the auto replace window (must be called before incrementing num_engines) } GroupStatistics::CountEngine(v, 1); GroupStatistics::UpdateAutoreplace(_current_company); if (v->IsPrimaryVehicle()) { GroupStatistics::CountVehicle(v, 1); OrderBackup::Restore(v, p2); } } return value; }
/** Build a ship. * @param tile tile of depot where ship is built * @param flags type of operation * @param p1 ship type being built (engine) * @param p2 unused * @param text unused * @return the cost of this operation or an error */ CommandCost CmdBuildShip(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { UnitID unit_num; if (!IsEngineBuildable(p1, VEH_SHIP, _current_company)) return_cmd_error(STR_ERROR_SHIP_NOT_AVAILABLE); const Engine *e = Engine::Get(p1); CommandCost value(EXPENSES_NEW_VEHICLES, e->GetCost()); /* Engines without valid cargo should not be available */ if (e->GetDefaultCargoType() == CT_INVALID) return CMD_ERROR; if (flags & DC_QUERY_COST) return value; /* The ai_new queries the vehicle cost before building the route, * so we must check against cheaters no sooner than now. --pasky */ if (!IsShipDepotTile(tile)) return CMD_ERROR; if (!IsTileOwner(tile, _current_company)) return CMD_ERROR; unit_num = (flags & DC_AUTOREPLACE) ? 0 : GetFreeUnitNumber(VEH_SHIP); if (!Vehicle::CanAllocateItem() || unit_num > _settings_game.vehicle.max_ships) return_cmd_error(STR_ERROR_TOO_MANY_VEHICLES_IN_GAME); if (flags & DC_EXEC) { int x; int y; const ShipVehicleInfo *svi = &e->u.ship; Ship *v = new Ship(); v->unitnumber = unit_num; v->owner = _current_company; v->tile = tile; x = TileX(tile) * TILE_SIZE + TILE_SIZE / 2; y = TileY(tile) * TILE_SIZE + TILE_SIZE / 2; v->x_pos = x; v->y_pos = y; v->z_pos = GetSlopeZ(x, y); v->UpdateDeltaXY(v->direction); v->vehstatus = VS_HIDDEN | VS_STOPPED | VS_DEFPAL; v->spritenum = svi->image_index; v->cargo_type = e->GetDefaultCargoType(); v->cargo_cap = svi->capacity; v->value = value.GetCost(); v->last_station_visited = INVALID_STATION; v->max_speed = svi->max_speed; v->engine_type = p1; v->reliability = e->reliability; v->reliability_spd_dec = e->reliability_spd_dec; v->max_age = e->GetLifeLengthInDays(); _new_vehicle_id = v->index; v->state = TRACK_BIT_DEPOT; v->service_interval = Company::Get(_current_company)->settings.vehicle.servint_ships; v->date_of_last_service = _date; v->build_year = _cur_year; v->cur_image = SPR_IMG_QUERY; v->random_bits = VehicleRandomBits(); if (e->flags & ENGINE_EXCLUSIVE_PREVIEW) SetBit(v->vehicle_flags, VF_BUILT_AS_PROTOTYPE); v->InvalidateNewGRFCacheOfChain(); v->cargo_cap = GetVehicleCapacity(v); v->InvalidateNewGRFCacheOfChain(); VehicleMove(v, false); InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); InvalidateWindowClassesData(WC_SHIPS_LIST, 0); SetWindowDirty(WC_COMPANY, v->owner); if (IsLocalCompany()) { InvalidateAutoreplaceWindow(v->engine_type, v->group_id); // updates the replace Ship window } Company::Get(_current_company)->num_engines[p1]++; } return value; }