/** 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 || !CheckOwnership(v->owner)) return CMD_ERROR; 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; }
/** * 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(); }
TInt CCallDummyBase::AcquireOwnership(const TTsyReqHandle aTsyReqHandle) // // Acquire call Ownership // { TInt ret(KErrNone); if (CheckOwnership(aTsyReqHandle)==CCallBase::EOwnedUnowned) { SetOwnership(aTsyReqHandle); ReqCompleted(aTsyReqHandle,KErrNone); } else { if(iList->iAcquireList.IsEmpty()) // List is empty. Client is the first one to request ownership of the call. { CAcquireEntry* entry=NULL; TRAP(ret, entry=CAcquireEntry::NewL(aTsyReqHandle)); if (ret==KErrNone) iList->iAcquireList.AddLast(*entry); } else // List is not empty. Another client has already requested to acquire ownership of the call. // Only one client can be waiting to acquire ownership at any one time. return KErrInUse; } return ret; }
TInt CCallDummyBase::TransferOwnership(const TTsyReqHandle aTsyReqHandle) // // Transfer Call Ownership // { if (CheckOwnership(aTsyReqHandle)!=CCallBase::EOwnedTrue) { ReqCompleted(aTsyReqHandle,KErrEtelNotCallOwner); return KErrNone; } if(iList->iAcquireList.IsEmpty()) // no one interested in this call ! { ReqCompleted(aTsyReqHandle,KErrEtelNoClientInterestedInThisCall); return KErrNone; } CAcquireEntry* entry=iList->iAcquireList.First(); if (entry) // someone interested in this call { SetOwnership(entry->iTsyReqHandle); ReqCompleted(entry->iTsyReqHandle,KErrNone); iList->Remove(entry); ReqCompleted(aTsyReqHandle,KErrNone); } return KErrNone; }
/** * 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(); }
/** * 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(); }
/** * 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(); }
TInt CCallDummyBase::Dial(const TTsyReqHandle aTsyReqHandle,const TDesC8* /*aParams*/,TDesC* aNumber) { if (CheckOwnership(aTsyReqHandle)==CCallBase::EOwnedUnowned) { SetOwnership(aTsyReqHandle); if(aNumber->Compare(DACQ_PHONE_NUMBER_TO_DIAL)!=KErrNone) TsyPanic(KTsyPanicDataCorrupted); iFac->QueueTimer(iDial,aTsyReqHandle,DACQ_ASYN_TIMEOUT, CCallDummyBase::DialHandler,this); } else if (CheckOwnership(aTsyReqHandle)==CCallBase::EOwnedFalse) ReqCompleted(aTsyReqHandle,KErrEtelNotCallOwner); else ReqCompleted(aTsyReqHandle,KErrEtelCallAlreadyActive); return KErrNone; }
TInt CCallDummyBase::HangUp(const TTsyReqHandle aTsyReqHandle) { if (CheckOwnership(aTsyReqHandle)==CCallBase::EOwnedTrue) { iFac->QueueTimer(iHangUp,aTsyReqHandle,DACQ_ASYN_TIMEOUT, CCallDummyBase::HangUpHandler,this); return KErrNone; } else return KErrEtelNotCallOwner; }
/** * 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(); }
/** * Set the start date of the timetable. * @param tile Not used. * @param flags Operation to perform. * @param p2 Various bitstuffed elements * - p2 = (bit 0-19) - Vehicle ID. * - p2 = (bit 20) - Set to 1 to set timetable start for all vehicles sharing this order * @param p2 The timetable start date. * @param text Not used. * @return The error or cost of the operation. */ CommandCost CmdSetTimetableStart(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { bool timetable_all = HasBit(p1, 20); Vehicle *v = Vehicle::GetIfValid(GB(p1, 0, 20)); if (v == NULL || !v->IsPrimaryVehicle() || v->orders.list == NULL) 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 (timetable_all && !v->orders.list->IsCompleteTimetable()) return CMD_ERROR; if (flags & DC_EXEC) { SmallVector<Vehicle *, 8> vehs; if (timetable_all) { for (Vehicle *w = v->orders.list->GetFirstSharedVehicle(); w != NULL; w = w->NextShared()) { *vehs.Append() = w; } } else { *vehs.Append() = v; } int total_duration = v->orders.list->GetTimetableTotalDuration(); int num_vehs = vehs.Length(); if (num_vehs >= 2) { QSortT(vehs.Begin(), vehs.Length(), &VehicleTimetableSorter); } int base = vehs.FindIndex(v); for (Vehicle **viter = vehs.Begin(); viter != vehs.End(); viter++) { int idx = (viter - vehs.Begin()) - base; Vehicle *w = *viter; w->lateness_counter = 0; ClrBit(w->vehicle_flags, VF_TIMETABLE_STARTED); /* Do multiplication, then division to reduce rounding errors. */ w->timetable_start = start_date + idx * total_duration / num_vehs / DAY_TICKS; SetWindowDirty(WC_VEHICLE_TIMETABLE, w->index); } } 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 new service interval * @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 || !CheckOwnership(v->owner)) return CMD_ERROR; uint16 serv_int = GetServiceIntervalClamped(p2, v->owner); // Double check the service interval from the user-input if (serv_int != p2) return CMD_ERROR; if (flags & DC_EXEC) { v->service_interval = serv_int; SetWindowDirty(WC_VEHICLE_DETAILS, v->index); } return CommandCost(); }
/** Start/Stop a vehicle * @param tile unused * @param flags type of operation * @param p1 vehicle to start/stop * @param p2 bit 0: Shall the start/stop newgrf callback be evaluated (only valid with DC_AUTOREPLACE for network safety) * @param text unused * @return the cost of this operation or an error */ CommandCost CmdStartStopVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { /* Disable the effect of p2 bit 0, when DC_AUTOREPLACE is not set */ if ((flags & DC_AUTOREPLACE) == 0) SetBit(p2, 0); Vehicle *v = Vehicle::GetIfValid(p1); if (v == NULL || !CheckOwnership(v->owner) || !v->IsPrimaryVehicle()) return CMD_ERROR; switch (v->type) { case VEH_TRAIN: if ((v->vehstatus & VS_STOPPED) && Train::From(v)->tcache.cached_power == 0) return_cmd_error(STR_ERROR_TRAIN_START_NO_CATENARY); break; case VEH_SHIP: case VEH_ROAD: break; case VEH_AIRCRAFT: { Aircraft *a = Aircraft::From(v); /* cannot stop airplane when in flight, or when taking off / landing */ if (a->state >= STARTTAKEOFF && a->state < TERM7) return_cmd_error(STR_ERROR_AIRCRAFT_IS_IN_FLIGHT); } break; default: return CMD_ERROR; } /* Check if this vehicle can be started/stopped. The callback will fail or * return 0xFF if it can. */ uint16 callback = GetVehicleCallback(CBID_VEHICLE_START_STOP_CHECK, 0, 0, v->engine_type, v); if (callback != CALLBACK_FAILED && GB(callback, 0, 8) != 0xFF && HasBit(p2, 0)) { StringID error = GetGRFStringID(GetEngineGRFID(v->engine_type), 0xD000 + callback); return_cmd_error(error); } if (flags & DC_EXEC) { if (v->IsStoppedInDepot() && (flags & DC_AUTOREPLACE) == 0) DeleteVehicleNews(p1, STR_NEWS_TRAIN_IS_WAITING + v->type); v->vehstatus ^= VS_STOPPED; if (v->type != VEH_TRAIN) v->cur_speed = 0; // trains can stop 'slowly' v->MarkDirty(); SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH); SetWindowDirty(WC_VEHICLE_DEPOT, v->tile); SetWindowClassesDirty(GetWindowClassForVehicleType(v->type)); } return CommandCost(); }
/** * Clear the lateness counter to make the vehicle on time. * @param tile Not used. * @param flags Operation to perform. * @param p1 Various bitstuffed elements * - p1 = (bit 0-19) - Vehicle with the orders to change. * @param p2 unused * @param text unused * @return the cost of this operation or an error */ CommandCost CmdSetVehicleOnTime(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { VehicleID veh = GB(p1, 0, 20); Vehicle *v = Vehicle::GetIfValid(veh); if (v == NULL || !v->IsPrimaryVehicle()) return CMD_ERROR; CommandCost ret = CheckOwnership(v->owner); if (ret.Failed()) return ret; if (flags & DC_EXEC) { v->lateness_counter = 0; SetWindowDirty(WC_VEHICLE_TIMETABLE, v->index); } return CommandCost(); }
/** * 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(); }
/** Sell a ship. * @param tile unused * @param flags type of operation * @param p1 vehicle ID to be sold * @param p2 unused * @param text unused * @return the cost of this operation or an error */ CommandCost CmdSellShip(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { Ship *v = Ship::GetIfValid(p1); if (v == NULL || !CheckOwnership(v->owner)) return CMD_ERROR; if (v->vehstatus & VS_CRASHED) return_cmd_error(STR_ERROR_CAN_T_SELL_DESTROYED_VEHICLE); if (!v->IsStoppedInDepot()) { return_cmd_error(STR_ERROR_SHIP_MUST_BE_STOPPED_IN_DEPOT); } CommandCost ret(EXPENSES_NEW_VEHICLES, -v->value); if (flags & DC_EXEC) { delete v; } return ret; }
/** * Start or stop filling the timetable automatically from the time the vehicle * actually takes to complete it. When starting to autofill the current times * are cleared and the timetable will start again from scratch. * @param tile Not used. * @param flags Operation to perform. * @param p1 Vehicle index. * @param p2 Various bitstuffed elements * - p2 = (bit 0) - Set to 1 to enable, 0 to disable autofill. * - p2 = (bit 1) - Set to 1 to preserve waiting times in non-destructive mode * @param text unused * @return the cost of this operation or an error */ CommandCost CmdAutofillTimetable(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; if (flags & DC_EXEC) { if (HasBit(p2, 0)) { /* Start autofilling the timetable, which clears the * "timetable has started" bit. Times are not cleared anymore, but are * overwritten when the order is reached now. */ SetBit(v->vehicle_flags, VF_AUTOFILL_TIMETABLE); ClrBit(v->vehicle_flags, VF_TIMETABLE_STARTED); /* Overwrite waiting times only if they got longer */ if (HasBit(p2, 1)) SetBit(v->vehicle_flags, VF_AUTOFILL_PRES_WAIT_TIME); v->timetable_start = 0; v->lateness_counter = 0; } else { ClrBit(v->vehicle_flags, VF_AUTOFILL_TIMETABLE); ClrBit(v->vehicle_flags, VF_AUTOFILL_PRES_WAIT_TIME); } } for (Vehicle *v2 = v->FirstShared(); v2 != NULL; v2 = v2->NextShared()) { if (v2 != v) { /* Stop autofilling; only one vehicle at a time can perform autofill */ ClrBit(v2->vehicle_flags, VF_AUTOFILL_TIMETABLE); ClrBit(v2->vehicle_flags, VF_AUTOFILL_PRES_WAIT_TIME); } SetWindowDirty(WC_VEHICLE_TIMETABLE, v2->index); } return CommandCost(); }
virtual void Unlock( __in KIRQL PreviousIrql ) { CheckOwnership(); if (m_RecursionCount > 0) { m_RecursionCount--; } else { m_OwnerThread = NULL; if (m_Verifier != NULL) { m_Verifier->Unlock(PreviousIrql, FALSE); } else { m_Lock.Release(PreviousIrql); } } }
/** Give a custom name to your vehicle * @param tile unused * @param flags type of operation * @param p1 vehicle ID to name * @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 CmdRenameVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { Vehicle *v = Vehicle::GetIfValid(p1); if (v == NULL || !CheckOwnership(v->owner)) return CMD_ERROR; bool reset = StrEmpty(text); if (!reset) { if (strlen(text) >= MAX_LENGTH_VEHICLE_NAME_BYTES) return CMD_ERROR; if (!(flags & DC_AUTOREPLACE) && !IsUniqueVehicleName(text)) return_cmd_error(STR_ERROR_NAME_MUST_BE_UNIQUE); } if (flags & DC_EXEC) { free(v->name); v->name = reset ? NULL : strdup(text); InvalidateWindowClassesData(WC_TRAINS_LIST, 1); MarkWholeScreenDirty(); } return CommandCost(); }
/** * Sell a vehicle. * @param tile unused. * @param flags for command. * @param p1 various bitstuffed data. * bits 0-19: vehicle ID being sold. * bits 20-30: vehicle type specific bits passed on to the vehicle build functions. * bit 31: make a backup of the vehicle's order (if an engine). * @param p2 User. * @param text unused. * @return the cost of this operation or an error. */ CommandCost CmdSellVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { Vehicle *v = Vehicle::GetIfValid(GB(p1, 0, 20)); if (v == NULL) return CMD_ERROR; Vehicle *front = v->First(); CommandCost ret = CheckOwnership(front->owner); if (ret.Failed()) return ret; if (front->vehstatus & VS_CRASHED) return_cmd_error(STR_ERROR_VEHICLE_IS_DESTROYED); if (!front->IsStoppedInDepot()) return_cmd_error(STR_ERROR_TRAIN_MUST_BE_STOPPED_INSIDE_DEPOT + front->type); /* Can we actually make the order backup, i.e. are there enough orders? */ if (p1 & MAKE_ORDER_BACKUP_FLAG && front->orders.list != NULL && !front->orders.list->IsShared() && !Order::CanAllocateItem(front->orders.list->GetNumOrders())) { /* Only happens in exceptional cases when there aren't enough orders anyhow. * Thus it should be safe to just drop the orders in that case. */ p1 &= ~MAKE_ORDER_BACKUP_FLAG; } if (v->type == VEH_TRAIN) { ret = CmdSellRailWagon(flags, v, GB(p1, 20, 12), p2); } else { ret = CommandCost(EXPENSES_NEW_VEHICLES, -front->value); if (flags & DC_EXEC) { if (front->IsPrimaryVehicle() && p1 & MAKE_ORDER_BACKUP_FLAG) OrderBackup::Backup(front, p2); delete front; } } return ret; }
/** * Give a custom name to your vehicle * @param tile unused * @param flags type of operation * @param p1 vehicle ID to name * @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 CmdRenameVehicle(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; bool reset = StrEmpty(text); if (!reset) { if (Utf8StringLength(text) >= MAX_LENGTH_VEHICLE_NAME_CHARS) return CMD_ERROR; if (!(flags & DC_AUTOREPLACE) && !IsUniqueVehicleName(text)) return_cmd_error(STR_ERROR_NAME_MUST_BE_UNIQUE); } if (flags & DC_EXEC) { free(v->name); v->name = reset ? NULL : strdup(text); InvalidateWindowClassesData(GetWindowClassForVehicleType(v->type), 1); MarkWholeScreenDirty(); } return CommandCost(); }
/** * Autoreplaces a vehicle * Trains are replaced as a whole chain, free wagons in depot are replaced on their own * @param tile not used * @param flags type of operation * @param p1 Index of vehicle * @param p2 not used * @param text unused * @return the cost of this operation or an error */ CommandCost CmdAutoreplaceVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { Vehicle *v = Vehicle::GetIfValid(p1); if (v == NULL) return CMD_ERROR; CommandCost ret = CheckOwnership(v->owner); if (ret.Failed()) return ret; if (!v->IsInDepot()) return CMD_ERROR; if (v->vehstatus & VS_CRASHED) return CMD_ERROR; bool free_wagon = false; if (v->type == VEH_TRAIN) { Train *t = Train::From(v); if (t->IsArticulatedPart() || t->IsRearDualheaded()) return CMD_ERROR; free_wagon = !t->IsFrontEngine(); if (free_wagon && t->First()->IsFrontEngine()) return CMD_ERROR; } else { if (!v->IsPrimaryVehicle()) return CMD_ERROR; } const Company *c = Company::Get(_current_company); bool wagon_removal = c->settings.renew_keep_length; /* Test whether any replacement is set, before issuing a whole lot of commands that would end in nothing changed */ Vehicle *w = v; bool any_replacements = false; while (w != NULL) { EngineID e; CommandCost cost = GetNewEngineType(w, c, e); if (cost.Failed()) return cost; any_replacements |= (e != INVALID_ENGINE); w = (!free_wagon && w->type == VEH_TRAIN ? Train::From(w)->GetNextUnit() : NULL); } CommandCost cost = CommandCost(EXPENSES_NEW_VEHICLES, 0); bool nothing_to_do = true; if (any_replacements) { bool was_stopped = free_wagon || ((v->vehstatus & VS_STOPPED) != 0); /* Stop the vehicle */ if (!was_stopped) cost.AddCost(CmdStartStopVehicle(v, true)); if (cost.Failed()) return cost; assert(v->IsStoppedInDepot()); /* We have to construct the new vehicle chain to test whether it is valid. * Vehicle construction needs random bits, so we have to save the random seeds * to prevent desyncs and to replay newgrf callbacks during DC_EXEC */ SavedRandomSeeds saved_seeds; SaveRandomSeeds(&saved_seeds); if (free_wagon) { cost.AddCost(ReplaceFreeUnit(&v, flags & ~DC_EXEC, ¬hing_to_do)); } else { cost.AddCost(ReplaceChain(&v, flags & ~DC_EXEC, wagon_removal, ¬hing_to_do)); } RestoreRandomSeeds(saved_seeds); if (cost.Succeeded() && (flags & DC_EXEC) != 0) { CommandCost ret; if (free_wagon) { ret = ReplaceFreeUnit(&v, flags, ¬hing_to_do); } else { ret = ReplaceChain(&v, flags, wagon_removal, ¬hing_to_do); } assert(ret.Succeeded() && ret.GetCost() == cost.GetCost()); } /* Restart the vehicle */ if (!was_stopped) cost.AddCost(CmdStartStopVehicle(v, false)); } if (cost.Succeeded() && nothing_to_do) cost = CommandCost(STR_ERROR_AUTOREPLACE_NOTHING_TO_DO); return cost; }
/** * Clone a vehicle. If it is a train, it will clone all the cars too * @param tile tile of the depot where the cloned vehicle is build * @param flags type of operation * @param p1 the original vehicle's index * @param p2 1 = shared orders, else copied orders * @param text unused * @return the cost of this operation or an error */ CommandCost CmdCloneVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { CommandCost total_cost(EXPENSES_NEW_VEHICLES); Vehicle *v = Vehicle::GetIfValid(p1); if (v == NULL || !v->IsPrimaryVehicle()) return CMD_ERROR; Vehicle *v_front = v; Vehicle *w = NULL; Vehicle *w_front = NULL; Vehicle *w_rear = NULL; /* * v_front is the front engine in the original vehicle * v is the car/vehicle of the original vehicle that is currently being copied * w_front is the front engine of the cloned vehicle * w is the car/vehicle currently being cloned * w_rear is the rear end of the cloned train. It's used to add more cars and is only used by trains */ CommandCost ret = CheckOwnership(v->owner); if (ret.Failed()) return ret; if (v->type == VEH_TRAIN && (!v->IsFrontEngine() || Train::From(v)->crash_anim_pos >= 4400)) return CMD_ERROR; /* check that we can allocate enough vehicles */ if (!(flags & DC_EXEC)) { int veh_counter = 0; do { veh_counter++; } while ((v = v->Next()) != NULL); if (!Vehicle::CanAllocateItem(veh_counter)) { return_cmd_error(STR_ERROR_TOO_MANY_VEHICLES_IN_GAME); } } v = v_front; do { if (v->type == VEH_TRAIN && Train::From(v)->IsRearDualheaded()) { /* we build the rear ends of multiheaded trains with the front ones */ continue; } /* In case we're building a multi headed vehicle and the maximum number of * vehicles is almost reached (e.g. max trains - 1) not all vehicles would * be cloned. When the non-primary engines were build they were seen as * 'new' vehicles whereas they would immediately be joined with a primary * engine. This caused the vehicle to be not build as 'the limit' had been * reached, resulting in partially build vehicles and such. */ DoCommandFlag build_flags = flags; if ((flags & DC_EXEC) && !v->IsPrimaryVehicle()) build_flags |= DC_AUTOREPLACE; CommandCost cost = DoCommand(tile, v->engine_type | (1 << 16), 0, build_flags, GetCmdBuildVeh(v)); if (cost.Failed()) { /* Can't build a part, then sell the stuff we already made; clear up the mess */ if (w_front != NULL) DoCommand(w_front->tile, w_front->index | (1 << 20), 0, flags, GetCmdSellVeh(w_front)); return cost; } total_cost.AddCost(cost); if (flags & DC_EXEC) { w = Vehicle::Get(_new_vehicle_id); if (v->type == VEH_TRAIN && HasBit(Train::From(v)->flags, VRF_REVERSE_DIRECTION)) { SetBit(Train::From(w)->flags, VRF_REVERSE_DIRECTION); } if (v->type == VEH_TRAIN && !v->IsFrontEngine()) { /* this s a train car * add this unit to the end of the train */ CommandCost result = DoCommand(0, w->index | 1 << 20, w_rear->index, flags, CMD_MOVE_RAIL_VEHICLE); if (result.Failed()) { /* The train can't be joined to make the same consist as the original. * Sell what we already made (clean up) and return an error. */ DoCommand(w_front->tile, w_front->index | 1 << 20, 0, flags, GetCmdSellVeh(w_front)); DoCommand(w_front->tile, w->index | 1 << 20, 0, flags, GetCmdSellVeh(w)); return result; // return error and the message returned from CMD_MOVE_RAIL_VEHICLE } } else { /* this is a front engine or not a train. */ w_front = w; w->service_interval = v->service_interval; w->SetServiceIntervalIsCustom(v->ServiceIntervalIsCustom()); w->SetServiceIntervalIsPercent(v->ServiceIntervalIsPercent()); } w_rear = w; // trains needs to know the last car in the train, so they can add more in next loop } } while (v->type == VEH_TRAIN && (v = v->GetNextVehicle()) != NULL); if ((flags & DC_EXEC) && v_front->type == VEH_TRAIN) { /* for trains this needs to be the front engine due to the callback function */ _new_vehicle_id = w_front->index; } if (flags & DC_EXEC) { /* Cloned vehicles belong to the same group */ DoCommand(0, v_front->group_id, w_front->index, flags, CMD_ADD_VEHICLE_GROUP); } /* Take care of refitting. */ w = w_front; v = v_front; /* Both building and refitting are influenced by newgrf callbacks, which * makes it impossible to accurately estimate the cloning costs. In * particular, it is possible for engines of the same type to be built with * different numbers of articulated parts, so when refitting we have to * loop over real vehicles first, and then the articulated parts of those * vehicles in a different loop. */ do { do { if (flags & DC_EXEC) { assert(w != NULL); /* Find out what's the best sub type */ byte subtype = GetBestFittingSubType(v, w, v->cargo_type); if (w->cargo_type != v->cargo_type || w->cargo_subtype != subtype) { CommandCost cost = DoCommand(0, w->index, v->cargo_type | 1U << 7 | (subtype << 8), flags, GetCmdRefitVeh(v)); if (cost.Succeeded()) total_cost.AddCost(cost); } if (w->IsGroundVehicle() && w->HasArticulatedPart()) { w = w->GetNextArticulatedPart(); } else { break; } } else { const Engine *e = v->GetEngine(); CargoID initial_cargo = (e->CanCarryCargo() ? e->GetDefaultCargoType() : (CargoID)CT_INVALID); if (v->cargo_type != initial_cargo && initial_cargo != CT_INVALID) { bool dummy; total_cost.AddCost(GetRefitCost(NULL, v->engine_type, v->cargo_type, v->cargo_subtype, &dummy)); } } if (v->IsGroundVehicle() && v->HasArticulatedPart()) { v = v->GetNextArticulatedPart(); } else { break; } } while (v != NULL); if ((flags & DC_EXEC) && v->type == VEH_TRAIN) w = w->GetNextVehicle(); } while (v->type == VEH_TRAIN && (v = v->GetNextVehicle()) != NULL); if (flags & DC_EXEC) { /* * Set the orders of the vehicle. Cannot do it earlier as we need * the vehicle refitted before doing this, otherwise the moved * cargo types might not match (passenger vs non-passenger) */ DoCommand(0, w_front->index | (p2 & 1 ? CO_SHARE : CO_COPY) << 30, v_front->index, flags, CMD_CLONE_ORDER); /* Now clone the vehicle's name, if it has one. */ if (v_front->name != NULL) CloneVehicleName(v_front, w_front); } /* Since we can't estimate the cost of cloning a vehicle accurately we must * check whether the company has enough money manually. */ if (!CheckCompanyHasMoney(total_cost)) { if (flags & DC_EXEC) { /* The vehicle has already been bought, so now it must be sold again. */ DoCommand(w_front->tile, w_front->index | 1 << 20, 0, flags, GetCmdSellVeh(w_front)); } return total_cost; } return total_cost; }
/** * Start/Stop a vehicle * @param tile unused * @param flags type of operation * @param p1 vehicle to start/stop, don't forget to change CcStartStopVehicle if you modify this! * @param p2 bit 0: Shall the start/stop newgrf callback be evaluated (only valid with DC_AUTOREPLACE for network safety) * @param text unused * @return the cost of this operation or an error */ CommandCost CmdStartStopVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { /* Disable the effect of p2 bit 0, when DC_AUTOREPLACE is not set */ if ((flags & DC_AUTOREPLACE) == 0) SetBit(p2, 0); Vehicle *v = Vehicle::GetIfValid(p1); if (v == NULL || !v->IsPrimaryVehicle()) return CMD_ERROR; CommandCost ret = CheckOwnership(v->owner); if (ret.Failed()) return ret; if (v->vehstatus & VS_CRASHED) return_cmd_error(STR_ERROR_VEHICLE_IS_DESTROYED); switch (v->type) { case VEH_TRAIN: if ((v->vehstatus & VS_STOPPED) && Train::From(v)->gcache.cached_power == 0) return_cmd_error(STR_ERROR_TRAIN_START_NO_POWER); break; case VEH_SHIP: case VEH_ROAD: break; case VEH_AIRCRAFT: { Aircraft *a = Aircraft::From(v); /* cannot stop airplane when in flight, or when taking off / landing */ if (!(v->vehstatus & VS_CRASHED) && a->state >= STARTTAKEOFF && a->state < TERM7) return_cmd_error(STR_ERROR_AIRCRAFT_IS_IN_FLIGHT); break; } default: return CMD_ERROR; } if (HasBit(p2, 0)) { /* Check if this vehicle can be started/stopped. Failure means 'allow'. */ uint16 callback = GetVehicleCallback(CBID_VEHICLE_START_STOP_CHECK, 0, 0, v->engine_type, v); StringID error = STR_NULL; if (callback != CALLBACK_FAILED) { if (v->GetGRF()->grf_version < 8) { /* 8 bit result 0xFF means 'allow' */ if (callback < 0x400 && GB(callback, 0, 8) != 0xFF) error = GetGRFStringID(v->GetGRFID(), 0xD000 + callback); } else { if (callback < 0x400) { error = GetGRFStringID(v->GetGRFID(), 0xD000 + callback); } else { switch (callback) { case 0x400: // allow break; default: // unknown reason -> disallow error = STR_ERROR_INCOMPATIBLE_RAIL_TYPES; break; } } } } if (error != STR_NULL) return_cmd_error(error); } if (flags & DC_EXEC) { if (v->IsStoppedInDepot() && (flags & DC_AUTOREPLACE) == 0) DeleteVehicleNews(p1, STR_NEWS_TRAIN_IS_WAITING + v->type); v->vehstatus ^= VS_STOPPED; if (v->type != VEH_TRAIN) v->cur_speed = 0; // trains can stop 'slowly' v->MarkDirty(); SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); SetWindowDirty(WC_VEHICLE_DEPOT, v->tile); SetWindowClassesDirty(GetWindowClassForVehicleType(v->type)); } return CommandCost(); }
/** * Refits a vehicle to the specified cargo type. * @param tile unused * @param flags type of operation * @param p1 vehicle ID to refit * @param p2 various bitstuffed elements * - p2 = (bit 0-4) - New cargo type to refit to. * - p2 = (bit 6) - Automatic refitting. * - p2 = (bit 7) - Refit only this vehicle. Used only for cloning vehicles. * - p2 = (bit 8-15) - New cargo subtype to refit to. 0xFF means to try keeping the same subtype according to GetBestFittingSubType(). * - p2 = (bit 16-23) - Number of vehicles to refit (not counting articulated parts). Zero means all vehicles. * Only used if "refit only this vehicle" is false. * @param text unused * @return the cost of this operation or an error */ CommandCost CmdRefitVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { Vehicle *v = Vehicle::GetIfValid(p1); if (v == NULL) return CMD_ERROR; /* Don't allow disasters and sparks and such to be refitted. * We cannot check for IsPrimaryVehicle as autoreplace also refits in free wagon chains. */ if (!IsCompanyBuildableVehicleType(v->type)) return CMD_ERROR; Vehicle *front = v->First(); CommandCost ret = CheckOwnership(front->owner); if (ret.Failed()) return ret; bool auto_refit = HasBit(p2, 6); /* Don't allow shadows and such to be refitted. */ if (v != front && (v->type == VEH_SHIP || v->type == VEH_AIRCRAFT)) return CMD_ERROR; /* Allow auto-refitting only during loading and normal refitting only in a depot. */ if ((!auto_refit || !front->current_order.IsType(OT_LOADING)) && !front->IsStoppedInDepot()) return_cmd_error(STR_ERROR_TRAIN_MUST_BE_STOPPED_INSIDE_DEPOT + front->type); if (front->vehstatus & VS_CRASHED) return_cmd_error(STR_ERROR_VEHICLE_IS_DESTROYED); /* Check cargo */ CargoID new_cid = GB(p2, 0, 5); byte new_subtype = GB(p2, 8, 8); if (new_cid >= NUM_CARGO) return CMD_ERROR; /* For ships and aircrafts there is always only one. */ bool only_this = HasBit(p2, 7) || front->type == VEH_SHIP || front->type == VEH_AIRCRAFT; uint8 num_vehicles = GB(p2, 16, 8); CommandCost cost = RefitVehicle(v, only_this, num_vehicles, new_cid, new_subtype, flags, auto_refit); if (flags & DC_EXEC) { /* Update the cached variables */ switch (v->type) { case VEH_TRAIN: Train::From(front)->ConsistChanged(auto_refit); break; case VEH_ROAD: RoadVehUpdateCache(RoadVehicle::From(front), auto_refit); if (_settings_game.vehicle.roadveh_acceleration_model != AM_ORIGINAL) RoadVehicle::From(front)->CargoChanged(); break; case VEH_SHIP: v->InvalidateNewGRFCacheOfChain(); v->colourmap = PAL_NONE; // invalidate vehicle colour map Ship::From(v)->UpdateCache(); break; case VEH_AIRCRAFT: v->InvalidateNewGRFCacheOfChain(); v->colourmap = PAL_NONE; // invalidate vehicle colour map UpdateAircraftCache(Aircraft::From(v), true); break; default: NOT_REACHED(); } InvalidateWindowData(WC_VEHICLE_DETAILS, front->index); SetWindowDirty(WC_VEHICLE_DEPOT, front->tile); InvalidateWindowClassesData(GetWindowClassForVehicleType(v->type), 0); } else { /* Always invalidate the cache; querycost might have filled it. */ v->InvalidateNewGRFCacheOfChain(); } return cost; }
/** * Change timetable data of an order. * @param tile Not used. * @param flags Operation to perform. * @param p1 Various bitstuffed elements * - p1 = (bit 0-19) - Vehicle with the orders to change. * - p1 = (bit 20-27) - Order index to modify. * - p1 = (bit 28-29) - Timetable data to change (@see ModifyTimetableFlags) * @param p2 The amount of time to wait. * - p2 = (bit 0-15) - The data to modify as specified by p1 bits 28-29. * 0 to clear times, UINT16_MAX to clear speed limit. * @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) { VehicleID veh = GB(p1, 0, 20); 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, 20, 8); Order *order = v->GetOrder(order_number); if (order == NULL || order->IsType(OT_IMPLICIT)) return CMD_ERROR; ModifyTimetableFlags mtf = Extract<ModifyTimetableFlags, 28, 2>(p1); if (mtf >= MTF_END) return CMD_ERROR; int wait_time = order->GetWaitTime(); int travel_time = order->GetTravelTime(); int max_speed = order->GetMaxSpeed(); switch (mtf) { case MTF_WAIT_TIME: wait_time = GB(p2, 0, 16); break; case MTF_TRAVEL_TIME: travel_time = GB(p2, 0, 16); break; case MTF_TRAVEL_SPEED: max_speed = GB(p2, 0, 16); if (max_speed == 0) max_speed = UINT16_MAX; // Disable speed limit. break; default: NOT_REACHED(); } if (wait_time != order->GetWaitTime()) { 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->GetTravelTime() && order->IsType(OT_CONDITIONAL)) return CMD_ERROR; if (max_speed != order->GetMaxSpeed() && (order->IsType(OT_CONDITIONAL) || v->type == VEH_AIRCRAFT)) return CMD_ERROR; if (flags & DC_EXEC) { switch (mtf) { case MTF_WAIT_TIME: /* Set time if changing the value or confirming an estimated time as timetabled. */ if (wait_time != order->GetWaitTime() || (wait_time > 0 && !order->IsWaitTimetabled())) { ChangeTimetable(v, order_number, wait_time, MTF_WAIT_TIME, wait_time > 0); } break; case MTF_TRAVEL_TIME: /* Set time if changing the value or confirming an estimated time as timetabled. */ if (travel_time != order->GetTravelTime() || (travel_time > 0 && !order->IsTravelTimetabled())) { ChangeTimetable(v, order_number, travel_time, MTF_TRAVEL_TIME, travel_time > 0); } break; case MTF_TRAVEL_SPEED: if (max_speed != order->GetMaxSpeed()) { ChangeTimetable(v, order_number, max_speed, MTF_TRAVEL_SPEED, max_speed != UINT16_MAX); } break; default: break; } } return CommandCost(); }