engine.c
author KUDr
Sun, 31 Dec 2006 23:48:04 +0000
branchcustombridgeheads
changeset 5618 a7db50b9f817
parent 5568 75f13d7bfaed
permissions -rw-r--r--
(svn r7710) [cbh] - Fix: [YAPF] one more assert fixed. Call from the TrainController() added by (r7705) has broken YAPF because it was called when vehicle was already on the next tile (with cbh choice). Before it was always called before the train entered tile with choice.
/* $Id$ */

#include "stdafx.h"
#include "openttd.h"
#include "debug.h"
#include "functions.h"
#include "table/strings.h"
#include "engine.h"
#include "gfx.h"
#include "player.h"
#include "command.h"
#include "vehicle.h"
#include "news.h"
#include "saveload.h"
#include "variables.h"
#include "train.h"
#include "newgrf_cargo.h"
#include "date.h"
#include "table/engines.h"

EngineInfo _engine_info[TOTAL_NUM_ENGINES];
RailVehicleInfo _rail_vehicle_info[NUM_TRAIN_ENGINES];
ShipVehicleInfo _ship_vehicle_info[NUM_SHIP_ENGINES];
AircraftVehicleInfo _aircraft_vehicle_info[NUM_AIRCRAFT_ENGINES];
RoadVehicleInfo _road_vehicle_info[NUM_ROAD_ENGINES];

enum {
	ENGINE_AVAILABLE   = 1,
	ENGINE_INTRODUCING = 2,
	ENGINE_PREVIEWING  = 4,
};

enum {
	YEAR_ENGINE_AGING_STOPS = 2050,
};


void ShowEnginePreviewWindow(EngineID engine);

void DeleteCustomEngineNames(void)
{
	uint i;
	StringID old;

	for (i = 0; i != TOTAL_NUM_ENGINES; i++) {
		old = _engine_name_strings[i];
		_engine_name_strings[i] = i + STR_8000_KIRBY_PAUL_TANK_STEAM;
		DeleteName(old);
	}

	_vehicle_design_names &= ~1;
}

void LoadCustomEngineNames(void)
{
	/* XXX: not done */
	DEBUG(misc, 1, "LoadCustomEngineNames: not done");
}

static void SetupEngineNames(void)
{
	StringID *name;

	for (name = _engine_name_strings; name != endof(_engine_name_strings); name++)
		*name = STR_SV_EMPTY;

	DeleteCustomEngineNames();
	LoadCustomEngineNames();
}

static void AdjustAvailAircraft(void)
{
	byte avail = 0;
	if (_cur_year >= 1955) avail |= 2; // big airport
	if (_cur_year <  1960 || _patches.always_small_airport) avail |= 1;  // small airport
	if (_cur_year >= 1963) avail |= 4; // enable heliport

	if (avail != _avail_aircraft) {
		_avail_aircraft = avail;
		InvalidateWindow(WC_BUILD_STATION, 0);
	}
}

static void CalcEngineReliability(Engine *e)
{
	uint age = e->age;

	if (age < e->duration_phase_1) {
		uint start = e->reliability_start;
		e->reliability = age * (e->reliability_max - start) / e->duration_phase_1 + start;
	} else if ((age -= e->duration_phase_1) < e->duration_phase_2 || _patches.never_expire_vehicles) {
		/* We are at the peak of this engines life. It will have max reliability.
		 * This is also true if the engines never expire. They will not go bad over time */
		e->reliability = e->reliability_max;
	} else if ((age -= e->duration_phase_2) < e->duration_phase_3) {
		uint max = e->reliability_max;
		e->reliability = (int)age * (int)(e->reliability_final - max) / e->duration_phase_3 + max;
	} else {
		/* time's up for this engine.
		 * We will now completely retire this design */
		e->player_avail = 0;
		e->reliability = e->reliability_final;
		InvalidateWindowClassesData(WC_BUILD_VEHICLE); // Kick this engine out of the lists
	}
	InvalidateWindowClasses(WC_BUILD_VEHICLE); // Update to show the new reliability
}

void AddTypeToEngines(void)
{
	Engine* e = _engines;

	do e->type = VEH_Train;    while (++e < &_engines[ROAD_ENGINES_INDEX]);
	do e->type = VEH_Road;     while (++e < &_engines[SHIP_ENGINES_INDEX]);
	do e->type = VEH_Ship;     while (++e < &_engines[AIRCRAFT_ENGINES_INDEX]);
	do e->type = VEH_Aircraft; while (++e < &_engines[TOTAL_NUM_ENGINES]);
	do e->type = VEH_Special;  while (++e < endof(_engines));
}

