src/autoreplace_cmd.cpp
branchnoai
changeset 10294 7798ae816af8
parent 10249 58810805030e
child 10455 22c441f5adf9
equal deleted inserted replaced
10292:7856e972f8aa 10294:7798ae816af8
     1 /* $Id$ */
     1 /* $Id$ */
       
     2 
       
     3 /** @file autoreplace_cmd.cpp Deals with autoreplace execution but not the setup */
     2 
     4 
     3 #include "stdafx.h"
     5 #include "stdafx.h"
     4 #include "openttd.h"
     6 #include "openttd.h"
     5 #include "roadveh.h"
     7 #include "roadveh.h"
     6 #include "ship.h"
     8 #include "ship.h"
   115 		if (CanRefitTo(engine_type, v->cargo_type)) return v->cargo_type;
   117 		if (CanRefitTo(engine_type, v->cargo_type)) return v->cargo_type;
   116 	} while ((v = v->Next()) != NULL);
   118 	} while ((v = v->Next()) != NULL);
   117 	return CT_NO_REFIT; // We failed to find a cargo type on the old vehicle and we will not refit the new one
   119 	return CT_NO_REFIT; // We failed to find a cargo type on the old vehicle and we will not refit the new one
   118 }
   120 }
   119 
   121 
   120 /* Replaces a vehicle (used to be called autorenew)
   122 /** Replaces a vehicle (used to be called autorenew)
   121  * This function is only called from MaybeReplaceVehicle()
   123  * This function is only called from MaybeReplaceVehicle()
   122  * Must be called with _current_player set to the owner of the vehicle
   124  * Must be called with _current_player set to the owner of the vehicle
   123  * @param w Vehicle to replace
   125  * @param w Vehicle to replace
   124  * @param flags is the flags to use when calling DoCommand(). Mainly DC_EXEC counts
   126  * @param flags is the flags to use when calling DoCommand(). Mainly DC_EXEC counts
       
   127  * @param p The vehicle owner (faster than refinding the pointer)
       
   128  * @param new_engine_type The EngineID to replace to
   125  * @return value is cost of the replacement or CMD_ERROR
   129  * @return value is cost of the replacement or CMD_ERROR
   126  */
   130  */
   127 static CommandCost ReplaceVehicle(Vehicle **w, byte flags, Money total_cost)
   131 static CommandCost ReplaceVehicle(Vehicle **w, byte flags, Money total_cost, const Player *p, EngineID new_engine_type)
   128 {
   132 {
   129 	CommandCost cost;
   133 	CommandCost cost;
   130 	CommandCost sell_value;
   134 	CommandCost sell_value;
   131 	Vehicle *old_v = *w;
   135 	Vehicle *old_v = *w;
   132 	const Player *p = GetPlayer(old_v->owner);
       
   133 	EngineID new_engine_type;
       
   134 	const UnitID cached_unitnumber = old_v->unitnumber;
   136 	const UnitID cached_unitnumber = old_v->unitnumber;
   135 	bool new_front = false;
   137 	bool new_front = false;
   136 	Vehicle *new_v = NULL;
   138 	Vehicle *new_v = NULL;
   137 	char *vehicle_name = NULL;
   139 	char *vehicle_name = NULL;
   138 	CargoID replacement_cargo_type;
   140 	CargoID replacement_cargo_type;
   139 
       
   140 	/* Check if there is a autoreplacement set for the vehicle */
       
   141 	new_engine_type = EngineReplacementForPlayer(p, old_v->engine_type, old_v->group_id);
       
   142 	/* if not, just renew to the same type */
       
   143 	if (new_engine_type == INVALID_ENGINE) new_engine_type = old_v->engine_type;
       
   144 
   141 
   145 	replacement_cargo_type = GetNewCargoTypeForReplace(old_v, new_engine_type);
   142 	replacement_cargo_type = GetNewCargoTypeForReplace(old_v, new_engine_type);
   146 
   143 
   147 	/* check if we can't refit to the needed type, so no replace takes place to prevent the vehicle from altering cargo type */
   144 	/* check if we can't refit to the needed type, so no replace takes place to prevent the vehicle from altering cargo type */
   148 	if (replacement_cargo_type == CT_INVALID) return CommandCost();
   145 	if (replacement_cargo_type == CT_INVALID) return CommandCost();
   290 	}
   287 	}
   291 
   288 
   292 	return cost;
   289 	return cost;
   293 }
   290 }
   294 
   291 
       
   292 /** Removes wagons from a train until it get a certain length
       
   293  * @param v The vehicle
       
   294  * @param old_total_length The wanted max length
       
   295  * @return The profit from selling the wagons
       
   296  */
       
   297 static CommandCost WagonRemoval(Vehicle *v, uint16 old_total_length)
       
   298 {
       
   299 	if (v->type != VEH_TRAIN) return CommandCost();
       
   300 	Vehicle *front = v;
       
   301 
       
   302 	CommandCost cost = CommandCost();
       
   303 
       
   304 	while (front->u.rail.cached_total_length > old_total_length) {
       
   305 		/* the train is too long. We will remove cars one by one from the start of the train until it's short enough */
       
   306 		while (v != NULL && RailVehInfo(v->engine_type)->railveh_type != RAILVEH_WAGON) {
       
   307 			/* We move backwards in the train until we find a wagon */
       
   308 			v = GetNextVehicle(v);
       
   309 		}
       
   310 
       
   311 		if (v == NULL) {
       
   312 			/* We sold all the wagons and the train is still not short enough */
       
   313 			SetDParam(0, front->unitnumber);
       
   314 			AddNewsItem(STR_TRAIN_TOO_LONG_AFTER_REPLACEMENT, NM_SMALL, NF_VIEWPORT | NF_VEHICLE, NT_ADVICE, DNC_NONE, front->index, 0);
       
   315 			return cost;
       
   316 		}
       
   317 
       
   318 		/* We found a wagon we can sell */
       
   319 		Vehicle *temp = v;
       
   320 		v = GetNextVehicle(v);
       
   321 		DoCommand(0, (INVALID_VEHICLE << 16) | temp->index, 0, DC_EXEC, CMD_MOVE_RAIL_VEHICLE); // remove the wagon from the train
       
   322 		MoveVehicleCargo(front, temp); // move the cargo back on the train
       
   323 		cost.AddCost(DoCommand(0, temp->index, 0, DC_EXEC, CMD_SELL_RAIL_WAGON)); // sell the wagon
       
   324 	}
       
   325 	return cost;
       
   326 }
       
   327 
       
   328 /** Get the EngineID of the replacement for a vehicle
       
   329  * @param v The vehicle to find a replacement for
       
   330  * @param p The vehicle's owner (it's faster to forward the pointer than refinding it)
       
   331  * @return the EngineID of the replacement. INVALID_ENGINE if no buildable replacement is found
       
   332  */
       
   333 static EngineID GetNewEngineType(const Vehicle *v, const Player *p)
       
   334 {
       
   335 	if (v->type == VEH_TRAIN && IsRearDualheaded(v)) {
       
   336 		/* we build the rear ends of multiheaded trains with the front ones */
       
   337 		return INVALID_ENGINE;
       
   338 	}
       
   339 
       
   340 	EngineID e = EngineReplacementForPlayer(p, v->engine_type, v->group_id);
       
   341 
       
   342 	if (e != INVALID_ENGINE && IsEngineBuildable(e, v->type, _current_player)) {
       
   343 		return e;
       
   344 	}
       
   345 
       
   346 	if (v->NeedsAutorenewing(p) && // replace if engine is too old
       
   347 	    IsEngineBuildable(v->engine_type, v->type, _current_player)) { // engine can still be build
       
   348 		return v->engine_type;
       
   349 	}
       
   350 
       
   351 	return INVALID_ENGINE;
       
   352 }
       
   353 
   295 /** replaces a vehicle if it's set for autoreplace or is too old
   354 /** replaces a vehicle if it's set for autoreplace or is too old
   296  * (used to be called autorenew)
   355  * (used to be called autorenew)
   297  * @param v The vehicle to replace
   356  * @param v The vehicle to replace
   298  * if the vehicle is a train, v needs to be the front engine
   357  * if the vehicle is a train, v needs to be the front engine
   299  * @param check Checks if the replace is valid. No action is done at all
   358  * @param flags
   300  * @param display_costs If set, a cost animation is shown (only if check is false)
   359  * @param display_costs If set, a cost animation is shown (only if DC_EXEC is set)
   301  * @return CMD_ERROR if something went wrong. Otherwise the price of the replace
   360  *        This bool also takes autorenew money into consideration
   302  */
   361  * @return the costs, the success bool and sometimes an error message
   303 CommandCost MaybeReplaceVehicle(Vehicle *v, bool check, bool display_costs)
   362  */
       
   363 CommandCost MaybeReplaceVehicle(Vehicle *v, uint32 flags, bool display_costs)
   304 {
   364 {
   305 	Vehicle *w;
   365 	Vehicle *w;
   306 	const Player *p = GetPlayer(v->owner);
   366 	const Player *p = GetPlayer(v->owner);
   307 	byte flags = 0;
   367 	CommandCost cost;
   308 	CommandCost cost, temp_cost;
   368 	bool stopped = false;
   309 	bool stopped;
   369 
       
   370 	/* We only want "real" vehicle types. */
       
   371 	assert(IsPlayerBuildableVehicleType(v));
       
   372 
       
   373 	/* Ensure that this bool is cleared. */
       
   374 	assert(!v->leave_depot_instantly);
       
   375 
       
   376 	/* We can't sell if the current player don't own the vehicle. */
       
   377 	assert(v->owner == _current_player);
       
   378 
       
   379 	if (!v->IsInDepot()) {
       
   380 		/* The vehicle should be inside the depot */
       
   381 		switch (v->type) {
       
   382 			default: NOT_REACHED();
       
   383 			case VEH_TRAIN:    return_cmd_error(STR_881A_TRAINS_CAN_ONLY_BE_ALTERED); break;
       
   384 			case VEH_ROAD:     return_cmd_error(STR_9013_MUST_BE_STOPPED_INSIDE);     break;
       
   385 			case VEH_SHIP:     return_cmd_error(STR_980B_SHIP_MUST_BE_STOPPED_IN);    break;
       
   386 			case VEH_AIRCRAFT: return_cmd_error(STR_A01B_AIRCRAFT_MUST_BE_STOPPED);   break;
       
   387 		}
       
   388 	}
   310 
   389 
   311 	/* Remember the length in case we need to trim train later on
   390 	/* Remember the length in case we need to trim train later on
   312 	 * If it's not a train, the value is unused
   391 	 * If it's not a train, the value is unused
   313 	 * round up to the length of the tiles used for the train instead of the train length instead
   392 	 * round up to the length of the tiles used for the train instead of the train length instead
   314 	 * Useful when newGRF uses custom length */
   393 	 * Useful when newGRF uses custom length */
   315 	uint16 old_total_length = (v->type == VEH_TRAIN ?
   394 	uint16 old_total_length = (v->type == VEH_TRAIN ?
   316 		(v->u.rail.cached_total_length + TILE_SIZE - 1) / TILE_SIZE * TILE_SIZE :
   395 		(v->u.rail.cached_total_length + TILE_SIZE - 1) / TILE_SIZE * TILE_SIZE :
   317 		-1
   396 		-1
   318 	);
   397 	);
   319 
   398 
   320 
   399 	if (!(v->vehstatus & VS_STOPPED)) {
   321 	_current_player = v->owner;
   400 		/* The vehicle is moving so we better stop it before we might alter consist or sell it */
   322 
   401 		v->vehstatus |= VS_STOPPED;
   323 	assert(IsPlayerBuildableVehicleType(v));
   402 		/* Remember that we stopped the vehicle */
   324 
   403 		stopped = true;
   325 	assert(v->vehstatus & VS_STOPPED); // the vehicle should have been stopped in VehicleEnteredDepotThisTick() if needed
   404 	}
   326 
   405 
   327 	/* Remember the flag v->leave_depot_instantly because if we replace the vehicle, the vehicle holding this flag will be sold
   406 	{
   328 	 * If it is set, then we only stopped the vehicle to replace it (if needed) and we will need to start it again.
       
   329 	 * We also need to reset the flag since it should remain false except from when the vehicle enters a depot until autoreplace is handled in the same tick */
       
   330 	stopped = v->leave_depot_instantly;
       
   331 	v->leave_depot_instantly = false;
       
   332 
       
   333 	for (;;) {
       
   334 		cost = CommandCost(EXPENSES_NEW_VEHICLES);
   407 		cost = CommandCost(EXPENSES_NEW_VEHICLES);
   335 		w = v;
   408 		w = v;
   336 		do {
   409 		do {
   337 			if (w->type == VEH_TRAIN && IsRearDualheaded(w)) {
   410 			EngineID new_engine = GetNewEngineType(w, p);
   338 				/* we build the rear ends of multiheaded trains with the front ones */
   411 			if (new_engine == INVALID_ENGINE) continue;
   339 				continue;
       
   340 			}
       
   341 
       
   342 			// check if the vehicle should be replaced
       
   343 			if (!w->NeedsAutorenewing(p) || // replace if engine is too old
       
   344 					w->max_age == 0) { // rail cars got a max age of 0
       
   345 				if (!EngineHasReplacementForPlayer(p, w->engine_type, w->group_id)) continue;
       
   346 			}
       
   347 
   412 
   348 			/* Now replace the vehicle */
   413 			/* Now replace the vehicle */
   349 			temp_cost = ReplaceVehicle(&w, flags, cost.GetCost());
   414 			cost.AddCost(ReplaceVehicle(&w, flags, cost.GetCost(), p, new_engine));
   350 
       
   351 			if (CmdFailed(temp_cost)) break; // replace failed for some reason. Leave the vehicle alone
       
   352 
   415 
   353 			if (flags & DC_EXEC &&
   416 			if (flags & DC_EXEC &&
   354 					(w->type != VEH_TRAIN || w->u.rail.first_engine == INVALID_ENGINE)) {
   417 					(w->type != VEH_TRAIN || w->u.rail.first_engine == INVALID_ENGINE)) {
   355 				/* now we bought a new engine and sold the old one. We need to fix the
   418 				/* now we bought a new engine and sold the old one. We need to fix the
   356 				 * pointers in order to avoid pointing to the old one for trains: these
   419 				 * pointers in order to avoid pointing to the old one for trains: these
   357 				 * pointers should point to the front engine and not the cars
   420 				 * pointers should point to the front engine and not the cars
   358 				 */
   421 				 */
   359 				v = w;
   422 				v = w;
   360 			}
   423 			}
   361 			cost.AddCost(temp_cost);
       
   362 		} while (w->type == VEH_TRAIN && (w = GetNextVehicle(w)) != NULL);
   424 		} while (w->type == VEH_TRAIN && (w = GetNextVehicle(w)) != NULL);
   363 
   425 
   364 		if (!(flags & DC_EXEC) && (p->player_money < (cost.GetCost() + p->engine_renew_money) || cost.GetCost() == 0)) {
   426 		if (flags & DC_QUERY_COST || cost.GetCost() == 0) {
   365 			if (!check && p->player_money < (cost.GetCost() + p->engine_renew_money) && ( _local_player == v->owner ) && cost.GetCost() != 0) {
   427 			/* We didn't do anything during the replace so we will just exit here */
       
   428 			if (stopped) v->vehstatus &= ~VS_STOPPED;
       
   429 			return cost;
       
   430 		}
       
   431 
       
   432 		if (display_costs && !(flags & DC_EXEC)) {
       
   433 			/* We want to ensure that we will not get below p->engine_renew_money.
       
   434 			 * We will not actually pay this amount. It's for display and checks only. */
       
   435 			cost.AddCost((Money)p->engine_renew_money);
       
   436 			if (CmdSucceeded(cost) && GetAvailableMoneyForCommand() < cost.GetCost()) {
       
   437 				/* We don't have enough money so we will set cost to failed */
       
   438 				cost.AddCost(CMD_ERROR);
       
   439 			}
       
   440 		}
       
   441 
       
   442 		if (display_costs && CmdFailed(cost)) {
       
   443 			if (GetAvailableMoneyForCommand() < cost.GetCost() && IsLocalPlayer()) {
   366 				StringID message;
   444 				StringID message;
   367 				SetDParam(0, v->unitnumber);
   445 				SetDParam(0, v->unitnumber);
   368 				switch (v->type) {
   446 				switch (v->type) {
   369 					case VEH_TRAIN:    message = STR_TRAIN_AUTORENEW_FAILED;       break;
   447 					case VEH_TRAIN:    message = STR_TRAIN_AUTORENEW_FAILED;       break;
   370 					case VEH_ROAD:     message = STR_ROADVEHICLE_AUTORENEW_FAILED; break;
   448 					case VEH_ROAD:     message = STR_ROADVEHICLE_AUTORENEW_FAILED; break;
   374 					default: NOT_REACHED(); message = 0; break;
   452 					default: NOT_REACHED(); message = 0; break;
   375 				}
   453 				}
   376 
   454 
   377 				AddNewsItem(message, NM_SMALL, NF_VIEWPORT|NF_VEHICLE, NT_ADVICE, DNC_NONE, v->index, 0);
   455 				AddNewsItem(message, NM_SMALL, NF_VIEWPORT|NF_VEHICLE, NT_ADVICE, DNC_NONE, v->index, 0);
   378 			}
   456 			}
   379 			if (stopped) v->vehstatus &= ~VS_STOPPED;
   457 		}
   380 			if (display_costs) _current_player = OWNER_NONE;
   458 	}
   381 			return CMD_ERROR;
   459 
   382 		}
   460 	if (flags & DC_EXEC && CmdSucceeded(cost)) {
   383 
   461 		if (v->type == VEH_TRAIN && p->renew_keep_length) {
   384 		if (flags & DC_EXEC) {
   462 			/* Remove wagons until the wanted length is reached */
   385 			break; // we are done replacing since the loop ran once with DC_EXEC
   463 			cost.AddCost(WagonRemoval(v, old_total_length));
   386 		} else if (check) {
   464 		}
   387 			/* It's a test only and we know that we can do this
   465 
   388 			 * NOTE: payment for wagon removal is NOT included in this price */
   466 		if (display_costs && IsLocalPlayer()) {
   389 			return cost;
   467 			ShowCostOrIncomeAnimation(v->x_pos, v->y_pos, v->z_pos, cost.GetCost());
   390 		}
   468 		}
   391 		// now we redo the loop, but this time we actually do stuff since we know that we can do it
   469 	}
   392 		flags |= DC_EXEC;
   470 
   393 	}
   471 	/* Start the vehicle if we stopped it earlier */
   394 
       
   395 	/* If setting is on to try not to exceed the old length of the train with the replacement */
       
   396 	if (v->type == VEH_TRAIN && p->renew_keep_length) {
       
   397 		Vehicle *temp;
       
   398 		w = v;
       
   399 
       
   400 		while (v->u.rail.cached_total_length > old_total_length) {
       
   401 			// the train is too long. We will remove cars one by one from the start of the train until it's short enough
       
   402 			while (w != NULL && RailVehInfo(w->engine_type)->railveh_type != RAILVEH_WAGON) {
       
   403 				w = GetNextVehicle(w);
       
   404 			}
       
   405 			if (w == NULL) {
       
   406 				// we failed to make the train short enough
       
   407 				SetDParam(0, v->unitnumber);
       
   408 				AddNewsItem(STR_TRAIN_TOO_LONG_AFTER_REPLACEMENT, NM_SMALL, NF_VIEWPORT | NF_VEHICLE, NT_ADVICE, DNC_NONE, v->index, 0);
       
   409 				break;
       
   410 			}
       
   411 			temp = w;
       
   412 			w = GetNextVehicle(w);
       
   413 			DoCommand(0, (INVALID_VEHICLE << 16) | temp->index, 0, DC_EXEC, CMD_MOVE_RAIL_VEHICLE);
       
   414 			MoveVehicleCargo(v, temp);
       
   415 			cost.AddCost(DoCommand(0, temp->index, 0, DC_EXEC, CMD_SELL_RAIL_WAGON));
       
   416 		}
       
   417 	}
       
   418 
       
   419 	if (stopped) v->vehstatus &= ~VS_STOPPED;
   472 	if (stopped) v->vehstatus &= ~VS_STOPPED;
   420 	if (display_costs) {
   473 
   421 		if (IsLocalPlayer()) ShowCostOrIncomeAnimation(v->x_pos, v->y_pos, v->z_pos, cost.GetCost());
       
   422 		_current_player = OWNER_NONE;
       
   423 	}
       
   424 	return cost;
   474 	return cost;
   425 }
   475 }