(svn r2024) -Fix: [autoreplace] reverted all changes involving v->set_for_replacement as they caused desyncs.

The bad sideeffect of this is that now no vehicle will automatically go to a depot anymore just 
because it is set to be autoreplaced. We will have to find a better way to solve this problem.

Revisions reverted: 1640, 1707, 1709, 1710, 1712(but not the cheat prevention in this one)
pull/155/head
bjarni 20 years ago
parent c7e5ef76df
commit e5abf58461

@ -410,12 +410,8 @@ int32 CmdStartStopAircraft(int x, int y, uint32 flags, uint32 p1, uint32 p2)
return 0; return 0;
} }
/* Send an aircraft to the hangar in the next station // p1 = vehicle
p1 = index of the aircraft // p2 = if set, the aircraft will try to goto a depot, but not stop
p2 = bit 0..15 = index of station with hangar to use (only used if bit 17 is set)
bit 16 = set v->set_for_replacement & do not stop in hangar
bit 17 = goto the hangar in airport given in bit 0..15 instead of next stop
bit 18 = clear v->set_for_replacement */
int32 CmdSendAircraftToHangar(int x, int y, uint32 flags, uint32 p1, uint32 p2) int32 CmdSendAircraftToHangar(int x, int y, uint32 flags, uint32 p1, uint32 p2)
{ {
Vehicle *v; Vehicle *v;
@ -429,13 +425,6 @@ int32 CmdSendAircraftToHangar(int x, int y, uint32 flags, uint32 p1, uint32 p2)
if (v->type != VEH_Aircraft || !CheckOwnership(v->owner)) if (v->type != VEH_Aircraft || !CheckOwnership(v->owner))
return CMD_ERROR; return CMD_ERROR;
if (HASBIT(p2, 16)) v->set_for_replacement = true; //now all clients knows that the vehicle wants to be replaced
if (HASBIT(p2, 18)) {
v->set_for_replacement = false;
return CMD_ERROR; // no need to send aircraft to a depot. We just told that it don't have to anymore
}
if (v->current_order.type == OT_GOTO_DEPOT && p2 == 0) { if (v->current_order.type == OT_GOTO_DEPOT && p2 == 0) {
if (flags & DC_EXEC) { if (flags & DC_EXEC) {
if (v->current_order.flags & OF_UNLOAD) v->cur_order_index++; if (v->current_order.flags & OF_UNLOAD) v->cur_order_index++;
@ -1125,8 +1114,7 @@ static void ProcessAircraftOrder(Vehicle *v)
if (v->current_order.type == OT_GOTO_DEPOT && if (v->current_order.type == OT_GOTO_DEPOT &&
(v->current_order.flags & (OF_UNLOAD | OF_FULL_LOAD)) == (OF_UNLOAD | OF_FULL_LOAD) && (v->current_order.flags & (OF_UNLOAD | OF_FULL_LOAD)) == (OF_UNLOAD | OF_FULL_LOAD) &&
!VehicleNeedsService(v) && !VehicleNeedsService(v)) {
v->set_for_replacement == false) {
v->cur_order_index++; v->cur_order_index++;
} }
@ -1530,50 +1518,12 @@ static void AircraftEventHandler_HeliTakeOff(Vehicle *v, const AirportFTAClass *
AircraftNextAirportPos_and_Order(v); AircraftNextAirportPos_and_Order(v);
// check if the aircraft needs to be replaced or renewed and send it to a hangar if needed // check if the aircraft needs to be replaced or renewed and send it to a hangar if needed
if (v->current_order.type != OT_GOTO_DEPOT && v->owner == _local_player) { if ((v->owner == _local_player && _autoreplace_array[v->engine_type] != v->engine_type) ||
// only the vehicle owner needs to calculate the rest (locally) (v->owner == _local_player && _patches.autorenew && v->age - v->max_age > (_patches.autorenew_months * 30))) {
if ((_autoreplace_array[v->engine_type] != v->engine_type) ||
(_patches.autorenew && v->age - v->max_age > (_patches.autorenew_months * 30))) {
if (v->set_for_replacement) {
Station *st = GetStation(v->u.air.targetairport);
// If an airport doesn't have terminals (so no landing space for airports),
// it surely doesn't have any hangars
if (st->xy == 0 || st->airport_tile == 0 || GetAirport(st->airport_type)->terminals == NULL)
return;
/* this is not the airport, where the helicopter will be replaced
No need to make everybody check this, since it would be a waste of bandwidth */
}
{
bool has_hangar = HaveHangarInOrderList(v); // this info is needed twice, but we only want to loop the orders once ;)
uint32 next_airport = 0;
if (!has_hangar) {
// the helicopter needs help to find a hangar since there are none in the schedule
SETBIT(next_airport, 17);
next_airport |= FindNearestHangar(v);
if (HASBIT(next_airport, 16)) {
/* when shared airports are allowed, a check for a rentable hangar should be added here */
/* TODO: tell the player that he needs a hangar */
v->set_for_replacement = false; //needed so it will check again later when the player might have build a hangar
return; // player do not own any hangars
}
}
SETBIT(next_airport, 16);
_current_player = _local_player; _current_player = _local_player;
DoCommandP(v->tile, v->index, next_airport, NULL, CMD_SEND_AIRCRAFT_TO_HANGAR | CMD_SHOW_NO_ERROR); DoCommandP(v->tile, v->index, 1, NULL, CMD_SEND_AIRCRAFT_TO_HANGAR | CMD_SHOW_NO_ERROR);
_current_player = OWNER_NONE; _current_player = OWNER_NONE;
} }
} else { // no need to go to a depot
if (v->set_for_replacement) {
// it seems that the user clicked "Stop replacing"
_current_player = _local_player;
DoCommandP(v->tile, v->index, (1 | (1 << 2)) << 16, NULL, CMD_SEND_AIRCRAFT_TO_HANGAR | CMD_SHOW_NO_ERROR);
_current_player = OWNER_NONE;
}
}
}
} }
static void AircraftEventHandler_Flying(Vehicle *v, const AirportFTAClass *Airport) static void AircraftEventHandler_Flying(Vehicle *v, const AirportFTAClass *Airport)
@ -1636,13 +1586,6 @@ static void AircraftEventHandler_Landing(Vehicle *v, const AirportFTAClass *Airp
_current_player = _local_player; _current_player = _local_player;
DoCommandP(v->tile, v->index, 1 << 16, NULL, CMD_SEND_AIRCRAFT_TO_HANGAR | CMD_SHOW_NO_ERROR); DoCommandP(v->tile, v->index, 1 << 16, NULL, CMD_SEND_AIRCRAFT_TO_HANGAR | CMD_SHOW_NO_ERROR);
_current_player = OWNER_NONE; _current_player = OWNER_NONE;
} else { // no need to go to a depot
if (v->set_for_replacement) {
// it seems that the user clicked "Stop replacing"
_current_player = _local_player;
DoCommandP(v->tile, v->index, (1 | (1 << 2)) << 16, NULL, CMD_SEND_AIRCRAFT_TO_HANGAR | CMD_SHOW_NO_ERROR);
_current_player = OWNER_NONE;
}
} }
} }
} }