void StartupEngines(void)
{
	Engine *e;
	const EngineInfo *ei;
	/* Aging of vehicles stops, so account for that when starting late */
	const Date aging_date = min(_date, ConvertYMDToDate(YEAR_ENGINE_AGING_STOPS, 0, 1));

	SetupEngineNames();

	for (e = _engines, ei = _engine_info; e != endof(_engines); e++, ei++) {
		uint32 r;

		e->age = 0;
		e->railtype = ei->railtype;
		e->flags = 0;
		e->player_avail = 0;

		// The magic value of 729 days below comes from the NewGRF spec. If the
		// base intro date is before 1922 then the random number of days is not
		// added.
		r = Random();
		e->intro_date = ei->base_intro <= ConvertYMDToDate(1922, 0, 1) ? ei->base_intro : (Date)GB(r, 0, 9) + ei->base_intro;
		if (e->intro_date <= _date) {
			e->age = (aging_date - e->intro_date) >> 5;
			e->player_avail = (byte)-1;
			e->flags |= ENGINE_AVAILABLE;
		}

		e->reliability_start = GB(r, 16, 14) + 0x7AE0;
		r = Random();
		e->reliability_max   = GB(r,  0, 14) + 0xBFFF;
		e->reliability_final = GB(r, 16, 14) + 0x3FFF;

		r = Random();
		e->duration_phase_1 = GB(r, 0, 5) + 7;
		e->duration_phase_2 = GB(r, 5, 4) + ei->base_life * 12 - 96;
		e->duration_phase_3 = GB(r, 9, 7) + 120;

		e->reliability_spd_dec = (ei->unk2&0x7F) << 2;

		/* my invented flag for something that is a wagon */
		if (ei->unk2 & 0x80) {
			e->age = 0xFFFF;
		} else {
			CalcEngineReliability(e);
		}

		e->lifelength = ei->lifelength + _patches.extend_vehicle_life;

		// prevent certain engines from ever appearing.
		if (!HASBIT(ei->climates, _opt.landscape)) {
			e->flags |= ENGINE_AVAILABLE;
			e->player_avail = 0;
		}

		/* This sets up type for the engine
		 * It is needed if you want to ask the engine what type it is
		 * It should hopefully be the same as when you ask a vehicle what it is
		 * but using this, you can ask what type an engine number is
		 * even if it is not a vehicle (yet)*/
	}

	AdjustAvailAircraft();
}

static void AcceptEnginePreview(Engine *e, PlayerID player)
{
	Player *p = GetPlayer(player);

	assert(e->railtype < RAILTYPE_END);
	SETBIT(e->player_avail, player);
	SETBIT(p->avail_railtypes, e->railtype);

	e->preview_player = 0xFF;
	if (player == _local_player) {
		InvalidateWindowClassesData(WC_BUILD_VEHICLE);
		InvalidateWindowClasses(WC_REPLACE_VEHICLE);
	}
}

static PlayerID GetBestPlayer(PlayerID pp)
{
	const Player *p;
	int32 best_hist;
	PlayerID best_player;
	uint mask = 0;

	do {
		best_hist = -1;
		best_player = PLAYER_SPECTATOR;
		FOR_ALL_PLAYERS(p) {
			if (p->is_active && p->block_preview == 0 && !HASBIT(mask, p->index) &&
					p->old_economy[0].performance_history > best_hist) {
				best_hist = p->old_economy[0].performance_history;
				best_player = p->index;
			}
		}

		if (best_player == PLAYER_SPECTATOR) return PLAYER_SPECTATOR;

		SETBIT(mask, best_player);
	} while (--pp != 0);

	return best_player;
}

void EnginesDailyLoop(void)
{
	EngineID i;

	if (_cur_year >= YEAR_ENGINE_AGING_STOPS) return;

	for (i = 0; i != lengthof(_engines); i++) {
		Engine *e = &_engines[i];

		if (e->flags & ENGINE_INTRODUCING) {
			if (e->flags & ENGINE_PREVIEWING) {
				if (e->preview_player != 0xFF && !--e->preview_wait) {
					e->flags &= ~ENGINE_PREVIEWING;
					DeleteWindowById(WC_ENGINE_PREVIEW, i);
					e->preview_player++;
				}
			} else if (e->preview_player != 0xFF) {
				PlayerID best_player = GetBestPlayer(e->preview_player);

				if (best_player == PLAYER_SPECTATOR) {
					e->preview_player = 0xFF;
					continue;
				}

				if (!IsHumanPlayer(best_player)) {
					/* XXX - TTDBUG: TTD has a bug here ???? */
					AcceptEnginePreview(e, best_player);
				} else {
					e->flags |= ENGINE_PREVIEWING;
					e->preview_wait = 20;
					if (IsInteractivePlayer(best_player)) ShowEnginePreviewWindow(i);
				}
			}
		}
	}
}

/** Accept an engine prototype. XXX - it is possible that the top-player
 * changes while you are waiting to accept the offer? Then it becomes invalid
 * @param tile unused
 * @param p1 engine-prototype offered
 * @param p2 unused
 */
int32 CmdWantEnginePreview(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
{
	Engine *e;

	if (!IsEngineIndex(p1)) return CMD_ERROR;
	e = GetEngine(p1);
	if (GetBestPlayer(e->preview_player) != _current_player) return CMD_ERROR;

	if (flags & DC_EXEC) AcceptEnginePreview(e, _current_player);

	return 0;
}

// Determine if an engine type is a wagon (and not a loco)
static bool IsWagon(EngineID index)
{
	return index < NUM_TRAIN_ENGINES && RailVehInfo(index)->flags & RVI_WAGON;
}

static void NewVehicleAvailable(Engine *e)
{
	Vehicle *v;
	Player *p;
	EngineID index = e - _engines;

	// In case the player didn't build the vehicle during the intro period,
	// prevent that player from getting future intro periods for a while.
	if (e->flags & ENGINE_INTRODUCING) {
		FOR_ALL_PLAYERS(p) {
			uint block_preview = p->block_preview;

			if (!HASBIT(e->player_avail, p->index)) continue;

			/* We assume the user did NOT build it.. prove me wrong ;) */
			p->block_preview = 20;

			FOR_ALL_VEHICLES(v) {
				if (v->type == VEH_Train || v->type == VEH_Road || v->type == VEH_Ship ||
						(v->type == VEH_Aircraft && v->subtype <= 2)) {
					if (v->owner == p->index && v->engine_type == index) {
						/* The user did prove me wrong, so restore old value */
						p->block_preview = block_preview;
						break;
					}
				}
			}
		}
	}

	e->flags = (e->flags & ~ENGINE_INTRODUCING) | ENGINE_AVAILABLE;
	InvalidateWindowClassesData(WC_BUILD_VEHICLE);
	InvalidateWindowClasses(WC_REPLACE_VEHICLE);

	// Now available for all players
	e->player_avail = (byte)-1;

	// Do not introduce new rail wagons
	if (IsWagon(index)) return;

	// make maglev / monorail available
	FOR_ALL_PLAYERS(p) {
		if (p->is_active) {
			assert(e->railtype < RAILTYPE_END);
			SETBIT(p->avail_railtypes, e->railtype);
		}
	}

	if (index < NUM_TRAIN_ENGINES) {
		AddNewsItem(index, NEWS_FLAGS(NM_CALLBACK, 0, NT_NEW_VEHICLES, DNC_TRAINAVAIL), 0, 0);
	} else if (index < NUM_TRAIN_ENGINES + NUM_ROAD_ENGINES) {
		AddNewsItem(index, NEWS_FLAGS(NM_CALLBACK, 0, NT_NEW_VEHICLES, DNC_ROADAVAIL), 0, 0);
	} else if (index < NUM_TRAIN_ENGINES + NUM_ROAD_ENGINES + NUM_SHIP_ENGINES) {
		AddNewsItem(index, NEWS_FLAGS(NM_CALLBACK, 0, NT_NEW_VEHICLES, DNC_SHIPAVAIL), 0, 0);
	} else {
		AddNewsItem(index, NEWS_FLAGS(NM_CALLBACK, 0, NT_NEW_VEHICLES, DNC_AIRCRAFTAVAIL), 0, 0);
	}
}

void EnginesMonthlyLoop(void)
{
	Engine *e;

	if (_cur_year < YEAR_ENGINE_AGING_STOPS) {
		for (e = _engines; e != endof(_engines); e++) {
			// Age the vehicle
			if (e->flags & ENGINE_AVAILABLE && e->age != 0xFFFF) {
				e->age++;
				CalcEngineReliability(e);
			}

			if (!(e->flags & ENGINE_AVAILABLE) && _date >= (e->intro_date + 365)) {
				// Introduce it to all players
				NewVehicleAvailable(e);
			} else if (!(e->flags & (ENGINE_AVAILABLE|ENGINE_INTRODUCING)) && _date >= e->intro_date) {
				// Introduction date has passed.. show introducing dialog to one player.
				e->flags |= ENGINE_INTRODUCING;

				// Do not introduce new rail wagons
				if (!IsWagon(e - _engines))
					e->preview_player = 1; // Give to the player with the highest rating.
			}
		}
	}
	AdjustAvailAircraft();
}

/** Rename an engine.
 * @param tile unused
 * @param p1 engine ID to rename
 * @param p2 unused
 */
int32 CmdRenameEngine(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
{
	StringID str;

	if (!IsEngineIndex(p1) || _cmd_text[0] == '\0') return CMD_ERROR;

	str = AllocateNameUnique(_cmd_text, 0);
	if (str == 0) return CMD_ERROR;

	if (flags & DC_EXEC) {
		StringID old_str = _engine_name_strings[p1];
		_engine_name_strings[p1] = str;
		DeleteName(old_str);
		_vehicle_design_names |= 3;
		MarkWholeScreenDirty();
	} else {
		DeleteName(str);
	}

	return 0;
}


/*
 * returns true if an engine is valid, of the specified type, and buildable by
 * the given player, false otherwise
 *
 * engine = index of the engine to check
 * type   = the type the engine should be of (VEH_xxx)
 * player = index of the player
 */
bool IsEngineBuildable(EngineID engine, byte type, PlayerID player)
{
	const Engine *e;

	// check if it's an engine that is in the engine array
	if (!IsEngineIndex(engine)) return false;

	e = GetEngine(engine);

	// check if it's an engine of specified type
	if (e->type != type) return false;

	// check if it's available
	if (!HASBIT(e->player_avail, player)) return false;

	return true;
}

/************************************************************************
 * Engine Replacement stuff
 ************************************************************************/

static void EngineRenewPoolNewBlock(uint start_item);

DEFINE_OLD_POOL(EngineRenew, EngineRenew, EngineRenewPoolNewBlock, NULL)

static void EngineRenewPoolNewBlock(uint start_item)
{
	EngineRenew *er;

	/* We don't use FOR_ALL here, because FOR_ALL skips invalid items.
	 *  TODO - This is just a temporary stage, this will be removed. */
	for (er = GetEngineRenew(start_item); er != NULL; er = (er->index + 1U < GetEngineRenewPoolSize()) ? GetEngineRenew(er->index + 1U) : NULL) {
		er->index = start_item++;
		er->from = INVALID_ENGINE;
	}
}


static EngineRenew *AllocateEngineRenew(void)
{
	EngineRenew *er;

	/* We don't use FOR_ALL here, because FOR_ALL skips invalid items.
	 *  TODO - This is just a temporary stage, this will be removed. */
	for (er = GetEngineRenew(0); er != NULL; er = (er->index + 1U < GetEngineRenewPoolSize()) ? GetEngineRenew(er->index + 1U) : NULL) {
		if (IsValidEngineRenew(er)) continue;

		er->to = INVALID_ENGINE;
		er->next = NULL;
		return er;
	}

	/* Check if we can add a block to the pool */
	if (AddBlockToPool(&_EngineRenew_pool)) return AllocateEngineRenew();

	return NULL;
}

/**
 * Retrieves the EngineRenew that specifies the replacement of the given
 * engine type from the given renewlist */
static EngineRenew *GetEngineReplacement(EngineRenewList erl, EngineID engine)
{
	EngineRenew *er = (EngineRenew *)erl;

	while (er) {
		if (er->from == engine) return er;
		er = er->next;
	}
	return NULL;
}

void RemoveAllEngineReplacement(EngineRenewList *erl)
{
	EngineRenew *er = (EngineRenew *)(*erl);
	EngineRenew *next;

	while (er) {
		next = er->next;
		DeleteEngineRenew(er);
		er = next;
	}
	*erl = NULL; // Empty list
}

EngineID EngineReplacement(EngineRenewList erl, EngineID engine)
{
	const EngineRenew *er = GetEngineReplacement(erl, engine);
	return er == NULL ? INVALID_ENGINE : er->to;
}

int32 AddEngineReplacement(EngineRenewList *erl, EngineID old_engine, EngineID new_engine, uint32 flags)
{
	EngineRenew *er;

	/* Check if the old vehicle is already in the list */
	er = GetEngineReplacement(*erl, old_engine);
	if (er != NULL) {
		if (flags & DC_EXEC) er->to = new_engine;
		return 0;
	}

	er = AllocateEngineRenew();
	if (er == NULL) return CMD_ERROR;

	if (flags & DC_EXEC) {
		er->from = old_engine;
		er->to = new_engine;

		/* Insert before the first element */
		er->next = (EngineRenew *)(*erl);
		*erl = (EngineRenewList)er;
	}

	return 0;
}

int32 RemoveEngineReplacement(EngineRenewList *erl, EngineID engine, uint32 flags)
{
	EngineRenew *er = (EngineRenew *)(*erl);
	EngineRenew *prev = NULL;

	while (er)
	{
		if (er->from == engine) {
			if (flags & DC_EXEC) {
				if (prev == NULL) { // First element
					/* The second becomes the new first element */
					*erl = (EngineRenewList)er->next;
				} else {
					/* Cut this element out */
					prev->next = er->next;
				}
				DeleteEngineRenew(er);
			}
			return 0;
		}
		prev = er;
		er = er->next;
	}

	return CMD_ERROR;
}

static const SaveLoad _engine_renew_desc[] = {
	SLE_VAR(EngineRenew, from, SLE_UINT16),
	SLE_VAR(EngineRenew, to,   SLE_UINT16),

	SLE_REF(EngineRenew, next, REF_ENGINE_RENEWS),

	SLE_END()
};

static void Save_ERNW(void)
{
	EngineRenew *er;

	FOR_ALL_ENGINE_RENEWS(er) {
		SlSetArrayIndex(er->index);
		SlObject(er, _engine_renew_desc);
	}
}

static void Load_ERNW(void)
{
	int index;

	while ((index = SlIterateArray()) != -1) {
		EngineRenew *er;

		if (!AddBlockIfNeeded(&_EngineRenew_pool, index))
			error("EngineRenews: failed loading savegame: too many EngineRenews");

		er = GetEngineRenew(index);
		SlObject(er, _engine_renew_desc);
	}
}

static const SaveLoad _engine_desc[] = {
	SLE_CONDVAR(Engine, intro_date,          SLE_FILE_U16 | SLE_VAR_I32,  0,  30),
	SLE_CONDVAR(Engine, intro_date,          SLE_INT32,                  31, SL_MAX_VERSION),
	SLE_CONDVAR(Engine, age,                 SLE_FILE_U16 | SLE_VAR_I32,  0,  30),
	SLE_CONDVAR(Engine, age,                 SLE_INT32,                  31, SL_MAX_VERSION),
	    SLE_VAR(Engine, reliability,         SLE_UINT16),
	    SLE_VAR(Engine, reliability_spd_dec, SLE_UINT16),
	    SLE_VAR(Engine, reliability_start,   SLE_UINT16),
	    SLE_VAR(Engine, reliability_max,     SLE_UINT16),
	    SLE_VAR(Engine, reliability_final,   SLE_UINT16),
	    SLE_VAR(Engine, duration_phase_1,    SLE_UINT16),
	    SLE_VAR(Engine, duration_phase_2,    SLE_UINT16),
	    SLE_VAR(Engine, duration_phase_3,    SLE_UINT16),

	    SLE_VAR(Engine, lifelength,          SLE_UINT8),
	    SLE_VAR(Engine, flags,               SLE_UINT8),
	    SLE_VAR(Engine, preview_player,      SLE_UINT8),
	    SLE_VAR(Engine, preview_wait,        SLE_UINT8),
	    SLE_VAR(Engine, railtype,            SLE_UINT8),
	    SLE_VAR(Engine, player_avail,        SLE_UINT8),

	// reserve extra space in savegame here. (currently 16 bytes)
	SLE_CONDNULL(16, 2, SL_MAX_VERSION),

	SLE_END()
};

static void Save_ENGN(void)
{
	uint i;

	for (i = 0; i != lengthof(_engines); i++) {
		SlSetArrayIndex(i);
		SlObject(&_engines[i], _engine_desc);
	}
}

static void Load_ENGN(void)
{
	int index;
	while ((index = SlIterateArray()) != -1) {
		SlObject(GetEngine(index), _engine_desc);
	}
}

static void LoadSave_ENGS(void)
{
	SlArray(_engine_name_strings, lengthof(_engine_name_strings), SLE_STRINGID);
}

const ChunkHandler _engine_chunk_handlers[] = {
	{ 'ENGN', Save_ENGN,     Load_ENGN,     CH_ARRAY          },
	{ 'ENGS', LoadSave_ENGS, LoadSave_ENGS, CH_RIFF           },
	{ 'ERNW', Save_ERNW,     Load_ERNW,     CH_ARRAY | CH_LAST},
};

void InitializeEngines(void)
{
	/* Clean the engine renew pool and create 1 block in it */
	CleanPool(&_EngineRenew_pool);
	AddBlockToPool(&_EngineRenew_pool);
}