@ -320,11 +320,6 @@ static Depot *FindClosestRoadDepot(Vehicle *v)
} }
} }
/* Send a road vehicle to the nearest depot
p1 = index of the road vehicle
p2 = bit 0 = do not stop in depot
bit 1 = set v->set_for_replacement
bit 2 = clear v->set_for_replacement */
int32 CmdSendRoadVehToDepot(int x, int y, uint32 flags, uint32 p1, uint32 p2) int32 CmdSendRoadVehToDepot(int x, int y, uint32 flags, uint32 p1, uint32 p2)
{ {
Vehicle *v; Vehicle *v;
@ -337,11 +332,6 @@ int32 CmdSendRoadVehToDepot(int x, int y, uint32 flags, uint32 p1, uint32 p2)
if (v->type != VEH_Road || !CheckOwnership(v->owner)) if (v->type != VEH_Road || !CheckOwnership(v->owner))
return CMD_ERROR; return CMD_ERROR;
if (HASBIT(p2, 0)) v->set_for_replacement = true;
if (HASBIT(p2, 2)) v->set_for_replacement = false;
if (HASBIT(p2, 1) || HASBIT(p2, 2)) return CMD_ERROR; // vehicle has a depot in schedule. It just needed to alter set_for_replacement
if (v->current_order.type == OT_GOTO_DEPOT) { if (v->current_order.type == OT_GOTO_DEPOT) {
if (flags & DC_EXEC) { if (flags & DC_EXEC) {
if (v->current_order.flags & OF_UNLOAD) if (v->current_order.flags & OF_UNLOAD)
@ -359,7 +349,7 @@ int32 CmdSendRoadVehToDepot(int x, int y, uint32 flags, uint32 p1, uint32 p2)
if (flags & DC_EXEC) { if (flags & DC_EXEC) {
v->current_order.type = OT_GOTO_DEPOT; v->current_order.type = OT_GOTO_DEPOT;
v->current_order.flags = p2 == 0 ? OF_NON_STOP | OF_FULL_LOAD : 0; v->current_order.flags = OF_NON_STOP | OF_FULL_LOAD;
v->current_order.station = depot->index; v->current_order.station = depot->index;
v->dest_tile = depot->xy; v->dest_tile = depot->xy;
InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, STATUS_BAR); InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, STATUS_BAR);
@ -615,7 +605,7 @@ static void ProcessRoadVehOrder(Vehicle *v)
if (v->current_order.type == OT_GOTO_DEPOT && if (v->current_order.type == OT_GOTO_DEPOT &&
(v->current_order.flags & (OF_UNLOAD | OF_FULL_LOAD)) == (OF_UNLOAD | OF_FULL_LOAD) && (v->current_order.flags & (OF_UNLOAD | OF_FULL_LOAD)) == (OF_UNLOAD | OF_FULL_LOAD) &&
!VehicleNeedsService(v) && !v->set_for_replacement) { !VehicleNeedsService(v)) {
v->cur_order_index++; v->cur_order_index++;
} }
@ -716,28 +706,6 @@ static void HandleRoadVehLoading(Vehicle *v)
v->cur_order_index++; v->cur_order_index++;
InvalidateVehicleOrder(v); InvalidateVehicleOrder(v);
if (v->current_order.type != OT_GOTO_DEPOT && v->owner == _local_player) {
// only the vehicle owner needs to calculate the rest (locally)
if ((_autoreplace_array[v->engine_type] != v->engine_type) ||
(_patches.autorenew && v->age - v->max_age > (_patches.autorenew_months * 30))) {
byte flags = 1;
// the flags means, bit 0 = needs to go to depot, bit 1 = have depot in orders
if (VehicleHasDepotOrders(v)) SETBIT(flags, 1);
if (!(HASBIT(flags, 1) && v->set_for_replacement)) {
_current_player = _local_player;
DoCommandP(v->tile, v->index, flags, NULL, CMD_SEND_ROADVEH_TO_DEPOT | CMD_SHOW_NO_ERROR);
_current_player = OWNER_NONE;
}
} else { // no need to go to a depot
if (v->set_for_replacement) {
// it seems that the user clicked "Stop replacing"
_current_player = _local_player;
DoCommandP(v->tile, v->index, 1 | (1 << 2), NULL, CMD_SEND_ROADVEH_TO_DEPOT | CMD_SHOW_NO_ERROR);
_current_player = OWNER_NONE;
}
}
}
} }
static void StartRoadVehSound(Vehicle *v) static void StartRoadVehSound(Vehicle *v)
@ -1600,10 +1568,10 @@ static void CheckIfRoadVehNeedsService(Vehicle *v)
{ {
Depot *depot; Depot *depot;
if (_patches.servint_roadveh == 0 && !v->set_for_replacement) if (_patches.servint_roadveh == 0)
return; return;
if (!VehicleNeedsService(v) && !v->set_for_replacement) if (!VehicleNeedsService(v))
return; return;
if (v->vehstatus & VS_STOPPED) if (v->vehstatus & VS_STOPPED)
@ -1625,10 +1593,7 @@ static void CheckIfRoadVehNeedsService(Vehicle *v)
depot = FindClosestRoadDepot(v); depot = FindClosestRoadDepot(v);
if (depot == NULL || DistanceManhattan(v->tile, depot->xy) > 12) { if (depot == NULL || DistanceManhattan(v->tile, depot->xy) > 12) {
if (v->current_order.type == OT_GOTO_DEPOT && !( if (v->current_order.type == OT_GOTO_DEPOT) {
DistanceManhattan(v->tile, v->dest_tile) > 25 && v->set_for_replacement)) {
/* a vehicle needs a greater distance to a depot to loose it than to find it since
they can circle forever othervise if they are in a loop with an unlucky distance */
v->current_order.type = OT_DUMMY; v->current_order.type = OT_DUMMY;
v->current_order.flags = 0; v->current_order.flags = 0;
InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, STATUS_BAR); InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, STATUS_BAR);

@ -98,10 +98,10 @@ static void CheckIfShipNeedsService(Vehicle *v)
{ {
Depot *depot; Depot *depot;
if (_patches.servint_ships == 0 && !v->set_for_replacement) if (_patches.servint_ships == 0)
return; return;
if (!VehicleNeedsService(v) && !v->set_for_replacement) if (!VehicleNeedsService(v))
return; return;
if (v->vehstatus & VS_STOPPED) if (v->vehstatus & VS_STOPPED)
@ -226,7 +226,7 @@ static void ProcessShipOrder(Vehicle *v)
if (v->current_order.type == OT_GOTO_DEPOT && if (v->current_order.type == OT_GOTO_DEPOT &&
(v->current_order.flags & (OF_UNLOAD | OF_FULL_LOAD)) == (OF_UNLOAD | OF_FULL_LOAD) && (v->current_order.flags & (OF_UNLOAD | OF_FULL_LOAD)) == (OF_UNLOAD | OF_FULL_LOAD) &&
!VehicleNeedsService(v) && !v->set_for_replacement) { !VehicleNeedsService(v)) {
v->cur_order_index++; v->cur_order_index++;
} }
@ -297,28 +297,6 @@ static void HandleShipLoading(Vehicle *v)
Order b = v->current_order; Order b = v->current_order;
v->current_order.type = OT_LEAVESTATION; v->current_order.type = OT_LEAVESTATION;
v->current_order.flags = 0; v->current_order.flags = 0;
if (v->current_order.type != OT_GOTO_DEPOT && v->owner == _local_player) {
// only the vehicle owner needs to calculate the rest (locally)
if ((_autoreplace_array[v->engine_type] != v->engine_type) ||
(_patches.autorenew && v->age - v->max_age > (_patches.autorenew_months * 30))) {
byte flags = 1;
// the flags means, bit 0 = needs to go to depot, bit 1 = have depot in orders
if (VehicleHasDepotOrders(v)) SETBIT(flags, 1);
if (!(HASBIT(flags, 1) && v->set_for_replacement)) {
_current_player = _local_player;
DoCommandP(v->tile, v->index, flags, NULL, CMD_SEND_SHIP_TO_DEPOT | CMD_SHOW_NO_ERROR);
_current_player = OWNER_NONE;
}
} else { // no need to go to a depot
if (v->set_for_replacement) {
// it seems that the user clicked "Stop replacing"
_current_player = _local_player;
DoCommandP(v->tile, v->index, 1 | (1 << 2), NULL, CMD_SEND_SHIP_TO_DEPOT | CMD_SHOW_NO_ERROR);
_current_player = OWNER_NONE;
}
}
}
if (!(b.flags & OF_NON_STOP)) if (!(b.flags & OF_NON_STOP))
return; return;
} }
@ -1001,11 +979,6 @@ int32 CmdStartStopShip(int x, int y, uint32 flags, uint32 p1, uint32 p2)
return 0; return 0;
} }
/* Send a ship to the nearest depot
p1 = index of the ship
p2 = bit 0 = do not stop in depot
bit 1 = set v->set_for_replacement
bit 2 = clear v->set_for_replacement */
int32 CmdSendShipToDepot(int x, int y, uint32 flags, uint32 p1, uint32 p2) int32 CmdSendShipToDepot(int x, int y, uint32 flags, uint32 p1, uint32 p2)
{ {
Vehicle *v; Vehicle *v;
@ -1018,11 +991,6 @@ int32 CmdSendShipToDepot(int x, int y, uint32 flags, uint32 p1, uint32 p2)
if (v->type != VEH_Ship || !CheckOwnership(v->owner)) if (v->type != VEH_Ship || !CheckOwnership(v->owner))
return CMD_ERROR; return CMD_ERROR;
if (HASBIT(p2, 0)) v->set_for_replacement = true;
if (HASBIT(p2, 2)) v->set_for_replacement = false;
if (HASBIT(p2, 1) || HASBIT(p2, 2)) return CMD_ERROR; // vehicle has a depot in schedule. It just needed to alter set_for_replacement
if (v->current_order.type == OT_GOTO_DEPOT) { if (v->current_order.type == OT_GOTO_DEPOT) {
if (flags & DC_EXEC) { if (flags & DC_EXEC) {
if (v->current_order.flags & OF_UNLOAD) v->cur_order_index++; if (v->current_order.flags & OF_UNLOAD) v->cur_order_index++;
@ -1038,7 +1006,7 @@ int32 CmdSendShipToDepot(int x, int y, uint32 flags, uint32 p1, uint32 p2)
if (flags & DC_EXEC) { if (flags & DC_EXEC) {
v->dest_tile = depot->xy; v->dest_tile = depot->xy;
v->current_order.type = OT_GOTO_DEPOT; v->current_order.type = OT_GOTO_DEPOT;
v->current_order.flags = HASBIT(p2, 0) ? 0 : OF_NON_STOP | OF_FULL_LOAD; v->current_order.flags = OF_NON_STOP | OF_FULL_LOAD;
v->current_order.station = depot->index; v->current_order.station = depot->index;
InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, STATUS_BAR); InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, STATUS_BAR);
} }

@ -1339,11 +1339,6 @@ static TrainFindDepotData FindClosestTrainDepot(Vehicle *v)
return tfdd; return tfdd;
} }
/* Send a train to the nearest depot
p1 = index of the train
p2 = bit 0 = do not stop in depot
bit 1 = set v->set_for_replacement
bit 2 = clear v->set_for_replacement */
int32 CmdTrainGotoDepot(int x, int y, uint32 flags, uint32 p1, uint32 p2) int32 CmdTrainGotoDepot(int x, int y, uint32 flags, uint32 p1, uint32 p2)
{ {
Vehicle *v; Vehicle *v;
@ -1356,12 +1351,6 @@ int32 CmdTrainGotoDepot(int x, int y, uint32 flags, uint32 p1, uint32 p2)
if (v->type != VEH_Train || !CheckOwnership(v->owner)) if (v->type != VEH_Train || !CheckOwnership(v->owner))
return CMD_ERROR; return CMD_ERROR;
if (HASBIT(p2, 0)) v->set_for_replacement = true;
if (HASBIT(p2, 2)) v->set_for_replacement = false;
if (HASBIT(p2, 1) || HASBIT(p2, 2)) return CMD_ERROR; // vehicle has a depot in schedule. It just needed to alter set_for_replacement
if (v->current_order.type == OT_GOTO_DEPOT) { if (v->current_order.type == OT_GOTO_DEPOT) {
if (flags & DC_EXEC) { if (flags & DC_EXEC) {
if (v->current_order.flags & OF_UNLOAD) { if (v->current_order.flags & OF_UNLOAD) {
@ -1383,7 +1372,7 @@ int32 CmdTrainGotoDepot(int x, int y, uint32 flags, uint32 p1, uint32 p2)
if (flags & DC_EXEC) { if (flags & DC_EXEC) {
v->dest_tile = tfdd.tile; v->dest_tile = tfdd.tile;
v->current_order.type = OT_GOTO_DEPOT; v->current_order.type = OT_GOTO_DEPOT;
v->current_order.flags = HASBIT(p2, 0) ? 0 : OF_NON_STOP | OF_FULL_LOAD; v->current_order.flags = OF_NON_STOP | OF_FULL_LOAD;
v->current_order.station = GetDepotByTile(tfdd.tile)->index; v->current_order.station = GetDepotByTile(tfdd.tile)->index;
InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, STATUS_BAR); InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, STATUS_BAR);
} }
@ -1882,7 +1871,7 @@ static bool ProcessTrainOrder(Vehicle *v)
if (v->current_order.type == OT_GOTO_DEPOT && if (v->current_order.type == OT_GOTO_DEPOT &&
(v->current_order.flags & (OF_UNLOAD | OF_FULL_LOAD)) == (OF_UNLOAD | OF_FULL_LOAD) && (v->current_order.flags & (OF_UNLOAD | OF_FULL_LOAD)) == (OF_UNLOAD | OF_FULL_LOAD) &&
!VehicleNeedsService(v) && !v->set_for_replacement) { !VehicleNeedsService(v)) {
v->cur_order_index++; v->cur_order_index++;
} }
@ -1994,27 +1983,6 @@ static void HandleTrainLoading(Vehicle *v, bool mode)
v->current_order.type = OT_LEAVESTATION; v->current_order.type = OT_LEAVESTATION;
v->current_order.flags = 0; v->current_order.flags = 0;
if (v->current_order.type != OT_GOTO_DEPOT && v->owner == _local_player) {
// only the vehicle owner needs to calculate the rest (locally)
if ((_autoreplace_array[v->engine_type] != v->engine_type) ||
(_patches.autorenew && v->age - v->max_age > (_patches.autorenew_months * 30))) {
byte flags = 1;
// the flags means, bit 0 = needs to go to depot, bit 1 = have depot in orders
if (VehicleHasDepotOrders(v)) SETBIT(flags, 1);
if (!(HASBIT(flags, 1) && v->set_for_replacement)) {
_current_player = _local_player;
DoCommandP(v->tile, v->index, flags, NULL, CMD_TRAIN_GOTO_DEPOT | CMD_SHOW_NO_ERROR);
_current_player = OWNER_NONE;
}
} else { // no need to go to a depot
if (v->set_for_replacement) {
// it seems that the user clicked "Stop replacing"
_current_player = _local_player;
DoCommandP(v->tile, v->index, 1 | (1 << 2), NULL, CMD_TRAIN_GOTO_DEPOT | CMD_SHOW_NO_ERROR);
_current_player = OWNER_NONE;
}
}
}
// If this was not the final order, don't remove it from the list. // If this was not the final order, don't remove it from the list.
if (!(b.flags & OF_NON_STOP)) if (!(b.flags & OF_NON_STOP))
return; return;
@ -3012,10 +2980,10 @@ static void CheckIfTrainNeedsService(Vehicle *v)
Depot *depot; Depot *depot;
TrainFindDepotData tfdd; TrainFindDepotData tfdd;
if (_patches.servint_trains == 0 && !v->set_for_replacement) if (_patches.servint_trains == 0)
return; return;
if (!VehicleNeedsService(v) && !v->set_for_replacement) if (!VehicleNeedsService(v))
return; return;
if (v->vehstatus & VS_STOPPED) if (v->vehstatus & VS_STOPPED)

@ -212,7 +212,6 @@ static Vehicle *InitializeVehicle(Vehicle *v)
v->string_id = 0; v->string_id = 0;
v->next_shared = NULL; v->next_shared = NULL;
v->prev_shared = NULL; v->prev_shared = NULL;
v->set_for_replacement = false;
/* random_bits is used to pick out a random sprite for vehicles /* random_bits is used to pick out a random sprite for vehicles
which are technical the same (newgrf stuff). which are technical the same (newgrf stuff).
Because RandomRange() results in desyncs, and because it does Because RandomRange() results in desyncs, and because it does
@ -1433,7 +1432,6 @@ int32 CmdReplaceVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2)
Engine *e; Engine *e;
e = DEREF_ENGINE(new_engine_type); e = DEREF_ENGINE(new_engine_type);
v->set_for_replacement = false;
v->reliability = e->reliability; v->reliability = e->reliability;
v->reliability_spd_dec = e->reliability_spd_dec; v->reliability_spd_dec = e->reliability_spd_dec;
v->age = 0; v->age = 0;

@ -184,8 +184,6 @@ struct Vehicle {
int32 profit_last_year; int32 profit_last_year;
uint32 value; uint32 value;
bool set_for_replacement; // marks this vehicle to be replaced
union { union {
VehicleRail rail; VehicleRail rail;
VehicleAir air; VehicleAir air;

Loading…
Cancel
Save